Skip to content

Commit 3d9816b

Browse files
committed
Add typehints to pathlib.types
1 parent ef06508 commit 3d9816b

File tree

1 file changed

+59
-35
lines changed

1 file changed

+59
-35
lines changed

Lib/pathlib/types.py

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@
1414
from glob import _PathGlobber
1515
from pathlib._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj
1616
from pathlib import PurePath, Path
17-
from typing import Optional, Protocol, runtime_checkable
17+
from typing import (
18+
Any, BinaryIO, Callable, Generator, Optional, Protocol, Sequence, TypeVar, Union,
19+
runtime_checkable,
20+
)
21+
22+
23+
_JP = TypeVar("_JP", bound="_JoinablePath")
24+
_RP = TypeVar("_RP", bound="_ReadablePath")
25+
_WP = TypeVar("_WP", bound="_WritablePath")
1826

1927

2028
def _explode_path(path):
@@ -72,38 +80,38 @@ class _JoinablePath(ABC):
7280

7381
@property
7482
@abstractmethod
75-
def parser(self):
83+
def parser(self) -> _PathParser:
7684
"""Implementation of pathlib._types.Parser used for low-level path
7785
parsing and manipulation.
7886
"""
7987
raise NotImplementedError
8088

8189
@abstractmethod
82-
def with_segments(self, *pathsegments):
90+
def with_segments(self: _JP, *pathsegments: Union[_JP, str]) -> _JP:
8391
"""Construct a new path object from any number of path-like objects.
8492
Subclasses may override this method to customize how new path objects
8593
are created from methods like `iterdir()`.
8694
"""
8795
raise NotImplementedError
8896

8997
@abstractmethod
90-
def __str__(self):
98+
def __str__(self) -> str:
9199
"""Return the string representation of the path, suitable for
92100
passing to system calls."""
93101
raise NotImplementedError
94102

95103
@property
96-
def anchor(self):
104+
def anchor(self) -> str:
97105
"""The concatenation of the drive and root, or ''."""
98106
return _explode_path(self)[0]
99107

100108
@property
101-
def name(self):
109+
def name(self) -> str:
102110
"""The final path component, if any."""
103111
return self.parser.split(str(self))[1]
104112

105113
@property
106-
def suffix(self):
114+
def suffix(self) -> str:
107115
"""
108116
The final component's last suffix, if any.
109117
@@ -112,7 +120,7 @@ def suffix(self):
112120
return self.parser.splitext(self.name)[1]
113121

114122
@property
115-
def suffixes(self):
123+
def suffixes(self) -> Sequence[str]:
116124
"""
117125
A list of the final component's suffixes, if any.
118126
@@ -127,11 +135,11 @@ def suffixes(self):
127135
return suffixes[::-1]
128136

129137
@property
130-
def stem(self):
138+
def stem(self) -> str:
131139
"""The final path component, minus its last suffix."""
132140
return self.parser.splitext(self.name)[0]
133141

134-
def with_name(self, name):
142+
def with_name(self: _JP, name: str) -> _JP:
135143
"""Return a new path with the file name changed."""
136144
split = self.parser.split
137145
if split(name)[0]:
@@ -140,7 +148,7 @@ def with_name(self, name):
140148
path = path.removesuffix(split(path)[1]) + name
141149
return self.with_segments(path)
142150

143-
def with_stem(self, stem):
151+
def with_stem(self: _JP, stem: str) -> _JP:
144152
"""Return a new path with the stem changed."""
145153
suffix = self.suffix
146154
if not suffix:
@@ -151,7 +159,7 @@ def with_stem(self, stem):
151159
else:
152160
return self.with_name(stem + suffix)
153161

154-
def with_suffix(self, suffix):
162+
def with_suffix(self: _JP, suffix: str) -> _JP:
155163
"""Return a new path with the file suffix changed. If the path
156164
has no suffix, add given suffix. If the given suffix is an empty
157165
string, remove the suffix from the path.
@@ -166,36 +174,36 @@ def with_suffix(self, suffix):
166174
return self.with_name(stem + suffix)
167175

168176
@property
169-
def parts(self):
177+
def parts(self) -> Sequence[str]:
170178
"""An object providing sequence-like access to the
171179
components in the filesystem path."""
172180
anchor, parts = _explode_path(self)
173181
if anchor:
174182
parts.append(anchor)
175183
return tuple(reversed(parts))
176184

177-
def joinpath(self, *pathsegments):
185+
def joinpath(self: _JP, *pathsegments: Union[_JP, str]) -> _JP:
178186
"""Combine this path with one or several arguments, and return a
179187
new path representing either a subpath (if all arguments are relative
180188
paths) or a totally different path (if one of the arguments is
181189
anchored).
182190
"""
183191
return self.with_segments(str(self), *pathsegments)
184192

185-
def __truediv__(self, key):
193+
def __truediv__(self: _JP, key: Union[_JP, str]) -> _JP:
186194
try:
187195
return self.with_segments(str(self), key)
188196
except TypeError:
189197
return NotImplemented
190198

191-
def __rtruediv__(self, key):
199+
def __rtruediv__(self: _JP, key: Union[_JP, str]) -> _JP:
192200
try:
193201
return self.with_segments(key, str(self))
194202
except TypeError:
195203
return NotImplemented
196204

197205
@property
198-
def parent(self):
206+
def parent(self: _JP) -> _JP:
199207
"""The logical parent of the path."""
200208
path = str(self)
201209
parent = self.parser.split(path)[0]
@@ -204,7 +212,7 @@ def parent(self):
204212
return self
205213

206214
@property
207-
def parents(self):
215+
def parents(self: _JP) -> Sequence[_JP]:
208216
"""A sequence of this path's logical parents."""
209217
split = self.parser.split
210218
path = str(self)
@@ -216,7 +224,7 @@ def parents(self):
216224
parent = split(path)[0]
217225
return tuple(parents)
218226

219-
def full_match(self, pattern):
227+
def full_match(self: _JP, pattern: Union[_JP, str]) -> bool:
220228
"""
221229
Return True if this path matches the given glob-style pattern. The
222230
pattern is matched against the entire path.
@@ -240,45 +248,50 @@ class _ReadablePath(_JoinablePath):
240248

241249
@property
242250
@abstractmethod
243-
def info(self):
251+
def info(self) -> PathInfo:
244252
"""
245253
A PathInfo object that exposes the file type and other file attributes
246254
of this path.
247255
"""
248256
raise NotImplementedError
249257

250258
@abstractmethod
251-
def __open_rb__(self, buffering=-1):
259+
def __open_rb__(self, buffering: int = -1) -> BinaryIO:
252260
"""
253261
Open the file pointed to by this path for reading in binary mode and
254262
return a file object, like open(mode='rb').
255263
"""
256264
raise NotImplementedError
257265

258-
def read_bytes(self):
266+
def read_bytes(self) -> bytes:
259267
"""
260268
Open the file in bytes mode, read it, and close the file.
261269
"""
262270
with magic_open(self, mode='rb', buffering=0) as f:
263271
return f.read()
264272

265-
def read_text(self, encoding=None, errors=None, newline=None):
273+
def read_text(
274+
self,
275+
encoding: Optional[str] = None,
276+
errors: Optional[str] = None,
277+
newline: Optional[str] = None,
278+
) -> str:
266279
"""
267280
Open the file in text mode, read it, and close the file.
268281
"""
269282
with magic_open(self, mode='r', encoding=encoding, errors=errors, newline=newline) as f:
270283
return f.read()
271284

272285
@abstractmethod
273-
def iterdir(self):
286+
def iterdir(self: _RP) -> Generator[_RP, None, None]:
274287
"""Yield path objects of the directory contents.
275288
276289
The children are yielded in arbitrary order, and the
277290
special entries '.' and '..' are not included.
278291
"""
279292
raise NotImplementedError
280293

281-
def glob(self, pattern, *, recurse_symlinks=True):
294+
def glob(self: _RP, pattern: Union[_RP, str], *, recurse_symlinks: bool = True) -> Generator[_RP, None, None]:
282295
"""Iterate over this subtree and yield all existing files (of any
283296
kind, including directories) matching the given relative pattern.
284297
"""
@@ -294,7 +307,12 @@ def glob(self, pattern, *, recurse_symlinks=True):
294307
select = globber.selector(parts)
295308
return select(self.joinpath(''))
296309

297-
def walk(self, top_down=True, on_error=None, follow_symlinks=False):
310+
def walk(
311+
self: _RP,
312+
top_down: bool = True,
313+
on_error: Optional[Callable[[Exception], None]] = None,
314+
follow_symlinks: bool = False,
315+
) -> Generator[tuple[_RP, list[str], list[str]], None, None]:
298316
"""Walk the directory tree from this directory, similar to os.walk()."""
299317
paths = [self]
300318
while paths:
@@ -326,13 +344,13 @@ def walk(self, top_down=True, on_error=None, follow_symlinks=False):
326344
paths += [path.joinpath(d) for d in reversed(dirnames)]
327345

328346
@abstractmethod
329-
def readlink(self):
347+
def readlink(self: _RP) -> _RP:
330348
"""
331349
Return the path to which the symbolic link points.
332350
"""
333351
raise NotImplementedError
334352

335-
def copy(self, target, **kwargs):
353+
def copy(self, target: _WP, **kwargs: Any) -> _WP:
336354
"""
337355
Recursively copy this file or directory tree to the given destination.
338356
"""
@@ -346,7 +364,7 @@ def copy(self, target, **kwargs):
346364
copy_to_target(self, **kwargs)
347365
return target.joinpath() # Empty join to ensure fresh metadata.
348366

349-
def copy_into(self, target_dir, **kwargs):
367+
def copy_into(self, target_dir: _WP, **kwargs: Any) -> _WP:
350368
"""
351369
Copy this file or directory tree into the given existing directory.
352370
"""
@@ -370,29 +388,29 @@ class _WritablePath(_JoinablePath):
370388
__slots__ = ()
371389

372390
@abstractmethod
373-
def symlink_to(self, target, target_is_directory=False):
391+
def symlink_to(self: _WP, target: _WP, target_is_directory: bool = False) -> None:
374392
"""
375393
Make this path a symlink pointing to the target path.
376394
Note the order of arguments (link, target) is the reverse of os.symlink.
377395
"""
378396
raise NotImplementedError
379397

380398
@abstractmethod
381-
def mkdir(self):
399+
def mkdir(self) -> None:
382400
"""
383401
Create a new directory at this given path.
384402
"""
385403
raise NotImplementedError
386404

387405
@abstractmethod
388-
def __open_wb__(self, buffering=-1):
406+
def __open_wb__(self, buffering: int = -1) -> BinaryIO:
389407
"""
390408
Open the file pointed to by this path for writing in binary mode and
391409
return a file object, like open(mode='wb').
392410
"""
393411
raise NotImplementedError
394412

395-
def write_bytes(self, data):
413+
def write_bytes(self, data: bytes) -> int:
396414
"""
397415
Open the file in bytes mode, write to it, and close the file.
398416
"""
@@ -401,7 +419,13 @@ def write_bytes(self, data):
401419
with magic_open(self, mode='wb') as f:
402420
return f.write(view)
403421

404-
def write_text(self, data, encoding=None, errors=None, newline=None):
422+
def write_text(
423+
self,
424+
data: str,
425+
encoding: Optional[str] = None,
426+
errors: Optional[str] = None,
427+
newline: Optional[str] = None,
428+
) -> int:
405429
"""
406430
Open the file in text mode, write to it, and close the file.
407431
"""
@@ -411,7 +435,7 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
411435
with magic_open(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
412436
return f.write(data)
413437

414-
def _copy_from(self, source, follow_symlinks=True):
438+
def _copy_from(self, source: _ReadablePath, follow_symlinks: bool = True) -> None:
415439
"""
416440
Recursively copy the given path to this path.
417441
"""

0 commit comments

Comments
 (0)