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

397 statements  

1from __future__ import annotations 

2 

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) 

27 

28from .. import to_thread 

29from ..abc import AsyncResource 

30from ._synchronization import CapacityLimiter 

31 

32if sys.version_info >= (3, 11): 

33 from typing import Self 

34else: 

35 from typing_extensions import Self 

36 

37if sys.version_info >= (3, 14): 

38 from pathlib.types import PathInfo 

39 

40if TYPE_CHECKING: 

41 from types import ModuleType 

42 

43 from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer 

44else: 

45 ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object 

46 

47 

48T = TypeVar("T", bound="Path") 

49 

50 

51class AsyncFile(AsyncResource, Generic[AnyStr]): 

52 """ 

53 An asynchronous file object. 

54 

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): 

57 

58 * read 

59 * read1 

60 * readline 

61 * readlines 

62 * readinto 

63 * readinto1 

64 * write 

65 * writelines 

66 * truncate 

67 * seek 

68 * tell 

69 * flush 

70 

71 All other methods are directly passed through. 

72 

73 This class supports the asynchronous context manager protocol which closes the 

74 underlying file at the end of the context block. 

75 

76 This class also supports asynchronous iteration:: 

77 

78 async with await open_file(...) as f: 

79 async for line in f: 

80 print(line) 

81 """ 

82 

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 ) 

91 

92 self._fp: Any = fp 

93 self._limiter = limiter 

94 

95 def __getattr__(self, name: str) -> object: 

96 return getattr(self._fp, name) 

97 

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 

102 

103 @property 

104 def wrapped(self) -> IO[AnyStr]: 

105 """The wrapped file object.""" 

106 return self._fp 

107 

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 

115 

116 async def aclose(self) -> None: 

117 return await to_thread.run_sync(self._fp.close, limiter=self._limiter) 

118 

119 async def read(self, size: int = -1) -> AnyStr: 

120 return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter) 

121 

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) 

124 

125 async def readline(self) -> AnyStr: 

126 return await to_thread.run_sync(self._fp.readline, limiter=self._limiter) 

127 

128 async def readlines(self) -> list[AnyStr]: 

129 return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter) 

130 

131 async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int: 

132 return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter) 

133 

134 async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int: 

135 return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter) 

136 

137 @overload 

138 async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ... 

139 

140 @overload 

141 async def write(self: AsyncFile[str], b: str) -> int: ... 

142 

143 async def write(self, b: ReadableBuffer | str) -> int: 

144 return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter) 

145 

146 @overload 

147 async def writelines( 

148 self: AsyncFile[bytes], lines: Iterable[ReadableBuffer] 

149 ) -> None: ... 

150 

151 @overload 

152 async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ... 

153 

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 ) 

158 

159 async def truncate(self, size: int | None = None) -> int: 

160 return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter) 

161 

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 ) 

166 

167 async def tell(self) -> int: 

168 return await to_thread.run_sync(self._fp.tell, limiter=self._limiter) 

169 

170 async def flush(self) -> None: 

171 return await to_thread.run_sync(self._fp.flush, limiter=self._limiter) 

172 

173 

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]: ... 

187 

188 

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]: ... 

202 

203 

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. 

218 

219 Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`. 

220 

221 :param limiter: an optional capacity limiter to use with the file 

222 instead of the default one 

223 :return: an asynchronous file object 

224 

225 .. versionchanged:: 4.14.0 

226 Added the ``limiter`` keyword argument. 

227 

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) 

242 

243 

244def wrap_file( 

245 file: IO[AnyStr], *, limiter: CapacityLimiter | None = None 

246) -> AsyncFile[AnyStr]: 

247 """ 

248 Wrap an existing file as an asynchronous file. 

249 

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 

254 

255 .. versionchanged:: 4.14.0 

256 Added the ``limiter`` keyword argument. 

257 

258 """ 

259 return AsyncFile(file, limiter=limiter) 

260 

261 

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] 

269 

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 

276 

277 return self.path_cls(nextval, limiter=self.limiter) 

278 

279 

280class Path: 

281 """ 

282 An asynchronous version of :class:`pathlib.Path`. 

283 

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. 

287 

288 It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for 

289 the deprecated :meth:`~pathlib.Path.link_to` method. 

290 

291 Some methods may be unavailable or have limited functionality, based on the Python 

292 version: 

293 

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) 

307 

308 Any methods that do disk I/O need to be awaited on. These methods are: 

309 

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` 

347 

348 Additionally, the following methods return an async iterator yielding 

349 :class:`~.Path` objects: 

350 

351 * :meth:`~pathlib.Path.glob` 

352 * :meth:`~pathlib.Path.iterdir` 

353 * :meth:`~pathlib.Path.rglob` 

354 

355 .. versionchanged:: 4.14.0 

356 Added the ``limiter`` keyword argument. 

357 """ 

358 

359 __slots__ = "__weakref__", "_limiter", "_path" 

360 

361 __weakref__: Any 

362 

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 ) 

371 

372 self._path: Final[pathlib.Path] = pathlib.Path(*args) 

373 self._limiter = limiter 

374 

375 def __fspath__(self) -> str: 

376 return self._path.__fspath__() 

377 

378 if sys.version_info >= (3, 15): 

379 

380 def __vfspath__(self) -> str: 

381 return self._path.__vfspath__() 

382 

383 def __str__(self) -> str: 

384 return self._path.__str__() 

385 

386 def __repr__(self) -> str: 

387 return f"{self.__class__.__name__}({self.as_posix()!r})" 

388 

389 def __bytes__(self) -> bytes: 

390 return self._path.__bytes__() 

391 

392 def __hash__(self) -> int: 

393 return self._path.__hash__() 

394 

395 def __eq__(self, other: object) -> bool: 

396 target = other._path if isinstance(other, Path) else other 

397 return self._path.__eq__(target) 

398 

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) 

402 

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) 

406 

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) 

410 

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) 

414 

415 def __truediv__(self, other: str | PathLike[str]) -> Self: 

416 return type(self)(self._path / other, limiter=self._limiter) 

417 

418 def __rtruediv__(self, other: str | PathLike[str]) -> Self: 

419 return type(self)(other, limiter=self._limiter) / self 

420 

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 

425 

426 @property 

427 def parts(self) -> tuple[str, ...]: 

428 return self._path.parts 

429 

430 @property 

431 def drive(self) -> str: 

432 return self._path.drive 

433 

434 @property 

435 def root(self) -> str: 

436 return self._path.root 

437 

438 @property 

439 def anchor(self) -> str: 

440 return self._path.anchor 

441 

442 @property 

443 def parents(self) -> Sequence[Self]: 

444 return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents) 

445 

446 @property 

447 def parent(self) -> Self: 

448 return type(self)(self._path.parent, limiter=self._limiter) 

449 

450 @property 

451 def name(self) -> str: 

452 return self._path.name 

453 

454 @property 

455 def suffix(self) -> str: 

456 return self._path.suffix 

457 

458 @property 

459 def suffixes(self) -> list[str]: 

460 return self._path.suffixes 

461 

462 @property 

463 def stem(self) -> str: 

464 return self._path.stem 

465 

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) 

469 

470 def as_posix(self) -> str: 

471 return self._path.as_posix() 

472 

473 def as_uri(self) -> str: 

474 return self._path.as_uri() 

475 

476 if sys.version_info >= (3, 13): 

477 parser: ClassVar[ModuleType] = pathlib.Path.parser 

478 

479 @classmethod 

480 def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self: 

481 return cls(pathlib.Path.from_uri(uri), limiter=limiter) 

482 

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) 

487 

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: 

493 

494 def match(self, path_pattern: str) -> bool: 

495 return self._path.match(path_pattern) 

496 

497 if sys.version_info >= (3, 14): 

498 

499 @property 

500 def info(self) -> PathInfo: 

501 return self._path.info 

502 

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 ) 

521 

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 ) 

540 

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 ) 

550 

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 ) 

561 

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 

568 

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) 

572 

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) 

577 

578 if sys.version_info >= (3, 12): 

579 

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 ) 

586 

587 else: 

588 

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 ) 

593 

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 ) 

601 

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)) 

635 

636 if sys.version_info >= (3, 13): 

637 

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 ) 

644 

645 else: 

646 

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 ) 

651 

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 

657 

658 await to_thread.run_sync(os.link, target, self, limiter=self._limiter) 

659 

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) 

664 

665 def is_absolute(self) -> bool: 

666 return self._path.is_absolute() 

667 

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 ) 

672 

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 ) 

677 

678 if sys.version_info >= (3, 13): 

679 

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 ) 

686 

687 else: 

688 

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 ) 

693 

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 ) 

698 

699 if sys.version_info >= (3, 13): 

700 

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 ) 

707 

708 else: 

709 

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 ) 

714 

715 if sys.version_info >= (3, 12): 

716 

717 async def is_junction(self) -> bool: 

718 return await to_thread.run_sync( 

719 self._path.is_junction, limiter=self._limiter 

720 ) 

721 

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 ) 

726 

727 if sys.version_info < (3, 15): 

728 

729 def is_reserved(self) -> bool: 

730 return self._path.is_reserved() 

731 

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 ) 

736 

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 ) 

741 

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 

752 

753 def joinpath(self, *args: str | PathLike[str]) -> Self: 

754 return type(self)(self._path.joinpath(*args), limiter=self._limiter) 

755 

756 async def lchmod(self, mode: int) -> None: 

757 await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter) 

758 

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 ) 

763 

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 ) 

770 

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]: ... 

780 

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]: ... 

790 

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) 

809 

810 if sys.version_info >= (3, 13): 

811 

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 ) 

818 

819 else: 

820 

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 ) 

825 

826 async def read_bytes(self) -> bytes: 

827 return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter) 

828 

829 if sys.version_info >= (3, 13): 

830 

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 ) 

844 

845 else: 

846 

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 ) 

853 

854 if sys.version_info >= (3, 12): 

855 

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 ) 

864 

865 else: 

866 

867 def relative_to(self, *other: str | PathLike[str]) -> Self: 

868 return type(self)(self._path.relative_to(*other), limiter=self._limiter) 

869 

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) 

875 

876 async def rename(self, target: str | pathlib.PurePath | Path) -> Self: 

877 if isinstance(target, Path): 

878 target = target._path 

879 

880 await to_thread.run_sync(self._path.rename, target, limiter=self._limiter) 

881 return type(self)(target, limiter=self._limiter) 

882 

883 async def replace(self, target: str | pathlib.PurePath | Path) -> Self: 

884 if isinstance(target, Path): 

885 target = target._path 

886 

887 await to_thread.run_sync(self._path.replace, target, limiter=self._limiter) 

888 return type(self)(target, limiter=self._limiter) 

889 

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 ) 

898 

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)) 

929 

930 async def rmdir(self) -> None: 

931 await to_thread.run_sync(self._path.rmdir, limiter=self._limiter) 

932 

933 async def samefile(self, other_path: str | PathLike[str]) -> bool: 

934 if isinstance(other_path, Path): 

935 other_path = other_path._path 

936 

937 return await to_thread.run_sync( 

938 self._path.samefile, 

939 other_path, 

940 abandon_on_cancel=True, 

941 limiter=self._limiter, 

942 ) 

943 

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 ) 

949 

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 

957 

958 await to_thread.run_sync( 

959 self._path.symlink_to, target, target_is_directory, limiter=self._limiter 

960 ) 

961 

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 ) 

966 

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 

973 

974 if sys.version_info >= (3, 12): 

975 

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 

987 

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 

993 

994 root, dirs, paths = value 

995 yield type(self)(root, limiter=self._limiter), dirs, paths 

996 

997 def with_name(self, name: str) -> Self: 

998 return type(self)(self._path.with_name(name), limiter=self._limiter) 

999 

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: 

1013 

1014 def with_stem(self, stem: str) -> Self: 

1015 return type(self)(self._path.with_stem(stem), limiter=self._limiter) 

1016 

1017 def with_suffix(self, suffix: str) -> Self: 

1018 return type(self)(self._path.with_suffix(suffix), limiter=self._limiter) 

1019 

1020 def with_segments(self, *pathsegments: str | PathLike[str]) -> Self: 

1021 return type(self)(*pathsegments, limiter=self._limiter) 

1022 

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 ) 

1027 

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 ) 

1043 

1044 

1045PathLike.register(Path)