Skip to content

add new pathlib base classes for 3.13 #12937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions stdlib/VERSIONS
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ os: 3.0-
ossaudiodev: 3.0-3.12
parser: 3.0-3.9
pathlib: 3.4-
pathlib._abc: 3.13-
pdb: 3.0-
pickle: 3.0-
pickletools: 3.0-
Expand Down
24 changes: 18 additions & 6 deletions stdlib/pathlib.pyi → stdlib/pathlib/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,23 @@ from types import TracebackType
from typing import IO, Any, BinaryIO, ClassVar, Literal, overload
from typing_extensions import Self, deprecated

if sys.version_info >= (3, 13):
from pathlib._abc import PathBase as _PathBase, PurePathBase as _PurePathBase
else:
_PathBase = object
_PurePathBase = object

if sys.version_info >= (3, 9):
from types import GenericAlias

__all__ = ["PurePath", "PurePosixPath", "PureWindowsPath", "Path", "PosixPath", "WindowsPath"]

if sys.version_info >= (3, 13):
from pathlib._abc import UnsupportedOperation as UnsupportedOperation

__all__ += ["UnsupportedOperation"]

class PurePath(PathLike[str]):
class PurePath(PathLike[str], _PurePathBase):
if sys.version_info >= (3, 13):
parser: ClassVar[types.ModuleType]
def full_match(self, pattern: StrPath, *, case_sensitive: bool | None = None) -> bool: ...
Expand Down Expand Up @@ -100,7 +108,9 @@ class PurePath(PathLike[str]):
class PurePosixPath(PurePath): ...
class PureWindowsPath(PurePath): ...

class Path(PurePath):
# This should be Path(_PathBase, PurePath), but _PathBase has to be at the end for the
# _Pathbase = object trick to work.
class Path(PurePath, _PathBase):
if sys.version_info >= (3, 12):
def __new__(cls, *args: StrPath, **kwargs: Unused) -> Self: ... # pyright: ignore[reportInconsistentConstructor]
else:
Expand Down Expand Up @@ -224,7 +234,12 @@ class Path(PurePath):
# Fallback if mode is not specified
@overload
def open(
self, mode: str, buffering: int = -1, encoding: str | None = None, errors: str | None = None, newline: str | None = None
self,
mode: str = "r",
buffering: int = -1,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
Comment on lines +237 to +242
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is just wrong. It should probably say "Fallback if mode is str". Having a default for mode doesn't make much sense because now this overload is basically identical to the first (non-3.14) in the case no mode is specified, one of the previous overloads will match.

But it's probably best to leave out this change from this PR and handle it in a separate PR anyway.

) -> IO[Any]: ...
if sys.platform != "win32":
# These methods do "exist" on Windows, but they always raise NotImplementedError,
Expand Down Expand Up @@ -291,6 +306,3 @@ class Path(PurePath):

class PosixPath(Path, PurePosixPath): ...
class WindowsPath(Path, PureWindowsPath): ...

if sys.version_info >= (3, 13):
class UnsupportedOperation(NotImplementedError): ...
128 changes: 128 additions & 0 deletions stdlib/pathlib/_abc.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import types
from _typeshed import AnyOrLiteralStr, BytesPath, ReadableBuffer, StrOrBytesPath, StrPath
from collections.abc import Callable, Generator, Iterator, Sequence
from os import PathLike, stat_result
from typing import IO, Any, AnyStr, ClassVar, overload
from typing_extensions import LiteralString, Self

__all__ = ["UnsupportedOperation"]

class UnsupportedOperation(NotImplementedError): ...

class ParserBase:
@property
def sep(self) -> str: ...
@overload
def join(self, path: LiteralString, *paths: LiteralString) -> LiteralString: ...
@overload
def join(self, path: StrPath, *paths: StrPath) -> str: ...
@overload
def join(self, path: BytesPath, *paths: BytesPath) -> bytes: ...
@overload
def split(self, path: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ...
@overload
def split(self, path: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ...
@overload
def splitdrive(self, path: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ...
@overload
def splitdrive(self, path: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ...
@overload
def normcase(self, path: PathLike[AnyStr]) -> AnyStr: ...
@overload
def normcase(self, path: AnyOrLiteralStr) -> AnyOrLiteralStr: ...
def isabs(self, path: StrOrBytesPath) -> bool: ...

class PurePathBase:
parser: ClassVar[types.ModuleType | ParserBase]
def __init__(self, path: StrPath, *paths: StrPath) -> None: ...
def with_segments(self, *pathsegments: StrPath) -> Self: ...
def as_posix(self) -> str: ...
@property
def drive(self) -> str: ...
@property
def root(self) -> str: ...
@property
def anchor(self) -> str: ...
@property
def name(self) -> str: ...
@property
def suffix(self) -> str: ...
@property
def suffixes(self) -> list[str]: ...
@property
def stem(self) -> str: ...
def with_name(self, name: str) -> Self: ...
def with_stem(self, stem: str) -> Self: ...
def with_suffix(self, suffix: str) -> Self: ...
def relative_to(self, other: StrPath, *, walk_up: bool = False) -> Self: ...
def is_relative_to(self, other: StrPath) -> bool: ...
@property
def parts(self) -> tuple[str, ...]: ...
def joinpath(self, *pathsegments: StrPath) -> Self: ...
def __truediv__(self, key: StrPath) -> Self: ...
def __rtruediv__(self, key: StrPath) -> Self: ...
@property
def parent(self) -> Self: ...
@property
def parents(self) -> Sequence[Self]: ...
def is_absolute(self) -> bool: ...
def match(self, path_pattern: str, *, case_sensitive: bool | None = None) -> bool: ...
def full_match(self, pattern: StrPath, *, case_sensitive: bool | None = None) -> bool: ...

class PathBase(PurePathBase):
def stat(self, *, follow_symlinks: bool = True) -> stat_result: ...
def lstat(self) -> stat_result: ...
def exists(self, *, follow_symlinks: bool = True) -> bool: ...
def is_dir(self, *, follow_symlinks: bool = True) -> bool: ...
def is_file(self, *, follow_symlinks: bool = True) -> bool: ...
def is_mount(self) -> bool: ...
def is_symlink(self) -> bool: ...
def is_junction(self) -> bool: ...
def is_block_device(self) -> bool: ...
def is_char_device(self) -> bool: ...
def is_fifo(self) -> bool: ...
def is_socket(self) -> bool: ...
def samefile(self, other_path: StrPath) -> bool: ...
def open(
self,
mode: str = "r",
buffering: int = -1,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> IO[Any]: ...
Comment on lines +86 to +93
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably use the same overloads as open()/Path.open() does. But since this is a new class, we should leave out the mode: str fallback.

def read_bytes(self) -> bytes: ...
def read_text(self, encoding: str | None = None, errors: str | None = None, newline: str | None = None) -> str: ...
def write_bytes(self, data: ReadableBuffer) -> int: ...
def write_text(
self, data: str, encoding: str | None = None, errors: str | None = None, newline: str | None = None
) -> int: ...
def iterdir(self) -> Generator[Self, None, None]: ...
def glob(self, pattern: str, *, case_sensitive: bool | None = None, recurse_symlinks: bool = True) -> Iterator[Self]: ...
def rglob(self, pattern: str, *, case_sensitive: bool | None = None, recurse_symlinks: bool = True) -> Iterator[Self]: ...
def walk(
self, top_down: bool = True, on_error: Callable[[OSError], object] | None = None, follow_symlinks: bool = False
) -> Iterator[tuple[Self, list[str], list[str]]]: ...
def absolute(self) -> Self: ...
@classmethod
def cwd(cls) -> Self: ...
def expanduser(self) -> Self: ...
@classmethod
def home(cls) -> Self: ...
def readlink(self) -> Self: ...
def resolve(self, strict: bool = False) -> Self: ...
def symlink_to(self, target: StrOrBytesPath, target_is_directory: bool = False) -> None: ...
def hardlink_to(self, target: StrOrBytesPath) -> None: ...
def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: ...
def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None: ...
def rename(self, target: StrPath) -> Self: ...
def replace(self, target: StrPath) -> Self: ...
def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: ...
def lchmod(self, mode: int) -> None: ...
def unlink(self, missing_ok: bool = False) -> None: ...
def rmdir(self) -> None: ...
def owner(self, *, follow_symlinks: bool = True) -> str: ...
def group(self, *, follow_symlinks: bool = True) -> str: ...
@classmethod
def from_uri(cls, uri: str) -> Self: ...
def as_uri(self) -> str: ...