I would like to suggest an improvement to the type hints for directory-reading methods in SFTPClient, specifically listdir, scandir, and readdir.
The Problem:
Currently, the signatures for these methods look something like this:
async def listdir(self, path: PurePath | str | bytes) -> list[str | bytes]
async def scandir(self, path: PurePath | str | bytes) -> AsyncIterator[SFTPName] (where SFTPName.filename is str | bytes)
While this accurately reflects that the methods can handle both strings and bytes, it causes friction during static type checking (with tools like Mypy or Pyright).
Because the return type is a broad union (str | bytes), users who pass a str or PurePath as the input path are forced to add isinstance(name, str) checks or # type: ignore comments before they can use standard string methods (like .endswith()) on the results.
The Proposed Solution:
We can align the typing behavior with Python's standard library (e.g., os.listdir and os.scandir), where the input type dictates the output type.
This can be achieved by:
- Making
SFTPName a Generic class to preserve the type of filename and longname.
- Using
typing.overload on listdir, scandir, and readdir.
Example Implementation:
import os
from typing import overload, Union, AsyncIterator, Sequence, Generic, TypeVar
from pathlib import PurePath
# Use AnyStr-like TypeVar to bind the type
T = TypeVar("T", str, bytes)
class SFTPName(Generic[T]):
filename: T
longname: T
attrs: 'SFTPAttrs'
# ... existing implementation
class SFTPClient:
# --- listdir ---
@overload
async def listdir(self, path: Union[PurePath, str] = '.') -> list[str]: ...
@overload
async def listdir(self, path: bytes) -> list[bytes]: ...
async def listdir(self, path: Union[PurePath, str, bytes] = '.') -> Union[list[str], list[bytes]]:
# ... existing implementation ...
pass
# --- scandir ---
@overload
def scandir(self, path: Union[PurePath, str] = '.') -> AsyncIterator[SFTPName[str]]: ...
@overload
def scandir(self, path: bytes) -> AsyncIterator[SFTPName[bytes]]: ...
def scandir(self, path: Union[PurePath, str, bytes] = '.') -> AsyncIterator[SFTPName]:
# ... existing implementation ...
pass
# --- readdir ---
@overload
async def readdir(self, path: Union[PurePath, str] = '.') -> Sequence[SFTPName[str]]: ...
@overload
async def readdir(self, path: bytes) -> Sequence[SFTPName[bytes]]: ...
async def readdir(self, path: Union[PurePath, str, bytes] = '.') -> Sequence[SFTPName]:
# ... existing implementation ...
pass
Benefits of this change:
- Strict Type Safety: It eliminates false-positive type errors in user code and removes the need for boilerplate type-narrowing when iterating over directories.
- Better IDE Support: Developers will get immediate, accurate autocompletion for
str or bytes depending on what they passed in.
- Zero Runtime Cost: This is a purely static typing enhancement (can be done in
.pyi stubs or inline) and won't affect performance.
I would like to suggest an improvement to the type hints for directory-reading methods in
SFTPClient, specificallylistdir,scandir, andreaddir.The Problem:
Currently, the signatures for these methods look something like this:
async def listdir(self, path: PurePath | str | bytes) -> list[str | bytes]async def scandir(self, path: PurePath | str | bytes) -> AsyncIterator[SFTPName](whereSFTPName.filenameisstr | bytes)While this accurately reflects that the methods can handle both strings and bytes, it causes friction during static type checking (with tools like Mypy or Pyright).
Because the return type is a broad union (
str | bytes), users who pass astrorPurePathas the input path are forced to addisinstance(name, str)checks or# type: ignorecomments before they can use standard string methods (like.endswith()) on the results.The Proposed Solution:
We can align the typing behavior with Python's standard library (e.g.,
os.listdirandos.scandir), where the input type dictates the output type.This can be achieved by:
SFTPNameaGenericclass to preserve the type offilenameandlongname.typing.overloadonlistdir,scandir, andreaddir.Example Implementation:
Benefits of this change:
strorbytesdepending on what they passed in..pyistubs or inline) and won't affect performance.