Adding type annotations for PEP 519
Python 3.6 will have a new , __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 . 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 . You may also want to read up on (or read ).
Adding os.scandir() to the stubs for os.py
For practice, let’s see if we can add something to the . As of this writing there’s no information for , 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. (Mypy , 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:
from typing import Generic, AnyStr, overload, Iterator
if sys.version_info >= (3, 5):
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: ...
def scandir() -> Iterator[DirEntry[str]]: ...
def scandir(path: AnyStr) -> Iterator[DirEntry[AnyStr]]: ...
Deconstructing this a bit, we see a (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:
def scandir(path: str = ...) -> Iterator[DirEntry[str]]: ...
def scandir(path: bytes) -> Iterator[DirEntry[bytes]]: ...
Either way there really are three ways to call scandir() , all three returning an iterable of DirEntry objects:
- scandir() -> Iterator[DirEntry[str]]
- scandir(str) -> Iterator[DirEntry[str]]
- scandir(bytes) -> Iterator[DirEntry[bytes]]
Next I’ll show how to add os.fspath() and how to add support for the __fspath__() protocol to DirEntry .
defines a simple ABC (), PathLike , with one method, __fspath__() . We need to add this to the , as follows:
def __fspath__(self) -> AnyStr: ...
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: