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
32if sys.version_info >= (3, 11):
33 from typing import Self
34else:
35 from typing_extensions import Self
37if sys.version_info >= (3, 14):
38 from pathlib.types import PathInfo
40if TYPE_CHECKING:
41 from types import ModuleType
43 from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
44else:
45 ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object
48T = TypeVar("T", bound="Path")
51class AsyncFile(AsyncResource, Generic[AnyStr]):
52 """
53 An asynchronous file object.
55 This class wraps a standard file object and provides async friendly versions of the
56 following blocking methods (where available on the original file object):
58 * read
59 * read1
60 * readline
61 * readlines
62 * readinto
63 * readinto1
64 * write
65 * writelines
66 * truncate
67 * seek
68 * tell
69 * flush
71 All other methods are directly passed through.
73 This class supports the asynchronous context manager protocol which closes the
74 underlying file at the end of the context block.
76 This class also supports asynchronous iteration::
78 async with await open_file(...) as f:
79 async for line in f:
80 print(line)
81 """
83 def __init__(
84 self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None
85 ) -> None:
86 if limiter is not None and not isinstance(limiter, CapacityLimiter):
87 raise TypeError(
88 f"limiter must be a CapacityLimiter or None, not "
89 f"{limiter.__class__.__name__}"
90 )
92 self._fp: Any = fp
93 self._limiter = limiter
95 def __getattr__(self, name: str) -> object:
96 return getattr(self._fp, name)
98 @property
99 def limiter(self) -> CapacityLimiter | None:
100 """The capacity limiter used by this file object, if not the global limiter."""
101 return self._limiter
103 @property
104 def wrapped(self) -> IO[AnyStr]:
105 """The wrapped file object."""
106 return self._fp
108 async def __aiter__(self) -> AsyncIterator[AnyStr]:
109 while True:
110 line = await self.readline()
111 if line:
112 yield line
113 else:
114 break
116 async def aclose(self) -> None:
117 return await to_thread.run_sync(self._fp.close, limiter=self._limiter)
119 async def read(self, size: int = -1) -> AnyStr:
120 return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter)
122 async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes:
123 return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter)
125 async def readline(self) -> AnyStr:
126 return await to_thread.run_sync(self._fp.readline, limiter=self._limiter)
128 async def readlines(self) -> list[AnyStr]:
129 return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter)
131 async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
132 return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter)
134 async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
135 return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter)
137 @overload
138 async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ...
140 @overload
141 async def write(self: AsyncFile[str], b: str) -> int: ...
143 async def write(self, b: ReadableBuffer | str) -> int:
144 return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter)
146 @overload
147 async def writelines(
148 self: AsyncFile[bytes], lines: Iterable[ReadableBuffer]
149 ) -> None: ...
151 @overload
152 async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ...
154 async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None:
155 return await to_thread.run_sync(
156 self._fp.writelines, lines, limiter=self._limiter
157 )
159 async def truncate(self, size: int | None = None) -> int:
160 return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter)
162 async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
163 return await to_thread.run_sync(
164 self._fp.seek, offset, whence, limiter=self._limiter
165 )
167 async def tell(self) -> int:
168 return await to_thread.run_sync(self._fp.tell, limiter=self._limiter)
170 async def flush(self) -> None:
171 return await to_thread.run_sync(self._fp.flush, limiter=self._limiter)
174@overload
175async def open_file(
176 file: str | PathLike[str] | int,
177 mode: OpenBinaryMode,
178 buffering: int = ...,
179 encoding: str | None = ...,
180 errors: str | None = ...,
181 newline: str | None = ...,
182 closefd: bool = ...,
183 opener: Callable[[str, int], int] | None = ...,
184 *,
185 limiter: CapacityLimiter | None = ...,
186) -> AsyncFile[bytes]: ...
189@overload
190async def open_file(
191 file: str | PathLike[str] | int,
192 mode: OpenTextMode = ...,
193 buffering: int = ...,
194 encoding: str | None = ...,
195 errors: str | None = ...,
196 newline: str | None = ...,
197 closefd: bool = ...,
198 opener: Callable[[str, int], int] | None = ...,
199 *,
200 limiter: CapacityLimiter | None = ...,
201) -> AsyncFile[str]: ...
204async def open_file(
205 file: str | PathLike[str] | int,
206 mode: str = "r",
207 buffering: int = -1,
208 encoding: str | None = None,
209 errors: str | None = None,
210 newline: str | None = None,
211 closefd: bool = True,
212 opener: Callable[[str, int], int] | None = None,
213 *,
214 limiter: CapacityLimiter | None = None,
215) -> AsyncFile[Any]:
216 """
217 Open a file asynchronously.
219 Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`.
221 :param limiter: an optional capacity limiter to use with the file
222 instead of the default one
223 :return: an asynchronous file object
225 .. versionchanged:: 4.14.0
226 Added the ``limiter`` keyword argument.
228 """
229 fp = await to_thread.run_sync(
230 open,
231 file,
232 mode,
233 buffering,
234 encoding,
235 errors,
236 newline,
237 closefd,
238 opener,
239 limiter=limiter,
240 )
241 return AsyncFile(fp, limiter=limiter)
244def wrap_file(
245 file: IO[AnyStr], *, limiter: CapacityLimiter | None = None
246) -> AsyncFile[AnyStr]:
247 """
248 Wrap an existing file as an asynchronous file.
250 :param file: an existing file-like object
251 :param limiter: an optional capacity limiter to use with the file
252 instead of the default one
253 :return: an asynchronous file object
255 .. versionchanged:: 4.14.0
256 Added the ``limiter`` keyword argument.
258 """
259 return AsyncFile(file, limiter=limiter)
262@dataclass(eq=False)
263class _PathIterator(AsyncIterator[T]):
264 iterator: Iterator[PathLike[str]]
265 limiter: CapacityLimiter | None
266 # This was added to ensure that iterating over a subclass of Path yields instances
267 # of that subclass rather than the base Path class.
268 path_cls: type[T]
270 async def __anext__(self) -> T:
271 nextval = await to_thread.run_sync(
272 next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter
273 )
274 if nextval is None:
275 raise StopAsyncIteration from None
277 return self.path_cls(nextval, limiter=self.limiter)
280class Path:
281 """
282 An asynchronous version of :class:`pathlib.Path`.
284 This class cannot be substituted for :class:`pathlib.Path` or
285 :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike`
286 interface.
288 It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for
289 the deprecated :meth:`~pathlib.Path.link_to` method.
291 Some methods may be unavailable or have limited functionality, based on the Python
292 version:
294 * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later)
295 * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later)
296 * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later)
297 * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later)
298 * :attr:`~pathlib.Path.info` (available on Python 3.14 or later)
299 * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later)
300 * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only
301 available on Python 3.13 or later)
302 * :meth:`~pathlib.Path.move` (available on Python 3.14 or later)
303 * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later)
304 * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available
305 on Python 3.12 or later)
306 * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later)
308 Any methods that do disk I/O need to be awaited on. These methods are:
310 * :meth:`~pathlib.Path.absolute`
311 * :meth:`~pathlib.Path.chmod`
312 * :meth:`~pathlib.Path.cwd`
313 * :meth:`~pathlib.Path.exists`
314 * :meth:`~pathlib.Path.expanduser`
315 * :meth:`~pathlib.Path.group`
316 * :meth:`~pathlib.Path.hardlink_to`
317 * :meth:`~pathlib.Path.home`
318 * :meth:`~pathlib.Path.is_block_device`
319 * :meth:`~pathlib.Path.is_char_device`
320 * :meth:`~pathlib.Path.is_dir`
321 * :meth:`~pathlib.Path.is_fifo`
322 * :meth:`~pathlib.Path.is_file`
323 * :meth:`~pathlib.Path.is_junction`
324 * :meth:`~pathlib.Path.is_mount`
325 * :meth:`~pathlib.Path.is_socket`
326 * :meth:`~pathlib.Path.is_symlink`
327 * :meth:`~pathlib.Path.lchmod`
328 * :meth:`~pathlib.Path.lstat`
329 * :meth:`~pathlib.Path.mkdir`
330 * :meth:`~pathlib.Path.open`
331 * :meth:`~pathlib.Path.owner`
332 * :meth:`~pathlib.Path.read_bytes`
333 * :meth:`~pathlib.Path.read_text`
334 * :meth:`~pathlib.Path.readlink`
335 * :meth:`~pathlib.Path.rename`
336 * :meth:`~pathlib.Path.replace`
337 * :meth:`~pathlib.Path.resolve`
338 * :meth:`~pathlib.Path.rmdir`
339 * :meth:`~pathlib.Path.samefile`
340 * :meth:`~pathlib.Path.stat`
341 * :meth:`~pathlib.Path.symlink_to`
342 * :meth:`~pathlib.Path.touch`
343 * :meth:`~pathlib.Path.unlink`
344 * :meth:`~pathlib.Path.walk`
345 * :meth:`~pathlib.Path.write_bytes`
346 * :meth:`~pathlib.Path.write_text`
348 Additionally, the following methods return an async iterator yielding
349 :class:`~.Path` objects:
351 * :meth:`~pathlib.Path.glob`
352 * :meth:`~pathlib.Path.iterdir`
353 * :meth:`~pathlib.Path.rglob`
355 .. versionchanged:: 4.14.0
356 Added the ``limiter`` keyword argument.
357 """
359 __slots__ = "__weakref__", "_limiter", "_path"
361 __weakref__: Any
363 def __init__(
364 self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None
365 ) -> None:
366 if limiter is not None and not isinstance(limiter, CapacityLimiter):
367 raise TypeError(
368 f"limiter must be a CapacityLimiter or None, not "
369 f"{limiter.__class__.__name__}"
370 )
372 self._path: Final[pathlib.Path] = pathlib.Path(*args)
373 self._limiter = limiter
375 def __fspath__(self) -> str:
376 return self._path.__fspath__()
378 if sys.version_info >= (3, 15):
380 def __vfspath__(self) -> str:
381 return self._path.__vfspath__()
383 def __str__(self) -> str:
384 return self._path.__str__()
386 def __repr__(self) -> str:
387 return f"{self.__class__.__name__}({self.as_posix()!r})"
389 def __bytes__(self) -> bytes:
390 return self._path.__bytes__()
392 def __hash__(self) -> int:
393 return self._path.__hash__()
395 def __eq__(self, other: object) -> bool:
396 target = other._path if isinstance(other, Path) else other
397 return self._path.__eq__(target)
399 def __lt__(self, other: pathlib.PurePath | Path) -> bool:
400 target = other._path if isinstance(other, Path) else other
401 return self._path.__lt__(target)
403 def __le__(self, other: pathlib.PurePath | Path) -> bool:
404 target = other._path if isinstance(other, Path) else other
405 return self._path.__le__(target)
407 def __gt__(self, other: pathlib.PurePath | Path) -> bool:
408 target = other._path if isinstance(other, Path) else other
409 return self._path.__gt__(target)
411 def __ge__(self, other: pathlib.PurePath | Path) -> bool:
412 target = other._path if isinstance(other, Path) else other
413 return self._path.__ge__(target)
415 def __truediv__(self, other: str | PathLike[str]) -> Self:
416 return type(self)(self._path / other, limiter=self._limiter)
418 def __rtruediv__(self, other: str | PathLike[str]) -> Self:
419 return type(self)(other, limiter=self._limiter) / self
421 @property
422 def limiter(self) -> CapacityLimiter | None:
423 """The capacity limiter used by this path, if not the global limiter."""
424 return self._limiter
426 @property
427 def parts(self) -> tuple[str, ...]:
428 return self._path.parts
430 @property
431 def drive(self) -> str:
432 return self._path.drive
434 @property
435 def root(self) -> str:
436 return self._path.root
438 @property
439 def anchor(self) -> str:
440 return self._path.anchor
442 @property
443 def parents(self) -> Sequence[Self]:
444 return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents)
446 @property
447 def parent(self) -> Self:
448 return type(self)(self._path.parent, limiter=self._limiter)
450 @property
451 def name(self) -> str:
452 return self._path.name
454 @property
455 def suffix(self) -> str:
456 return self._path.suffix
458 @property
459 def suffixes(self) -> list[str]:
460 return self._path.suffixes
462 @property
463 def stem(self) -> str:
464 return self._path.stem
466 async def absolute(self) -> Self:
467 path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter)
468 return type(self)(path, limiter=self._limiter)
470 def as_posix(self) -> str:
471 return self._path.as_posix()
473 def as_uri(self) -> str:
474 return self._path.as_uri()
476 if sys.version_info >= (3, 13):
477 parser: ClassVar[ModuleType] = pathlib.Path.parser
479 @classmethod
480 def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self:
481 return cls(pathlib.Path.from_uri(uri), limiter=limiter)
483 def full_match(
484 self, path_pattern: str, *, case_sensitive: bool | None = None
485 ) -> bool:
486 return self._path.full_match(path_pattern, case_sensitive=case_sensitive)
488 def match(
489 self, path_pattern: str, *, case_sensitive: bool | None = None
490 ) -> bool:
491 return self._path.match(path_pattern, case_sensitive=case_sensitive)
492 else:
494 def match(self, path_pattern: str) -> bool:
495 return self._path.match(path_pattern)
497 if sys.version_info >= (3, 14):
499 @property
500 def info(self) -> PathInfo:
501 return self._path.info
503 async def copy(
504 self,
505 target: str | os.PathLike[str],
506 *,
507 follow_symlinks: bool = True,
508 preserve_metadata: bool = False,
509 ) -> Self:
510 func = partial(
511 self._path.copy,
512 follow_symlinks=follow_symlinks,
513 preserve_metadata=preserve_metadata,
514 )
515 return type(self)(
516 await to_thread.run_sync(
517 func, pathlib.Path(target), limiter=self._limiter
518 ),
519 limiter=self._limiter,
520 )
522 async def copy_into(
523 self,
524 target_dir: str | os.PathLike[str],
525 *,
526 follow_symlinks: bool = True,
527 preserve_metadata: bool = False,
528 ) -> Self:
529 func = partial(
530 self._path.copy_into,
531 follow_symlinks=follow_symlinks,
532 preserve_metadata=preserve_metadata,
533 )
534 return type(self)(
535 await to_thread.run_sync(
536 func, pathlib.Path(target_dir), limiter=self._limiter
537 ),
538 limiter=self._limiter,
539 )
541 async def move(self, target: str | os.PathLike[str]) -> Self:
542 # Upstream does not handle anyio.Path properly as a PathLike
543 target = pathlib.Path(target)
544 return type(self)(
545 await to_thread.run_sync(
546 self._path.move, target, limiter=self._limiter
547 ),
548 limiter=self._limiter,
549 )
551 async def move_into(
552 self,
553 target_dir: str | os.PathLike[str],
554 ) -> Self:
555 return type(self)(
556 await to_thread.run_sync(
557 self._path.move_into, target_dir, limiter=self._limiter
558 ),
559 limiter=self._limiter,
560 )
562 def is_relative_to(self, other: str | PathLike[str]) -> bool:
563 try:
564 self.relative_to(other)
565 return True
566 except ValueError:
567 return False
569 async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
570 func = partial(os.chmod, follow_symlinks=follow_symlinks)
571 return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter)
573 @classmethod
574 async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self:
575 path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter)
576 return cls(path, limiter=limiter)
578 if sys.version_info >= (3, 12):
580 async def exists(self, *, follow_symlinks: bool = True) -> bool:
581 return await to_thread.run_sync(
582 partial(self._path.exists, follow_symlinks=follow_symlinks),
583 abandon_on_cancel=True,
584 limiter=self._limiter,
585 )
587 else:
589 async def exists(self) -> bool:
590 return await to_thread.run_sync(
591 self._path.exists, abandon_on_cancel=True, limiter=self._limiter
592 )
594 async def expanduser(self) -> Self:
595 return type(self)(
596 await to_thread.run_sync(
597 self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter
598 ),
599 limiter=self._limiter,
600 )
602 if sys.version_info < (3, 12):
603 # Python 3.11 and earlier
604 def glob(self, pattern: str) -> AsyncIterator[Self]:
605 gen = self._path.glob(pattern)
606 return _PathIterator(gen, self._limiter, type(self))
607 elif (3, 12) <= sys.version_info < (3, 13):
608 # changed in Python 3.12:
609 # - The case_sensitive parameter was added.
610 def glob(
611 self,
612 pattern: str,
613 *,
614 case_sensitive: bool | None = None,
615 ) -> AsyncIterator[Self]:
616 gen = self._path.glob(pattern, case_sensitive=case_sensitive)
617 return _PathIterator(gen, self._limiter, type(self))
618 elif sys.version_info >= (3, 13):
619 # Changed in Python 3.13:
620 # - The recurse_symlinks parameter was added.
621 # - The pattern parameter accepts a path-like object.
622 def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
623 self,
624 pattern: str | PathLike[str],
625 *,
626 case_sensitive: bool | None = None,
627 recurse_symlinks: bool = False,
628 ) -> AsyncIterator[Self]:
629 gen = self._path.glob(
630 pattern, # type: ignore[arg-type]
631 case_sensitive=case_sensitive,
632 recurse_symlinks=recurse_symlinks,
633 )
634 return _PathIterator(gen, self._limiter, type(self))
636 if sys.version_info >= (3, 13):
638 async def group(self, *, follow_symlinks: bool = True) -> str:
639 return await to_thread.run_sync(
640 partial(self._path.group, follow_symlinks=follow_symlinks),
641 abandon_on_cancel=True,
642 limiter=self._limiter,
643 )
645 else:
647 async def group(self) -> str:
648 return await to_thread.run_sync(
649 self._path.group, abandon_on_cancel=True, limiter=self._limiter
650 )
652 async def hardlink_to(
653 self, target: str | bytes | PathLike[str] | PathLike[bytes]
654 ) -> None:
655 if isinstance(target, Path):
656 target = target._path
658 await to_thread.run_sync(os.link, target, self, limiter=self._limiter)
660 @classmethod
661 async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self:
662 home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter)
663 return cls(home_path, limiter=limiter)
665 def is_absolute(self) -> bool:
666 return self._path.is_absolute()
668 async def is_block_device(self) -> bool:
669 return await to_thread.run_sync(
670 self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter
671 )
673 async def is_char_device(self) -> bool:
674 return await to_thread.run_sync(
675 self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter
676 )
678 if sys.version_info >= (3, 13):
680 async def is_dir(self, *, follow_symlinks: bool = True) -> bool:
681 return await to_thread.run_sync(
682 partial(self._path.is_dir, follow_symlinks=follow_symlinks),
683 abandon_on_cancel=True,
684 limiter=self._limiter,
685 )
687 else:
689 async def is_dir(self) -> bool:
690 return await to_thread.run_sync(
691 self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter
692 )
694 async def is_fifo(self) -> bool:
695 return await to_thread.run_sync(
696 self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter
697 )
699 if sys.version_info >= (3, 13):
701 async def is_file(self, *, follow_symlinks: bool = True) -> bool:
702 return await to_thread.run_sync(
703 partial(self._path.is_file, follow_symlinks=follow_symlinks),
704 abandon_on_cancel=True,
705 limiter=self._limiter,
706 )
708 else:
710 async def is_file(self) -> bool:
711 return await to_thread.run_sync(
712 self._path.is_file, abandon_on_cancel=True, limiter=self._limiter
713 )
715 if sys.version_info >= (3, 12):
717 async def is_junction(self) -> bool:
718 return await to_thread.run_sync(
719 self._path.is_junction, limiter=self._limiter
720 )
722 async def is_mount(self) -> bool:
723 return await to_thread.run_sync(
724 os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter
725 )
727 if sys.version_info < (3, 15):
729 def is_reserved(self) -> bool:
730 return self._path.is_reserved()
732 async def is_socket(self) -> bool:
733 return await to_thread.run_sync(
734 self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter
735 )
737 async def is_symlink(self) -> bool:
738 return await to_thread.run_sync(
739 self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter
740 )
742 async def iterdir(self) -> AsyncIterator[Self]:
743 gen = (
744 self._path.iterdir()
745 if sys.version_info < (3, 13)
746 else await to_thread.run_sync(
747 self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter
748 )
749 )
750 async for path in _PathIterator(gen, self._limiter, type(self)):
751 yield path
753 def joinpath(self, *args: str | PathLike[str]) -> Self:
754 return type(self)(self._path.joinpath(*args), limiter=self._limiter)
756 async def lchmod(self, mode: int) -> None:
757 await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter)
759 async def lstat(self) -> os.stat_result:
760 return await to_thread.run_sync(
761 self._path.lstat, abandon_on_cancel=True, limiter=self._limiter
762 )
764 async def mkdir(
765 self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
766 ) -> None:
767 await to_thread.run_sync(
768 self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter
769 )
771 @overload
772 async def open(
773 self,
774 mode: OpenBinaryMode,
775 buffering: int = ...,
776 encoding: str | None = ...,
777 errors: str | None = ...,
778 newline: str | None = ...,
779 ) -> AsyncFile[bytes]: ...
781 @overload
782 async def open(
783 self,
784 mode: OpenTextMode = ...,
785 buffering: int = ...,
786 encoding: str | None = ...,
787 errors: str | None = ...,
788 newline: str | None = ...,
789 ) -> AsyncFile[str]: ...
791 async def open(
792 self,
793 mode: str = "r",
794 buffering: int = -1,
795 encoding: str | None = None,
796 errors: str | None = None,
797 newline: str | None = None,
798 ) -> AsyncFile[Any]:
799 fp = await to_thread.run_sync(
800 self._path.open,
801 mode,
802 buffering,
803 encoding,
804 errors,
805 newline,
806 limiter=self._limiter,
807 )
808 return AsyncFile(fp, limiter=self._limiter)
810 if sys.version_info >= (3, 13):
812 async def owner(self, *, follow_symlinks: bool = True) -> str:
813 return await to_thread.run_sync(
814 partial(self._path.owner, follow_symlinks=follow_symlinks),
815 abandon_on_cancel=True,
816 limiter=self._limiter,
817 )
819 else:
821 async def owner(self) -> str:
822 return await to_thread.run_sync(
823 self._path.owner, abandon_on_cancel=True, limiter=self._limiter
824 )
826 async def read_bytes(self) -> bytes:
827 return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter)
829 if sys.version_info >= (3, 13):
831 async def read_text(
832 self,
833 encoding: str | None = None,
834 errors: str | None = None,
835 newline: str | None = None,
836 ) -> str:
837 return await to_thread.run_sync(
838 self._path.read_text,
839 encoding,
840 errors,
841 newline,
842 limiter=self._limiter,
843 )
845 else:
847 async def read_text(
848 self, encoding: str | None = None, errors: str | None = None
849 ) -> str:
850 return await to_thread.run_sync(
851 self._path.read_text, encoding, errors, limiter=self._limiter
852 )
854 if sys.version_info >= (3, 12):
856 def relative_to(
857 self, *other: str | PathLike[str], walk_up: bool = False
858 ) -> Self:
859 # relative_to() should work with any PathLike but it doesn't
860 others = [pathlib.Path(other) for other in other]
861 return type(self)(
862 self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter
863 )
865 else:
867 def relative_to(self, *other: str | PathLike[str]) -> Self:
868 return type(self)(self._path.relative_to(*other), limiter=self._limiter)
870 async def readlink(self) -> Self:
871 target = await to_thread.run_sync(
872 os.readlink, self._path, limiter=self._limiter
873 )
874 return type(self)(target, limiter=self._limiter)
876 async def rename(self, target: str | pathlib.PurePath | Path) -> Self:
877 if isinstance(target, Path):
878 target = target._path
880 await to_thread.run_sync(self._path.rename, target, limiter=self._limiter)
881 return type(self)(target, limiter=self._limiter)
883 async def replace(self, target: str | pathlib.PurePath | Path) -> Self:
884 if isinstance(target, Path):
885 target = target._path
887 await to_thread.run_sync(self._path.replace, target, limiter=self._limiter)
888 return type(self)(target, limiter=self._limiter)
890 async def resolve(self, strict: bool = False) -> Self:
891 func = partial(self._path.resolve, strict=strict)
892 return type(self)(
893 await to_thread.run_sync(
894 func, abandon_on_cancel=True, limiter=self._limiter
895 ),
896 limiter=self._limiter,
897 )
899 if sys.version_info < (3, 12):
900 # Pre Python 3.12
901 def rglob(self, pattern: str) -> AsyncIterator[Self]:
902 gen = self._path.rglob(pattern)
903 return _PathIterator(gen, self._limiter, type(self))
904 elif (3, 12) <= sys.version_info < (3, 13):
905 # Changed in Python 3.12:
906 # - The case_sensitive parameter was added.
907 def rglob(
908 self, pattern: str, *, case_sensitive: bool | None = None
909 ) -> AsyncIterator[Self]:
910 gen = self._path.rglob(pattern, case_sensitive=case_sensitive)
911 return _PathIterator(gen, self._limiter, type(self))
912 elif sys.version_info >= (3, 13):
913 # Changed in Python 3.13:
914 # - The recurse_symlinks parameter was added.
915 # - The pattern parameter accepts a path-like object.
916 def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
917 self,
918 pattern: str | PathLike[str],
919 *,
920 case_sensitive: bool | None = None,
921 recurse_symlinks: bool = False,
922 ) -> AsyncIterator[Self]:
923 gen = self._path.rglob(
924 pattern, # type: ignore[arg-type]
925 case_sensitive=case_sensitive,
926 recurse_symlinks=recurse_symlinks,
927 )
928 return _PathIterator(gen, self._limiter, type(self))
930 async def rmdir(self) -> None:
931 await to_thread.run_sync(self._path.rmdir, limiter=self._limiter)
933 async def samefile(self, other_path: str | PathLike[str]) -> bool:
934 if isinstance(other_path, Path):
935 other_path = other_path._path
937 return await to_thread.run_sync(
938 self._path.samefile,
939 other_path,
940 abandon_on_cancel=True,
941 limiter=self._limiter,
942 )
944 async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
945 func = partial(os.stat, follow_symlinks=follow_symlinks)
946 return await to_thread.run_sync(
947 func, self._path, abandon_on_cancel=True, limiter=self._limiter
948 )
950 async def symlink_to(
951 self,
952 target: str | bytes | PathLike[str] | PathLike[bytes],
953 target_is_directory: bool = False,
954 ) -> None:
955 if isinstance(target, Path):
956 target = target._path
958 await to_thread.run_sync(
959 self._path.symlink_to, target, target_is_directory, limiter=self._limiter
960 )
962 async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
963 await to_thread.run_sync(
964 self._path.touch, mode, exist_ok, limiter=self._limiter
965 )
967 async def unlink(self, missing_ok: bool = False) -> None:
968 try:
969 await to_thread.run_sync(self._path.unlink, limiter=self._limiter)
970 except FileNotFoundError:
971 if not missing_ok:
972 raise
974 if sys.version_info >= (3, 12):
976 async def walk(
977 self,
978 top_down: bool = True,
979 on_error: Callable[[OSError], object] | None = None,
980 follow_symlinks: bool = False,
981 ) -> AsyncIterator[tuple[Self, list[str], list[str]]]:
982 def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None:
983 try:
984 return next(gen)
985 except StopIteration:
986 return None
988 gen = self._path.walk(top_down, on_error, follow_symlinks)
989 while True:
990 value = await to_thread.run_sync(get_next_value, limiter=self._limiter)
991 if value is None:
992 return
994 root, dirs, paths = value
995 yield type(self)(root, limiter=self._limiter), dirs, paths
997 def with_name(self, name: str) -> Self:
998 return type(self)(self._path.with_name(name), limiter=self._limiter)
1000 if sys.version_info < (3, 13):
1001 # Backport pathlib's Python>=3.13 behavior for empty stems on paths with non-empty suffixes.
1002 # See: https://github.com/python/cpython/pull/114612
1003 def with_stem(self, stem: str) -> Self:
1004 suffix = self._path.suffix
1005 if not suffix:
1006 return self.with_name(stem)
1007 elif not stem:
1008 # If the suffix is non-empty, we can't make the stem empty.
1009 raise ValueError(f"{self!r} has a non-empty suffix")
1010 else:
1011 return self.with_name(stem + suffix)
1012 else:
1014 def with_stem(self, stem: str) -> Self:
1015 return type(self)(self._path.with_stem(stem), limiter=self._limiter)
1017 def with_suffix(self, suffix: str) -> Self:
1018 return type(self)(self._path.with_suffix(suffix), limiter=self._limiter)
1020 def with_segments(self, *pathsegments: str | PathLike[str]) -> Self:
1021 return type(self)(*pathsegments, limiter=self._limiter)
1023 async def write_bytes(self, data: ReadableBuffer) -> int:
1024 return await to_thread.run_sync(
1025 self._path.write_bytes, data, limiter=self._limiter
1026 )
1028 async def write_text(
1029 self,
1030 data: str,
1031 encoding: str | None = None,
1032 errors: str | None = None,
1033 newline: str | None = None,
1034 ) -> int:
1035 return await to_thread.run_sync(
1036 self._path.write_text,
1037 data,
1038 encoding,
1039 errors,
1040 newline,
1041 limiter=self._limiter,
1042 )
1045PathLike.register(Path)