Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/anyio/_core/_fileio.py: 43%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import os
4import pathlib
5import sys
6from collections.abc import (
7 AsyncIterator,
8 Callable,
9 Iterable,
10 Iterator,
11 Sequence,
12)
13from dataclasses import dataclass
14from functools import partial
15from os import PathLike
16from typing import (
17 IO,
18 TYPE_CHECKING,
19 Any,
20 AnyStr,
21 ClassVar,
22 Final,
23 Generic,
24 TypeVar,
25 overload,
26)
28from .. import to_thread
29from ..abc import AsyncResource
30from ._synchronization import CapacityLimiter
31from ._tasks import CancelScope
33if sys.version_info >= (3, 11):
34 from typing import Self
35else:
36 from typing_extensions import Self
38if sys.version_info >= (3, 14):
39 from pathlib.types import PathInfo
41if TYPE_CHECKING:
42 from types import ModuleType
44 from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
45else:
46 ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object
49T = TypeVar("T", bound="Path")
52class AsyncFile(AsyncResource, Generic[AnyStr]):
53 """
54 An asynchronous file object.
56 This class wraps a standard file object and provides async friendly versions of the
57 following blocking methods (where available on the original file object):
59 * read
60 * read1
61 * readline
62 * readlines
63 * readinto
64 * readinto1
65 * write
66 * writelines
67 * truncate
68 * seek
69 * tell
70 * flush
72 All other methods are directly passed through.
74 This class supports the asynchronous context manager protocol which closes the
75 underlying file at the end of the context block.
77 This class also supports asynchronous iteration::
79 async with await open_file(...) as f:
80 async for line in f:
81 print(line)
82 """
84 def __init__(
85 self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None
86 ) -> None:
87 if limiter is not None and not isinstance(limiter, CapacityLimiter):
88 raise TypeError(
89 f"limiter must be a CapacityLimiter or None, not "
90 f"{limiter.__class__.__name__}"
91 )
93 self._fp: Any = fp
94 self._limiter = limiter
96 def __getattr__(self, name: str) -> object:
97 return getattr(self._fp, name)
99 @property
100 def limiter(self) -> CapacityLimiter | None:
101 """The capacity limiter used by this file object, if not the global limiter."""
102 return self._limiter
104 @property
105 def wrapped(self) -> IO[AnyStr]:
106 """The wrapped file object."""
107 return self._fp
109 async def __aiter__(self) -> AsyncIterator[AnyStr]:
110 while True:
111 line = await self.readline()
112 if line:
113 yield line
114 else:
115 break
117 async def aclose(self) -> None:
118 with CancelScope(shield=True):
119 await to_thread.run_sync(self._fp.close, limiter=self._limiter)
121 async def read(self, size: int = -1) -> AnyStr:
122 return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter)
124 async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes:
125 return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter)
127 async def readline(self) -> AnyStr:
128 return await to_thread.run_sync(self._fp.readline, limiter=self._limiter)
130 async def readlines(self) -> list[AnyStr]:
131 return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter)
133 async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
134 return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter)
136 async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
137 return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter)
139 @overload
140 async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ...
142 @overload
143 async def write(self: AsyncFile[str], b: str) -> int: ...
145 async def write(self, b: ReadableBuffer | str) -> int:
146 return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter)
148 @overload
149 async def writelines(
150 self: AsyncFile[bytes], lines: Iterable[ReadableBuffer]
151 ) -> None: ...
153 @overload
154 async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ...
156 async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None:
157 return await to_thread.run_sync(
158 self._fp.writelines, lines, limiter=self._limiter
159 )
161 async def truncate(self, size: int | None = None) -> int:
162 return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter)
164 async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
165 return await to_thread.run_sync(
166 self._fp.seek, offset, whence, limiter=self._limiter
167 )
169 async def tell(self) -> int:
170 return await to_thread.run_sync(self._fp.tell, limiter=self._limiter)
172 async def flush(self) -> None:
173 return await to_thread.run_sync(self._fp.flush, limiter=self._limiter)
176@overload
177async def open_file(
178 file: str | PathLike[str] | int,
179 mode: OpenBinaryMode,
180 buffering: int = ...,
181 encoding: str | None = ...,
182 errors: str | None = ...,
183 newline: str | None = ...,
184 closefd: bool = ...,
185 opener: Callable[[str, int], int] | None = ...,
186 *,
187 limiter: CapacityLimiter | None = ...,
188) -> AsyncFile[bytes]: ...
191@overload
192async def open_file(
193 file: str | PathLike[str] | int,
194 mode: OpenTextMode = ...,
195 buffering: int = ...,
196 encoding: str | None = ...,
197 errors: str | None = ...,
198 newline: str | None = ...,
199 closefd: bool = ...,
200 opener: Callable[[str, int], int] | None = ...,
201 *,
202 limiter: CapacityLimiter | None = ...,
203) -> AsyncFile[str]: ...
206async def open_file(
207 file: str | PathLike[str] | int,
208 mode: str = "r",
209 buffering: int = -1,
210 encoding: str | None = None,
211 errors: str | None = None,
212 newline: str | None = None,
213 closefd: bool = True,
214 opener: Callable[[str, int], int] | None = None,
215 *,
216 limiter: CapacityLimiter | None = None,
217) -> AsyncFile[Any]:
218 """
219 Open a file asynchronously.
221 Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`.
223 :param limiter: an optional capacity limiter to use with the file
224 instead of the default one
225 :return: an asynchronous file object
227 .. versionchanged:: 4.14.0
228 Added the ``limiter`` keyword argument.
230 """
231 fp = await to_thread.run_sync(
232 open,
233 file,
234 mode,
235 buffering,
236 encoding,
237 errors,
238 newline,
239 closefd,
240 opener,
241 limiter=limiter,
242 )
243 return AsyncFile(fp, limiter=limiter)
246def wrap_file(
247 file: IO[AnyStr], *, limiter: CapacityLimiter | None = None
248) -> AsyncFile[AnyStr]:
249 """
250 Wrap an existing file as an asynchronous file.
252 :param file: an existing file-like object
253 :param limiter: an optional capacity limiter to use with the file
254 instead of the default one
255 :return: an asynchronous file object
257 .. versionchanged:: 4.14.0
258 Added the ``limiter`` keyword argument.
260 """
261 return AsyncFile(file, limiter=limiter)
264@dataclass(eq=False)
265class _PathIterator(AsyncIterator[T]):
266 iterator: Iterator[PathLike[str]]
267 limiter: CapacityLimiter | None
268 # This was added to ensure that iterating over a subclass of Path yields instances
269 # of that subclass rather than the base Path class.
270 path_cls: type[T]
272 async def __anext__(self) -> T:
273 nextval = await to_thread.run_sync(
274 next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter
275 )
276 if nextval is None:
277 raise StopAsyncIteration from None
279 return self.path_cls(nextval, limiter=self.limiter)
282class Path:
283 """
284 An asynchronous version of :class:`pathlib.Path`.
286 This class cannot be substituted for :class:`pathlib.Path` or
287 :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike`
288 interface.
290 It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for
291 the deprecated :meth:`~pathlib.Path.link_to` method.
293 Some methods may be unavailable or have limited functionality, based on the Python
294 version:
296 * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later)
297 * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later)
298 * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later)
299 * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later)
300 * :attr:`~pathlib.Path.info` (available on Python 3.14 or later)
301 * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later)
302 * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only
303 available on Python 3.13 or later)
304 * :meth:`~pathlib.Path.move` (available on Python 3.14 or later)
305 * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later)
306 * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available
307 on Python 3.12 or later)
308 * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later)
310 Any methods that do disk I/O need to be awaited on. These methods are:
312 * :meth:`~pathlib.Path.absolute`
313 * :meth:`~pathlib.Path.chmod`
314 * :meth:`~pathlib.Path.cwd`
315 * :meth:`~pathlib.Path.exists`
316 * :meth:`~pathlib.Path.expanduser`
317 * :meth:`~pathlib.Path.group`
318 * :meth:`~pathlib.Path.hardlink_to`
319 * :meth:`~pathlib.Path.home`
320 * :meth:`~pathlib.Path.is_block_device`
321 * :meth:`~pathlib.Path.is_char_device`
322 * :meth:`~pathlib.Path.is_dir`
323 * :meth:`~pathlib.Path.is_fifo`
324 * :meth:`~pathlib.Path.is_file`
325 * :meth:`~pathlib.Path.is_junction`
326 * :meth:`~pathlib.Path.is_mount`
327 * :meth:`~pathlib.Path.is_socket`
328 * :meth:`~pathlib.Path.is_symlink`
329 * :meth:`~pathlib.Path.lchmod`
330 * :meth:`~pathlib.Path.lstat`
331 * :meth:`~pathlib.Path.mkdir`
332 * :meth:`~pathlib.Path.open`
333 * :meth:`~pathlib.Path.owner`
334 * :meth:`~pathlib.Path.read_bytes`
335 * :meth:`~pathlib.Path.read_text`
336 * :meth:`~pathlib.Path.readlink`
337 * :meth:`~pathlib.Path.rename`
338 * :meth:`~pathlib.Path.replace`
339 * :meth:`~pathlib.Path.resolve`
340 * :meth:`~pathlib.Path.rmdir`
341 * :meth:`~pathlib.Path.samefile`
342 * :meth:`~pathlib.Path.stat`
343 * :meth:`~pathlib.Path.symlink_to`
344 * :meth:`~pathlib.Path.touch`
345 * :meth:`~pathlib.Path.unlink`
346 * :meth:`~pathlib.Path.walk`
347 * :meth:`~pathlib.Path.write_bytes`
348 * :meth:`~pathlib.Path.write_text`
350 Additionally, the following methods return an async iterator yielding
351 :class:`~.Path` objects:
353 * :meth:`~pathlib.Path.glob`
354 * :meth:`~pathlib.Path.iterdir`
355 * :meth:`~pathlib.Path.rglob`
357 .. versionchanged:: 4.14.0
358 Added the ``limiter`` keyword argument.
359 """
361 __slots__ = "__weakref__", "_limiter", "_path"
363 __weakref__: Any
365 def __init__(
366 self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None
367 ) -> None:
368 if limiter is not None and not isinstance(limiter, CapacityLimiter):
369 raise TypeError(
370 f"limiter must be a CapacityLimiter or None, not "
371 f"{limiter.__class__.__name__}"
372 )
374 self._path: Final[pathlib.Path] = pathlib.Path(*args)
375 self._limiter = limiter
377 def __fspath__(self) -> str:
378 return self._path.__fspath__()
380 if sys.version_info >= (3, 15):
382 def __vfspath__(self) -> str:
383 return self._path.__vfspath__()
385 def __str__(self) -> str:
386 return self._path.__str__()
388 def __repr__(self) -> str:
389 return f"{self.__class__.__name__}({self.as_posix()!r})"
391 def __bytes__(self) -> bytes:
392 return self._path.__bytes__()
394 def __hash__(self) -> int:
395 return self._path.__hash__()
397 def __eq__(self, other: object) -> bool:
398 target = other._path if isinstance(other, Path) else other
399 return self._path.__eq__(target)
401 def __lt__(self, other: pathlib.PurePath | Path) -> bool:
402 target = other._path if isinstance(other, Path) else other
403 return self._path.__lt__(target)
405 def __le__(self, other: pathlib.PurePath | Path) -> bool:
406 target = other._path if isinstance(other, Path) else other
407 return self._path.__le__(target)
409 def __gt__(self, other: pathlib.PurePath | Path) -> bool:
410 target = other._path if isinstance(other, Path) else other
411 return self._path.__gt__(target)
413 def __ge__(self, other: pathlib.PurePath | Path) -> bool:
414 target = other._path if isinstance(other, Path) else other
415 return self._path.__ge__(target)
417 def __truediv__(self, other: str | PathLike[str]) -> Self:
418 return type(self)(self._path / other, limiter=self._limiter)
420 def __rtruediv__(self, other: str | PathLike[str]) -> Self:
421 return type(self)(other, limiter=self._limiter) / self
423 @property
424 def limiter(self) -> CapacityLimiter | None:
425 """The capacity limiter used by this path, if not the global limiter."""
426 return self._limiter
428 @property
429 def parts(self) -> tuple[str, ...]:
430 return self._path.parts
432 @property
433 def drive(self) -> str:
434 return self._path.drive
436 @property
437 def root(self) -> str:
438 return self._path.root
440 @property
441 def anchor(self) -> str:
442 return self._path.anchor
444 @property
445 def parents(self) -> Sequence[Self]:
446 return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents)
448 @property
449 def parent(self) -> Self:
450 return type(self)(self._path.parent, limiter=self._limiter)
452 @property
453 def name(self) -> str:
454 return self._path.name
456 @property
457 def suffix(self) -> str:
458 return self._path.suffix
460 @property
461 def suffixes(self) -> list[str]:
462 return self._path.suffixes
464 @property
465 def stem(self) -> str:
466 return self._path.stem
468 async def absolute(self) -> Self:
469 path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter)
470 return type(self)(path, limiter=self._limiter)
472 def as_posix(self) -> str:
473 return self._path.as_posix()
475 def as_uri(self) -> str:
476 return self._path.as_uri()
478 if sys.version_info >= (3, 13):
479 parser: ClassVar[ModuleType] = pathlib.Path.parser
481 @classmethod
482 def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self:
483 return cls(pathlib.Path.from_uri(uri), limiter=limiter)
485 def full_match(
486 self, path_pattern: str, *, case_sensitive: bool | None = None
487 ) -> bool:
488 return self._path.full_match(path_pattern, case_sensitive=case_sensitive)
490 def match(
491 self, path_pattern: str, *, case_sensitive: bool | None = None
492 ) -> bool:
493 return self._path.match(path_pattern, case_sensitive=case_sensitive)
494 else:
496 def match(self, path_pattern: str) -> bool:
497 return self._path.match(path_pattern)
499 if sys.version_info >= (3, 14):
501 @property
502 def info(self) -> PathInfo:
503 return self._path.info
505 async def copy(
506 self,
507 target: str | os.PathLike[str],
508 *,
509 follow_symlinks: bool = True,
510 preserve_metadata: bool = False,
511 ) -> Self:
512 func = partial(
513 self._path.copy,
514 follow_symlinks=follow_symlinks,
515 preserve_metadata=preserve_metadata,
516 )
517 return type(self)(
518 await to_thread.run_sync(
519 func, pathlib.Path(target), limiter=self._limiter
520 ),
521 limiter=self._limiter,
522 )
524 async def copy_into(
525 self,
526 target_dir: str | os.PathLike[str],
527 *,
528 follow_symlinks: bool = True,
529 preserve_metadata: bool = False,
530 ) -> Self:
531 func = partial(
532 self._path.copy_into,
533 follow_symlinks=follow_symlinks,
534 preserve_metadata=preserve_metadata,
535 )
536 return type(self)(
537 await to_thread.run_sync(
538 func, pathlib.Path(target_dir), limiter=self._limiter
539 ),
540 limiter=self._limiter,
541 )
543 async def move(self, target: str | os.PathLike[str]) -> Self:
544 # Upstream does not handle anyio.Path properly as a PathLike
545 target = pathlib.Path(target)
546 return type(self)(
547 await to_thread.run_sync(
548 self._path.move, target, limiter=self._limiter
549 ),
550 limiter=self._limiter,
551 )
553 async def move_into(
554 self,
555 target_dir: str | os.PathLike[str],
556 ) -> Self:
557 return type(self)(
558 await to_thread.run_sync(
559 self._path.move_into, target_dir, limiter=self._limiter
560 ),
561 limiter=self._limiter,
562 )
564 def is_relative_to(self, other: str | PathLike[str]) -> bool:
565 try:
566 self.relative_to(other)
567 return True
568 except ValueError:
569 return False
571 async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
572 func = partial(os.chmod, follow_symlinks=follow_symlinks)
573 return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter)
575 @classmethod
576 async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self:
577 path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter)
578 return cls(path, limiter=limiter)
580 if sys.version_info >= (3, 12):
582 async def exists(self, *, follow_symlinks: bool = True) -> bool:
583 return await to_thread.run_sync(
584 partial(self._path.exists, follow_symlinks=follow_symlinks),
585 abandon_on_cancel=True,
586 limiter=self._limiter,
587 )
589 else:
591 async def exists(self) -> bool:
592 return await to_thread.run_sync(
593 self._path.exists, abandon_on_cancel=True, limiter=self._limiter
594 )
596 async def expanduser(self) -> Self:
597 return type(self)(
598 await to_thread.run_sync(
599 self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter
600 ),
601 limiter=self._limiter,
602 )
604 if sys.version_info < (3, 12):
605 # Python 3.11 and earlier
606 def glob(self, pattern: str) -> AsyncIterator[Self]:
607 gen = self._path.glob(pattern)
608 return _PathIterator(gen, self._limiter, type(self))
609 elif (3, 12) <= sys.version_info < (3, 13):
610 # changed in Python 3.12:
611 # - The case_sensitive parameter was added.
612 def glob(
613 self,
614 pattern: str,
615 *,
616 case_sensitive: bool | None = None,
617 ) -> AsyncIterator[Self]:
618 gen = self._path.glob(pattern, case_sensitive=case_sensitive)
619 return _PathIterator(gen, self._limiter, type(self))
620 elif sys.version_info >= (3, 13):
621 # Changed in Python 3.13:
622 # - The recurse_symlinks parameter was added.
623 # - The pattern parameter accepts a path-like object.
624 def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
625 self,
626 pattern: str | PathLike[str],
627 *,
628 case_sensitive: bool | None = None,
629 recurse_symlinks: bool = False,
630 ) -> AsyncIterator[Self]:
631 gen = self._path.glob(
632 pattern, # type: ignore[arg-type]
633 case_sensitive=case_sensitive,
634 recurse_symlinks=recurse_symlinks,
635 )
636 return _PathIterator(gen, self._limiter, type(self))
638 if sys.version_info >= (3, 13):
640 async def group(self, *, follow_symlinks: bool = True) -> str:
641 return await to_thread.run_sync(
642 partial(self._path.group, follow_symlinks=follow_symlinks),
643 abandon_on_cancel=True,
644 limiter=self._limiter,
645 )
647 else:
649 async def group(self) -> str:
650 return await to_thread.run_sync(
651 self._path.group, abandon_on_cancel=True, limiter=self._limiter
652 )
654 async def hardlink_to(
655 self, target: str | bytes | PathLike[str] | PathLike[bytes]
656 ) -> None:
657 if isinstance(target, Path):
658 target = target._path
660 await to_thread.run_sync(os.link, target, self, limiter=self._limiter)
662 @classmethod
663 async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self:
664 home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter)
665 return cls(home_path, limiter=limiter)
667 def is_absolute(self) -> bool:
668 return self._path.is_absolute()
670 async def is_block_device(self) -> bool:
671 return await to_thread.run_sync(
672 self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter
673 )
675 async def is_char_device(self) -> bool:
676 return await to_thread.run_sync(
677 self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter
678 )
680 if sys.version_info >= (3, 13):
682 async def is_dir(self, *, follow_symlinks: bool = True) -> bool:
683 return await to_thread.run_sync(
684 partial(self._path.is_dir, follow_symlinks=follow_symlinks),
685 abandon_on_cancel=True,
686 limiter=self._limiter,
687 )
689 else:
691 async def is_dir(self) -> bool:
692 return await to_thread.run_sync(
693 self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter
694 )
696 async def is_fifo(self) -> bool:
697 return await to_thread.run_sync(
698 self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter
699 )
701 if sys.version_info >= (3, 13):
703 async def is_file(self, *, follow_symlinks: bool = True) -> bool:
704 return await to_thread.run_sync(
705 partial(self._path.is_file, follow_symlinks=follow_symlinks),
706 abandon_on_cancel=True,
707 limiter=self._limiter,
708 )
710 else:
712 async def is_file(self) -> bool:
713 return await to_thread.run_sync(
714 self._path.is_file, abandon_on_cancel=True, limiter=self._limiter
715 )
717 if sys.version_info >= (3, 12):
719 async def is_junction(self) -> bool:
720 return await to_thread.run_sync(
721 self._path.is_junction, limiter=self._limiter
722 )
724 async def is_mount(self) -> bool:
725 return await to_thread.run_sync(
726 os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter
727 )
729 if sys.version_info < (3, 15):
731 def is_reserved(self) -> bool:
732 return self._path.is_reserved()
734 async def is_socket(self) -> bool:
735 return await to_thread.run_sync(
736 self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter
737 )
739 async def is_symlink(self) -> bool:
740 return await to_thread.run_sync(
741 self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter
742 )
744 async def iterdir(self) -> AsyncIterator[Self]:
745 gen = (
746 self._path.iterdir()
747 if sys.version_info < (3, 13)
748 else await to_thread.run_sync(
749 self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter
750 )
751 )
752 async for path in _PathIterator(gen, self._limiter, type(self)):
753 yield path
755 def joinpath(self, *args: str | PathLike[str]) -> Self:
756 return type(self)(self._path.joinpath(*args), limiter=self._limiter)
758 async def lchmod(self, mode: int) -> None:
759 await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter)
761 async def lstat(self) -> os.stat_result:
762 return await to_thread.run_sync(
763 self._path.lstat, abandon_on_cancel=True, limiter=self._limiter
764 )
766 async def mkdir(
767 self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
768 ) -> None:
769 await to_thread.run_sync(
770 self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter
771 )
773 @overload
774 async def open(
775 self,
776 mode: OpenBinaryMode,
777 buffering: int = ...,
778 encoding: str | None = ...,
779 errors: str | None = ...,
780 newline: str | None = ...,
781 ) -> AsyncFile[bytes]: ...
783 @overload
784 async def open(
785 self,
786 mode: OpenTextMode = ...,
787 buffering: int = ...,
788 encoding: str | None = ...,
789 errors: str | None = ...,
790 newline: str | None = ...,
791 ) -> AsyncFile[str]: ...
793 async def open(
794 self,
795 mode: str = "r",
796 buffering: int = -1,
797 encoding: str | None = None,
798 errors: str | None = None,
799 newline: str | None = None,
800 ) -> AsyncFile[Any]:
801 fp = await to_thread.run_sync(
802 self._path.open,
803 mode,
804 buffering,
805 encoding,
806 errors,
807 newline,
808 limiter=self._limiter,
809 )
810 return AsyncFile(fp, limiter=self._limiter)
812 if sys.version_info >= (3, 13):
814 async def owner(self, *, follow_symlinks: bool = True) -> str:
815 return await to_thread.run_sync(
816 partial(self._path.owner, follow_symlinks=follow_symlinks),
817 abandon_on_cancel=True,
818 limiter=self._limiter,
819 )
821 else:
823 async def owner(self) -> str:
824 return await to_thread.run_sync(
825 self._path.owner, abandon_on_cancel=True, limiter=self._limiter
826 )
828 async def read_bytes(self) -> bytes:
829 return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter)
831 if sys.version_info >= (3, 13):
833 async def read_text(
834 self,
835 encoding: str | None = None,
836 errors: str | None = None,
837 newline: str | None = None,
838 ) -> str:
839 return await to_thread.run_sync(
840 self._path.read_text,
841 encoding,
842 errors,
843 newline,
844 limiter=self._limiter,
845 )
847 else:
849 async def read_text(
850 self, encoding: str | None = None, errors: str | None = None
851 ) -> str:
852 return await to_thread.run_sync(
853 self._path.read_text, encoding, errors, limiter=self._limiter
854 )
856 if sys.version_info >= (3, 12):
858 def relative_to(
859 self, *other: str | PathLike[str], walk_up: bool = False
860 ) -> Self:
861 # relative_to() should work with any PathLike but it doesn't
862 others = [pathlib.Path(other) for other in other]
863 return type(self)(
864 self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter
865 )
867 else:
869 def relative_to(self, *other: str | PathLike[str]) -> Self:
870 return type(self)(self._path.relative_to(*other), limiter=self._limiter)
872 async def readlink(self) -> Self:
873 target = await to_thread.run_sync(
874 os.readlink, self._path, limiter=self._limiter
875 )
876 return type(self)(target, limiter=self._limiter)
878 async def rename(self, target: str | pathlib.PurePath | Path) -> Self:
879 if isinstance(target, Path):
880 target = target._path
882 await to_thread.run_sync(self._path.rename, target, limiter=self._limiter)
883 return type(self)(target, limiter=self._limiter)
885 async def replace(self, target: str | pathlib.PurePath | Path) -> Self:
886 if isinstance(target, Path):
887 target = target._path
889 await to_thread.run_sync(self._path.replace, target, limiter=self._limiter)
890 return type(self)(target, limiter=self._limiter)
892 async def resolve(self, strict: bool = False) -> Self:
893 func = partial(self._path.resolve, strict=strict)
894 return type(self)(
895 await to_thread.run_sync(
896 func, abandon_on_cancel=True, limiter=self._limiter
897 ),
898 limiter=self._limiter,
899 )
901 if sys.version_info < (3, 12):
902 # Pre Python 3.12
903 def rglob(self, pattern: str) -> AsyncIterator[Self]:
904 gen = self._path.rglob(pattern)
905 return _PathIterator(gen, self._limiter, type(self))
906 elif (3, 12) <= sys.version_info < (3, 13):
907 # Changed in Python 3.12:
908 # - The case_sensitive parameter was added.
909 def rglob(
910 self, pattern: str, *, case_sensitive: bool | None = None
911 ) -> AsyncIterator[Self]:
912 gen = self._path.rglob(pattern, case_sensitive=case_sensitive)
913 return _PathIterator(gen, self._limiter, type(self))
914 elif sys.version_info >= (3, 13):
915 # Changed in Python 3.13:
916 # - The recurse_symlinks parameter was added.
917 # - The pattern parameter accepts a path-like object.
918 def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
919 self,
920 pattern: str | PathLike[str],
921 *,
922 case_sensitive: bool | None = None,
923 recurse_symlinks: bool = False,
924 ) -> AsyncIterator[Self]:
925 gen = self._path.rglob(
926 pattern, # type: ignore[arg-type]
927 case_sensitive=case_sensitive,
928 recurse_symlinks=recurse_symlinks,
929 )
930 return _PathIterator(gen, self._limiter, type(self))
932 async def rmdir(self) -> None:
933 await to_thread.run_sync(self._path.rmdir, limiter=self._limiter)
935 async def samefile(self, other_path: str | PathLike[str]) -> bool:
936 if isinstance(other_path, Path):
937 other_path = other_path._path
939 return await to_thread.run_sync(
940 self._path.samefile,
941 other_path,
942 abandon_on_cancel=True,
943 limiter=self._limiter,
944 )
946 async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
947 func = partial(os.stat, follow_symlinks=follow_symlinks)
948 return await to_thread.run_sync(
949 func, self._path, abandon_on_cancel=True, limiter=self._limiter
950 )
952 async def symlink_to(
953 self,
954 target: str | bytes | PathLike[str] | PathLike[bytes],
955 target_is_directory: bool = False,
956 ) -> None:
957 if isinstance(target, Path):
958 target = target._path
960 await to_thread.run_sync(
961 self._path.symlink_to, target, target_is_directory, limiter=self._limiter
962 )
964 async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
965 await to_thread.run_sync(
966 self._path.touch, mode, exist_ok, limiter=self._limiter
967 )
969 async def unlink(self, missing_ok: bool = False) -> None:
970 try:
971 await to_thread.run_sync(self._path.unlink, limiter=self._limiter)
972 except FileNotFoundError:
973 if not missing_ok:
974 raise
976 if sys.version_info >= (3, 12):
978 async def walk(
979 self,
980 top_down: bool = True,
981 on_error: Callable[[OSError], object] | None = None,
982 follow_symlinks: bool = False,
983 ) -> AsyncIterator[tuple[Self, list[str], list[str]]]:
984 def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None:
985 try:
986 return next(gen)
987 except StopIteration:
988 return None
990 gen = self._path.walk(top_down, on_error, follow_symlinks)
991 while True:
992 value = await to_thread.run_sync(get_next_value, limiter=self._limiter)
993 if value is None:
994 return
996 root, dirs, paths = value
997 yield type(self)(root, limiter=self._limiter), dirs, paths
999 def with_name(self, name: str) -> Self:
1000 return type(self)(self._path.with_name(name), limiter=self._limiter)
1002 if sys.version_info < (3, 13):
1003 # Backport pathlib's Python>=3.13 behavior for empty stems on paths with non-empty suffixes.
1004 # See: https://github.com/python/cpython/pull/114612
1005 def with_stem(self, stem: str) -> Self:
1006 suffix = self._path.suffix
1007 if not suffix:
1008 return self.with_name(stem)
1009 elif not stem:
1010 # If the suffix is non-empty, we can't make the stem empty.
1011 raise ValueError(f"{self!r} has a non-empty suffix")
1012 else:
1013 return self.with_name(stem + suffix)
1014 else:
1016 def with_stem(self, stem: str) -> Self:
1017 return type(self)(self._path.with_stem(stem), limiter=self._limiter)
1019 def with_suffix(self, suffix: str) -> Self:
1020 return type(self)(self._path.with_suffix(suffix), limiter=self._limiter)
1022 def with_segments(self, *pathsegments: str | PathLike[str]) -> Self:
1023 return type(self)(*pathsegments, limiter=self._limiter)
1025 async def write_bytes(self, data: ReadableBuffer) -> int:
1026 return await to_thread.run_sync(
1027 self._path.write_bytes, data, limiter=self._limiter
1028 )
1030 async def write_text(
1031 self,
1032 data: str,
1033 encoding: str | None = None,
1034 errors: str | None = None,
1035 newline: str | None = None,
1036 ) -> int:
1037 return await to_thread.run_sync(
1038 self._path.write_text,
1039 data,
1040 encoding,
1041 errors,
1042 newline,
1043 limiter=self._limiter,
1044 )
1047PathLike.register(Path)