Python 3.6 will have a new dunder protocol, __fspath__() , which should be supported by classes that represent filesystem paths. Example of such classes are the pathlib.Path family and os.DirEntry(returned by os.scandir() ).
You can read more about this protocol in the brand new PEP 519. In this blog post I’m going to discuss how we would add type annotations for these additions to the standard library.
I’m making frequent use of AnyStr , a quite magical type variable predefined in the typing module. If you’re not familiar with it, I recommend reading my blog post about AnyStr . You may also want to read up on generics in PEP 484(or read mypy’s docs on the subject).
Adding os.scandir() to the stubs for os.py
For practice, let’s see if we can add something to the stub file for os.py. As of this writing there’s no typeshed information for os.scandir() , which I think is a shame. I think the following will do nicely. Note how we only define DirEntry and scandir() for Python versions >= 3.5.(Mypydoesn’t support this yet, but it will soon, and the example here still works — it just doesn’t realize scandir() is only available in Python 3.5.) This could be added to the end of stdlib/3/os/__init__.pyi:
Deconstructing this a bit, we see a generic class(that’s what the Generic[AnyStr] base class means) and an overloaded function. The scandir() definition uses @overload because it can also be called without arguments. We could also write it as follows; it’ll work either way:
That’s really all there is to it(except for the sys.version_info check, which I’ll leave out here since it doesn’t really work yet). Next we define os.fspath() , which wraps this protocol. It’s slightly more complicated than just calling its argument’s __fspath__() method, because it also handles strings and bytes. So here it is:
Adding os.scandir() to the stubs for os.py
from typing import Generic, AnyStr, overload, Iterator
if sys.version_info >= (3, 5):
class DirEntry(Generic[AnyStr]):
name = ... # type: AnyStr
path = ... # type: AnyStr
def inode(self) -> int: ...
def is_dir(self, *, follow_symlinks: bool = ...) -> bool: ...
def is_file(self, *, follow_symlinks: bool = ...) -> bool: ...
def is_symlink(self) -> bool: ...
def stat(self, *, follow_symlinks: bool = ...) -> stat_result: ...
@overload
def scandir() -> Iterator[DirEntry[str]]: ...
@overload
def scandir(path: AnyStr) -> Iterator[DirEntry[AnyStr]]: ...
@overload
def scandir(path: str = ...) -> Iterator[DirEntry[str]]: ...
@overload
def scandir(path: bytes) -> Iterator[DirEntry[bytes]]: ...
Adding os.fspath()
class PathLike(Generic[AnyStr]):
@abstractmethod
def __fspath__(self) -> AnyStr: ...