Skip to content

Improve type hints for SFTPClient directory methods (listdir, scandir, readdir) using @overload #805

@vivodi

Description

@vivodi

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:

  1. Making SFTPName a Generic class to preserve the type of filename and longname.
  2. 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:

  1. Strict Type Safety: It eliminates false-positive type errors in user code and removes the need for boilerplate type-narrowing when iterating over directories.
  2. Better IDE Support: Developers will get immediate, accurate autocompletion for str or bytes depending on what they passed in.
  3. Zero Runtime Cost: This is a purely static typing enhancement (can be done in .pyi stubs or inline) and won't affect performance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions