_fileio.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  1. from __future__ import annotations
  2. import os
  3. import pathlib
  4. import sys
  5. from collections.abc import (
  6. AsyncIterator,
  7. Callable,
  8. Iterable,
  9. Iterator,
  10. Sequence,
  11. )
  12. from dataclasses import dataclass
  13. from functools import partial
  14. from os import PathLike
  15. from typing import (
  16. IO,
  17. TYPE_CHECKING,
  18. Any,
  19. AnyStr,
  20. ClassVar,
  21. Final,
  22. Generic,
  23. TypeVar,
  24. overload,
  25. )
  26. from .. import to_thread
  27. from ..abc import AsyncResource
  28. from ._synchronization import CapacityLimiter
  29. if sys.version_info >= (3, 11):
  30. from typing import Self
  31. else:
  32. from typing_extensions import Self
  33. if sys.version_info >= (3, 14):
  34. from pathlib.types import PathInfo
  35. if TYPE_CHECKING:
  36. from types import ModuleType
  37. from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
  38. else:
  39. ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object
  40. T = TypeVar("T", bound="Path")
  41. class AsyncFile(AsyncResource, Generic[AnyStr]):
  42. """
  43. An asynchronous file object.
  44. This class wraps a standard file object and provides async friendly versions of the
  45. following blocking methods (where available on the original file object):
  46. * read
  47. * read1
  48. * readline
  49. * readlines
  50. * readinto
  51. * readinto1
  52. * write
  53. * writelines
  54. * truncate
  55. * seek
  56. * tell
  57. * flush
  58. All other methods are directly passed through.
  59. This class supports the asynchronous context manager protocol which closes the
  60. underlying file at the end of the context block.
  61. This class also supports asynchronous iteration::
  62. async with await open_file(...) as f:
  63. async for line in f:
  64. print(line)
  65. """
  66. def __init__(
  67. self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None
  68. ) -> None:
  69. if limiter is not None and not isinstance(limiter, CapacityLimiter):
  70. raise TypeError(
  71. f"limiter must be a CapacityLimiter or None, not "
  72. f"{limiter.__class__.__name__}"
  73. )
  74. self._fp: Any = fp
  75. self._limiter = limiter
  76. def __getattr__(self, name: str) -> object:
  77. return getattr(self._fp, name)
  78. @property
  79. def limiter(self) -> CapacityLimiter | None:
  80. """The capacity limiter used by this file object, if not the global limiter."""
  81. return self._limiter
  82. @property
  83. def wrapped(self) -> IO[AnyStr]:
  84. """The wrapped file object."""
  85. return self._fp
  86. async def __aiter__(self) -> AsyncIterator[AnyStr]:
  87. while True:
  88. line = await self.readline()
  89. if line:
  90. yield line
  91. else:
  92. break
  93. async def aclose(self) -> None:
  94. return await to_thread.run_sync(self._fp.close, limiter=self._limiter)
  95. async def read(self, size: int = -1) -> AnyStr:
  96. return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter)
  97. async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes:
  98. return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter)
  99. async def readline(self) -> AnyStr:
  100. return await to_thread.run_sync(self._fp.readline, limiter=self._limiter)
  101. async def readlines(self) -> list[AnyStr]:
  102. return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter)
  103. async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
  104. return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter)
  105. async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
  106. return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter)
  107. @overload
  108. async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ...
  109. @overload
  110. async def write(self: AsyncFile[str], b: str) -> int: ...
  111. async def write(self, b: ReadableBuffer | str) -> int:
  112. return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter)
  113. @overload
  114. async def writelines(
  115. self: AsyncFile[bytes], lines: Iterable[ReadableBuffer]
  116. ) -> None: ...
  117. @overload
  118. async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ...
  119. async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None:
  120. return await to_thread.run_sync(
  121. self._fp.writelines, lines, limiter=self._limiter
  122. )
  123. async def truncate(self, size: int | None = None) -> int:
  124. return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter)
  125. async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
  126. return await to_thread.run_sync(
  127. self._fp.seek, offset, whence, limiter=self._limiter
  128. )
  129. async def tell(self) -> int:
  130. return await to_thread.run_sync(self._fp.tell, limiter=self._limiter)
  131. async def flush(self) -> None:
  132. return await to_thread.run_sync(self._fp.flush, limiter=self._limiter)
  133. @overload
  134. async def open_file(
  135. file: str | PathLike[str] | int,
  136. mode: OpenBinaryMode,
  137. buffering: int = ...,
  138. encoding: str | None = ...,
  139. errors: str | None = ...,
  140. newline: str | None = ...,
  141. closefd: bool = ...,
  142. opener: Callable[[str, int], int] | None = ...,
  143. *,
  144. limiter: CapacityLimiter | None = ...,
  145. ) -> AsyncFile[bytes]: ...
  146. @overload
  147. async def open_file(
  148. file: str | PathLike[str] | int,
  149. mode: OpenTextMode = ...,
  150. buffering: int = ...,
  151. encoding: str | None = ...,
  152. errors: str | None = ...,
  153. newline: str | None = ...,
  154. closefd: bool = ...,
  155. opener: Callable[[str, int], int] | None = ...,
  156. *,
  157. limiter: CapacityLimiter | None = ...,
  158. ) -> AsyncFile[str]: ...
  159. async def open_file(
  160. file: str | PathLike[str] | int,
  161. mode: str = "r",
  162. buffering: int = -1,
  163. encoding: str | None = None,
  164. errors: str | None = None,
  165. newline: str | None = None,
  166. closefd: bool = True,
  167. opener: Callable[[str, int], int] | None = None,
  168. *,
  169. limiter: CapacityLimiter | None = None,
  170. ) -> AsyncFile[Any]:
  171. """
  172. Open a file asynchronously.
  173. Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`.
  174. :param limiter: an optional capacity limiter to use with the file
  175. instead of the default one
  176. :return: an asynchronous file object
  177. .. versionchanged:: 4.14.0
  178. Added the ``limiter`` keyword argument.
  179. """
  180. fp = await to_thread.run_sync(
  181. open,
  182. file,
  183. mode,
  184. buffering,
  185. encoding,
  186. errors,
  187. newline,
  188. closefd,
  189. opener,
  190. limiter=limiter,
  191. )
  192. return AsyncFile(fp, limiter=limiter)
  193. def wrap_file(
  194. file: IO[AnyStr], *, limiter: CapacityLimiter | None = None
  195. ) -> AsyncFile[AnyStr]:
  196. """
  197. Wrap an existing file as an asynchronous file.
  198. :param file: an existing file-like object
  199. :param limiter: an optional capacity limiter to use with the file
  200. instead of the default one
  201. :return: an asynchronous file object
  202. .. versionchanged:: 4.14.0
  203. Added the ``limiter`` keyword argument.
  204. """
  205. return AsyncFile(file, limiter=limiter)
  206. @dataclass(eq=False)
  207. class _PathIterator(AsyncIterator[T]):
  208. iterator: Iterator[PathLike[str]]
  209. limiter: CapacityLimiter | None
  210. # This was added to ensure that iterating over a subclass of Path yields instances
  211. # of that subclass rather than the base Path class.
  212. path_cls: type[T]
  213. async def __anext__(self) -> T:
  214. nextval = await to_thread.run_sync(
  215. next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter
  216. )
  217. if nextval is None:
  218. raise StopAsyncIteration from None
  219. return self.path_cls(nextval, limiter=self.limiter)
  220. class Path:
  221. """
  222. An asynchronous version of :class:`pathlib.Path`.
  223. This class cannot be substituted for :class:`pathlib.Path` or
  224. :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike`
  225. interface.
  226. It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for
  227. the deprecated :meth:`~pathlib.Path.link_to` method.
  228. Some methods may be unavailable or have limited functionality, based on the Python
  229. version:
  230. * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later)
  231. * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later)
  232. * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later)
  233. * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later)
  234. * :attr:`~pathlib.Path.info` (available on Python 3.14 or later)
  235. * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later)
  236. * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only
  237. available on Python 3.13 or later)
  238. * :meth:`~pathlib.Path.move` (available on Python 3.14 or later)
  239. * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later)
  240. * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available
  241. on Python 3.12 or later)
  242. * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later)
  243. Any methods that do disk I/O need to be awaited on. These methods are:
  244. * :meth:`~pathlib.Path.absolute`
  245. * :meth:`~pathlib.Path.chmod`
  246. * :meth:`~pathlib.Path.cwd`
  247. * :meth:`~pathlib.Path.exists`
  248. * :meth:`~pathlib.Path.expanduser`
  249. * :meth:`~pathlib.Path.group`
  250. * :meth:`~pathlib.Path.hardlink_to`
  251. * :meth:`~pathlib.Path.home`
  252. * :meth:`~pathlib.Path.is_block_device`
  253. * :meth:`~pathlib.Path.is_char_device`
  254. * :meth:`~pathlib.Path.is_dir`
  255. * :meth:`~pathlib.Path.is_fifo`
  256. * :meth:`~pathlib.Path.is_file`
  257. * :meth:`~pathlib.Path.is_junction`
  258. * :meth:`~pathlib.Path.is_mount`
  259. * :meth:`~pathlib.Path.is_socket`
  260. * :meth:`~pathlib.Path.is_symlink`
  261. * :meth:`~pathlib.Path.lchmod`
  262. * :meth:`~pathlib.Path.lstat`
  263. * :meth:`~pathlib.Path.mkdir`
  264. * :meth:`~pathlib.Path.open`
  265. * :meth:`~pathlib.Path.owner`
  266. * :meth:`~pathlib.Path.read_bytes`
  267. * :meth:`~pathlib.Path.read_text`
  268. * :meth:`~pathlib.Path.readlink`
  269. * :meth:`~pathlib.Path.rename`
  270. * :meth:`~pathlib.Path.replace`
  271. * :meth:`~pathlib.Path.resolve`
  272. * :meth:`~pathlib.Path.rmdir`
  273. * :meth:`~pathlib.Path.samefile`
  274. * :meth:`~pathlib.Path.stat`
  275. * :meth:`~pathlib.Path.symlink_to`
  276. * :meth:`~pathlib.Path.touch`
  277. * :meth:`~pathlib.Path.unlink`
  278. * :meth:`~pathlib.Path.walk`
  279. * :meth:`~pathlib.Path.write_bytes`
  280. * :meth:`~pathlib.Path.write_text`
  281. Additionally, the following methods return an async iterator yielding
  282. :class:`~.Path` objects:
  283. * :meth:`~pathlib.Path.glob`
  284. * :meth:`~pathlib.Path.iterdir`
  285. * :meth:`~pathlib.Path.rglob`
  286. .. versionchanged:: 4.14.0
  287. Added the ``limiter`` keyword argument.
  288. """
  289. __slots__ = "_path", "_limiter", "__weakref__"
  290. __weakref__: Any
  291. def __init__(
  292. self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None
  293. ) -> None:
  294. if limiter is not None and not isinstance(limiter, CapacityLimiter):
  295. raise TypeError(
  296. f"limiter must be a CapacityLimiter or None, not "
  297. f"{limiter.__class__.__name__}"
  298. )
  299. self._path: Final[pathlib.Path] = pathlib.Path(*args)
  300. self._limiter = limiter
  301. def __fspath__(self) -> str:
  302. return self._path.__fspath__()
  303. if sys.version_info >= (3, 15):
  304. def __vfspath__(self) -> str:
  305. return self._path.__vfspath__()
  306. def __str__(self) -> str:
  307. return self._path.__str__()
  308. def __repr__(self) -> str:
  309. return f"{self.__class__.__name__}({self.as_posix()!r})"
  310. def __bytes__(self) -> bytes:
  311. return self._path.__bytes__()
  312. def __hash__(self) -> int:
  313. return self._path.__hash__()
  314. def __eq__(self, other: object) -> bool:
  315. target = other._path if isinstance(other, Path) else other
  316. return self._path.__eq__(target)
  317. def __lt__(self, other: pathlib.PurePath | Path) -> bool:
  318. target = other._path if isinstance(other, Path) else other
  319. return self._path.__lt__(target)
  320. def __le__(self, other: pathlib.PurePath | Path) -> bool:
  321. target = other._path if isinstance(other, Path) else other
  322. return self._path.__le__(target)
  323. def __gt__(self, other: pathlib.PurePath | Path) -> bool:
  324. target = other._path if isinstance(other, Path) else other
  325. return self._path.__gt__(target)
  326. def __ge__(self, other: pathlib.PurePath | Path) -> bool:
  327. target = other._path if isinstance(other, Path) else other
  328. return self._path.__ge__(target)
  329. def __truediv__(self, other: str | PathLike[str]) -> Self:
  330. return type(self)(self._path / other, limiter=self._limiter)
  331. def __rtruediv__(self, other: str | PathLike[str]) -> Self:
  332. return type(self)(other, limiter=self._limiter) / self
  333. @property
  334. def limiter(self) -> CapacityLimiter | None:
  335. """The capacity limiter used by this path, if not the global limiter."""
  336. return self._limiter
  337. @property
  338. def parts(self) -> tuple[str, ...]:
  339. return self._path.parts
  340. @property
  341. def drive(self) -> str:
  342. return self._path.drive
  343. @property
  344. def root(self) -> str:
  345. return self._path.root
  346. @property
  347. def anchor(self) -> str:
  348. return self._path.anchor
  349. @property
  350. def parents(self) -> Sequence[Self]:
  351. return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents)
  352. @property
  353. def parent(self) -> Self:
  354. return type(self)(self._path.parent, limiter=self._limiter)
  355. @property
  356. def name(self) -> str:
  357. return self._path.name
  358. @property
  359. def suffix(self) -> str:
  360. return self._path.suffix
  361. @property
  362. def suffixes(self) -> list[str]:
  363. return self._path.suffixes
  364. @property
  365. def stem(self) -> str:
  366. return self._path.stem
  367. async def absolute(self) -> Self:
  368. path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter)
  369. return type(self)(path, limiter=self._limiter)
  370. def as_posix(self) -> str:
  371. return self._path.as_posix()
  372. def as_uri(self) -> str:
  373. return self._path.as_uri()
  374. if sys.version_info >= (3, 13):
  375. parser: ClassVar[ModuleType] = pathlib.Path.parser
  376. @classmethod
  377. def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self:
  378. return cls(pathlib.Path.from_uri(uri), limiter=limiter)
  379. def full_match(
  380. self, path_pattern: str, *, case_sensitive: bool | None = None
  381. ) -> bool:
  382. return self._path.full_match(path_pattern, case_sensitive=case_sensitive)
  383. def match(
  384. self, path_pattern: str, *, case_sensitive: bool | None = None
  385. ) -> bool:
  386. return self._path.match(path_pattern, case_sensitive=case_sensitive)
  387. else:
  388. def match(self, path_pattern: str) -> bool:
  389. return self._path.match(path_pattern)
  390. if sys.version_info >= (3, 14):
  391. @property
  392. def info(self) -> PathInfo:
  393. return self._path.info
  394. async def copy(
  395. self,
  396. target: str | os.PathLike[str],
  397. *,
  398. follow_symlinks: bool = True,
  399. preserve_metadata: bool = False,
  400. ) -> Self:
  401. func = partial(
  402. self._path.copy,
  403. follow_symlinks=follow_symlinks,
  404. preserve_metadata=preserve_metadata,
  405. )
  406. return type(self)(
  407. await to_thread.run_sync(
  408. func, pathlib.Path(target), limiter=self._limiter
  409. ),
  410. limiter=self._limiter,
  411. )
  412. async def copy_into(
  413. self,
  414. target_dir: str | os.PathLike[str],
  415. *,
  416. follow_symlinks: bool = True,
  417. preserve_metadata: bool = False,
  418. ) -> Self:
  419. func = partial(
  420. self._path.copy_into,
  421. follow_symlinks=follow_symlinks,
  422. preserve_metadata=preserve_metadata,
  423. )
  424. return type(self)(
  425. await to_thread.run_sync(
  426. func, pathlib.Path(target_dir), limiter=self._limiter
  427. ),
  428. limiter=self._limiter,
  429. )
  430. async def move(self, target: str | os.PathLike[str]) -> Self:
  431. # Upstream does not handle anyio.Path properly as a PathLike
  432. target = pathlib.Path(target)
  433. return type(self)(
  434. await to_thread.run_sync(
  435. self._path.move, target, limiter=self._limiter
  436. ),
  437. limiter=self._limiter,
  438. )
  439. async def move_into(
  440. self,
  441. target_dir: str | os.PathLike[str],
  442. ) -> Self:
  443. return type(self)(
  444. await to_thread.run_sync(
  445. self._path.move_into, target_dir, limiter=self._limiter
  446. ),
  447. limiter=self._limiter,
  448. )
  449. def is_relative_to(self, other: str | PathLike[str]) -> bool:
  450. try:
  451. self.relative_to(other)
  452. return True
  453. except ValueError:
  454. return False
  455. async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
  456. func = partial(os.chmod, follow_symlinks=follow_symlinks)
  457. return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter)
  458. @classmethod
  459. async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self:
  460. path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter)
  461. return cls(path, limiter=limiter)
  462. async def exists(self) -> bool:
  463. return await to_thread.run_sync(
  464. self._path.exists, abandon_on_cancel=True, limiter=self._limiter
  465. )
  466. async def expanduser(self) -> Self:
  467. return type(self)(
  468. await to_thread.run_sync(
  469. self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter
  470. ),
  471. limiter=self._limiter,
  472. )
  473. if sys.version_info < (3, 12):
  474. # Python 3.11 and earlier
  475. def glob(self, pattern: str) -> AsyncIterator[Self]:
  476. gen = self._path.glob(pattern)
  477. return _PathIterator(gen, self._limiter, type(self))
  478. elif (3, 12) <= sys.version_info < (3, 13):
  479. # changed in Python 3.12:
  480. # - The case_sensitive parameter was added.
  481. def glob(
  482. self,
  483. pattern: str,
  484. *,
  485. case_sensitive: bool | None = None,
  486. ) -> AsyncIterator[Self]:
  487. gen = self._path.glob(pattern, case_sensitive=case_sensitive)
  488. return _PathIterator(gen, self._limiter, type(self))
  489. elif sys.version_info >= (3, 13):
  490. # Changed in Python 3.13:
  491. # - The recurse_symlinks parameter was added.
  492. # - The pattern parameter accepts a path-like object.
  493. def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
  494. self,
  495. pattern: str | PathLike[str],
  496. *,
  497. case_sensitive: bool | None = None,
  498. recurse_symlinks: bool = False,
  499. ) -> AsyncIterator[Self]:
  500. gen = self._path.glob(
  501. pattern, # type: ignore[arg-type]
  502. case_sensitive=case_sensitive,
  503. recurse_symlinks=recurse_symlinks,
  504. )
  505. return _PathIterator(gen, self._limiter, type(self))
  506. async def group(self) -> str:
  507. return await to_thread.run_sync(
  508. self._path.group, abandon_on_cancel=True, limiter=self._limiter
  509. )
  510. async def hardlink_to(
  511. self, target: str | bytes | PathLike[str] | PathLike[bytes]
  512. ) -> None:
  513. if isinstance(target, Path):
  514. target = target._path
  515. await to_thread.run_sync(os.link, target, self, limiter=self._limiter)
  516. @classmethod
  517. async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self:
  518. home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter)
  519. return cls(home_path, limiter=limiter)
  520. def is_absolute(self) -> bool:
  521. return self._path.is_absolute()
  522. async def is_block_device(self) -> bool:
  523. return await to_thread.run_sync(
  524. self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter
  525. )
  526. async def is_char_device(self) -> bool:
  527. return await to_thread.run_sync(
  528. self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter
  529. )
  530. async def is_dir(self) -> bool:
  531. return await to_thread.run_sync(
  532. self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter
  533. )
  534. async def is_fifo(self) -> bool:
  535. return await to_thread.run_sync(
  536. self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter
  537. )
  538. async def is_file(self) -> bool:
  539. return await to_thread.run_sync(
  540. self._path.is_file, abandon_on_cancel=True, limiter=self._limiter
  541. )
  542. if sys.version_info >= (3, 12):
  543. async def is_junction(self) -> bool:
  544. return await to_thread.run_sync(
  545. self._path.is_junction, limiter=self._limiter
  546. )
  547. async def is_mount(self) -> bool:
  548. return await to_thread.run_sync(
  549. os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter
  550. )
  551. if sys.version_info < (3, 15):
  552. def is_reserved(self) -> bool:
  553. return self._path.is_reserved()
  554. async def is_socket(self) -> bool:
  555. return await to_thread.run_sync(
  556. self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter
  557. )
  558. async def is_symlink(self) -> bool:
  559. return await to_thread.run_sync(
  560. self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter
  561. )
  562. async def iterdir(self) -> AsyncIterator[Self]:
  563. gen = (
  564. self._path.iterdir()
  565. if sys.version_info < (3, 13)
  566. else await to_thread.run_sync(
  567. self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter
  568. )
  569. )
  570. async for path in _PathIterator(gen, self._limiter, type(self)):
  571. yield path
  572. def joinpath(self, *args: str | PathLike[str]) -> Self:
  573. return type(self)(self._path.joinpath(*args), limiter=self._limiter)
  574. async def lchmod(self, mode: int) -> None:
  575. await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter)
  576. async def lstat(self) -> os.stat_result:
  577. return await to_thread.run_sync(
  578. self._path.lstat, abandon_on_cancel=True, limiter=self._limiter
  579. )
  580. async def mkdir(
  581. self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
  582. ) -> None:
  583. await to_thread.run_sync(
  584. self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter
  585. )
  586. @overload
  587. async def open(
  588. self,
  589. mode: OpenBinaryMode,
  590. buffering: int = ...,
  591. encoding: str | None = ...,
  592. errors: str | None = ...,
  593. newline: str | None = ...,
  594. ) -> AsyncFile[bytes]: ...
  595. @overload
  596. async def open(
  597. self,
  598. mode: OpenTextMode = ...,
  599. buffering: int = ...,
  600. encoding: str | None = ...,
  601. errors: str | None = ...,
  602. newline: str | None = ...,
  603. ) -> AsyncFile[str]: ...
  604. async def open(
  605. self,
  606. mode: str = "r",
  607. buffering: int = -1,
  608. encoding: str | None = None,
  609. errors: str | None = None,
  610. newline: str | None = None,
  611. ) -> AsyncFile[Any]:
  612. fp = await to_thread.run_sync(
  613. self._path.open,
  614. mode,
  615. buffering,
  616. encoding,
  617. errors,
  618. newline,
  619. limiter=self._limiter,
  620. )
  621. return AsyncFile(fp, limiter=self._limiter)
  622. async def owner(self) -> str:
  623. return await to_thread.run_sync(
  624. self._path.owner, abandon_on_cancel=True, limiter=self._limiter
  625. )
  626. async def read_bytes(self) -> bytes:
  627. return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter)
  628. async def read_text(
  629. self, encoding: str | None = None, errors: str | None = None
  630. ) -> str:
  631. return await to_thread.run_sync(
  632. self._path.read_text, encoding, errors, limiter=self._limiter
  633. )
  634. if sys.version_info >= (3, 12):
  635. def relative_to(
  636. self, *other: str | PathLike[str], walk_up: bool = False
  637. ) -> Self:
  638. # relative_to() should work with any PathLike but it doesn't
  639. others = [pathlib.Path(other) for other in other]
  640. return type(self)(
  641. self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter
  642. )
  643. else:
  644. def relative_to(self, *other: str | PathLike[str]) -> Self:
  645. return type(self)(self._path.relative_to(*other), limiter=self._limiter)
  646. async def readlink(self) -> Self:
  647. target = await to_thread.run_sync(
  648. os.readlink, self._path, limiter=self._limiter
  649. )
  650. return type(self)(target, limiter=self._limiter)
  651. async def rename(self, target: str | pathlib.PurePath | Path) -> Self:
  652. if isinstance(target, Path):
  653. target = target._path
  654. await to_thread.run_sync(self._path.rename, target, limiter=self._limiter)
  655. return type(self)(target, limiter=self._limiter)
  656. async def replace(self, target: str | pathlib.PurePath | Path) -> Self:
  657. if isinstance(target, Path):
  658. target = target._path
  659. await to_thread.run_sync(self._path.replace, target, limiter=self._limiter)
  660. return type(self)(target, limiter=self._limiter)
  661. async def resolve(self, strict: bool = False) -> Self:
  662. func = partial(self._path.resolve, strict=strict)
  663. return type(self)(
  664. await to_thread.run_sync(
  665. func, abandon_on_cancel=True, limiter=self._limiter
  666. ),
  667. limiter=self._limiter,
  668. )
  669. if sys.version_info < (3, 12):
  670. # Pre Python 3.12
  671. def rglob(self, pattern: str) -> AsyncIterator[Self]:
  672. gen = self._path.rglob(pattern)
  673. return _PathIterator(gen, self._limiter, type(self))
  674. elif (3, 12) <= sys.version_info < (3, 13):
  675. # Changed in Python 3.12:
  676. # - The case_sensitive parameter was added.
  677. def rglob(
  678. self, pattern: str, *, case_sensitive: bool | None = None
  679. ) -> AsyncIterator[Self]:
  680. gen = self._path.rglob(pattern, case_sensitive=case_sensitive)
  681. return _PathIterator(gen, self._limiter, type(self))
  682. elif sys.version_info >= (3, 13):
  683. # Changed in Python 3.13:
  684. # - The recurse_symlinks parameter was added.
  685. # - The pattern parameter accepts a path-like object.
  686. def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
  687. self,
  688. pattern: str | PathLike[str],
  689. *,
  690. case_sensitive: bool | None = None,
  691. recurse_symlinks: bool = False,
  692. ) -> AsyncIterator[Self]:
  693. gen = self._path.rglob(
  694. pattern, # type: ignore[arg-type]
  695. case_sensitive=case_sensitive,
  696. recurse_symlinks=recurse_symlinks,
  697. )
  698. return _PathIterator(gen, self._limiter, type(self))
  699. async def rmdir(self) -> None:
  700. await to_thread.run_sync(self._path.rmdir, limiter=self._limiter)
  701. async def samefile(self, other_path: str | PathLike[str]) -> bool:
  702. if isinstance(other_path, Path):
  703. other_path = other_path._path
  704. return await to_thread.run_sync(
  705. self._path.samefile,
  706. other_path,
  707. abandon_on_cancel=True,
  708. limiter=self._limiter,
  709. )
  710. async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
  711. func = partial(os.stat, follow_symlinks=follow_symlinks)
  712. return await to_thread.run_sync(
  713. func, self._path, abandon_on_cancel=True, limiter=self._limiter
  714. )
  715. async def symlink_to(
  716. self,
  717. target: str | bytes | PathLike[str] | PathLike[bytes],
  718. target_is_directory: bool = False,
  719. ) -> None:
  720. if isinstance(target, Path):
  721. target = target._path
  722. await to_thread.run_sync(
  723. self._path.symlink_to, target, target_is_directory, limiter=self._limiter
  724. )
  725. async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
  726. await to_thread.run_sync(
  727. self._path.touch, mode, exist_ok, limiter=self._limiter
  728. )
  729. async def unlink(self, missing_ok: bool = False) -> None:
  730. try:
  731. await to_thread.run_sync(self._path.unlink, limiter=self._limiter)
  732. except FileNotFoundError:
  733. if not missing_ok:
  734. raise
  735. if sys.version_info >= (3, 12):
  736. async def walk(
  737. self,
  738. top_down: bool = True,
  739. on_error: Callable[[OSError], object] | None = None,
  740. follow_symlinks: bool = False,
  741. ) -> AsyncIterator[tuple[Self, list[str], list[str]]]:
  742. def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None:
  743. try:
  744. return next(gen)
  745. except StopIteration:
  746. return None
  747. gen = self._path.walk(top_down, on_error, follow_symlinks)
  748. while True:
  749. value = await to_thread.run_sync(get_next_value, limiter=self._limiter)
  750. if value is None:
  751. return
  752. root, dirs, paths = value
  753. yield type(self)(root, limiter=self._limiter), dirs, paths
  754. def with_name(self, name: str) -> Self:
  755. return type(self)(self._path.with_name(name), limiter=self._limiter)
  756. def with_stem(self, stem: str) -> Self:
  757. return type(self)(
  758. self._path.with_name(stem + self._path.suffix), limiter=self._limiter
  759. )
  760. def with_suffix(self, suffix: str) -> Self:
  761. return type(self)(self._path.with_suffix(suffix), limiter=self._limiter)
  762. def with_segments(self, *pathsegments: str | PathLike[str]) -> Self:
  763. return type(self)(*pathsegments, limiter=self._limiter)
  764. async def write_bytes(self, data: ReadableBuffer) -> int:
  765. return await to_thread.run_sync(
  766. self._path.write_bytes, data, limiter=self._limiter
  767. )
  768. async def write_text(
  769. self,
  770. data: str,
  771. encoding: str | None = None,
  772. errors: str | None = None,
  773. newline: str | None = None,
  774. ) -> int:
  775. return await to_thread.run_sync(
  776. self._path.write_text,
  777. data,
  778. encoding,
  779. errors,
  780. newline,
  781. limiter=self._limiter,
  782. )
  783. PathLike.register(Path)