Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/platformdirs/api.py: 55%

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  

1"""Base API.""" 

2 

3from __future__ import annotations 

4 

5import os 

6from abc import ABC, abstractmethod 

7from copy import copy 

8from pathlib import Path 

9from typing import TYPE_CHECKING 

10 

11if TYPE_CHECKING: 

12 from collections.abc import Callable, Iterable, Iterator 

13 from typing import Literal 

14 

15 

16class PlatformDirsABC(ABC): # ruff:ignore[too-many-public-methods] 

17 """Abstract base class defining all platform directory properties, their :class:`~pathlib.Path` variants, and iterators. 

18 

19 Platform-specific subclasses (e.g. :class:`~platformdirs.windows.Windows`, :class:`~platformdirs.macos.MacOS`, 

20 :class:`~platformdirs.unix.Unix`) implement the abstract properties to return the appropriate paths for each 

21 operating system. 

22 

23 """ 

24 

25 def __init__( # ruff:ignore[too-many-arguments, too-many-positional-arguments] 

26 self, 

27 appname: str | None = None, 

28 appauthor: str | Literal[False] | None = None, 

29 version: str | None = None, 

30 roaming: bool = False, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] 

31 multipath: bool = False, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] 

32 opinion: bool = True, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] 

33 ensure_exists: bool = False, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] 

34 use_site_for_root: bool = False, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] 

35 ) -> None: 

36 """Create a new platform directory. 

37 

38 :param appname: See `appname`. 

39 :param appauthor: See `appauthor`. 

40 :param version: See `version`. 

41 :param roaming: See `roaming`. 

42 :param multipath: See `multipath`. 

43 :param opinion: See `opinion`. 

44 :param ensure_exists: See `ensure_exists`. 

45 :param use_site_for_root: See `use_site_for_root`. 

46 

47 """ 

48 self.appname = appname 

49 self.appauthor = appauthor 

50 self.version = version 

51 self.roaming = roaming 

52 """Whether to use the roaming appdata directory on Windows. 

53 

54 That means that for users on a Windows network setup for roaming profiles, this user data will be synced on 

55 login (see `here <https://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_). 

56 

57 """ 

58 self.multipath = multipath 

59 """An optional parameter which indicates that the entire list of data dirs should be returned. 

60 

61 When ``False``, only the first item is returned. Affects ``site_data_dir``, ``site_config_dir`` and 

62 ``site_applications_dir`` on Unix and macOS, and ``site_cache_dir`` on macOS under Homebrew. All but 

63 ``site_applications_dir`` raise :class:`ValueError` under it when `appname` or `version` contains 

64 :data:`os.pathsep`. 

65 

66 """ 

67 self.opinion = opinion 

68 """Whether to use opinionated values. 

69 

70 When enabled, appends an additional subdirectory for certain directories: e.g. ``Cache`` for cache and ``Logs`` 

71 for logs on Windows, ``log`` for logs on Unix. 

72 

73 """ 

74 self.ensure_exists = ensure_exists 

75 """Create the directory a property returns, with missing parents, each time the property is read. 

76 

77 Media directories qualify only when the user set them through XDG. platformdirs never creates fonts, bin, 

78 applications or default media directories. Off by default. 

79 

80 """ 

81 self.use_site_for_root = use_site_for_root 

82 """Whether to redirect ``user_*_dir`` calls to their ``site_*_dir`` equivalents when the effective uid is 0. 

83 

84 Only has an effect on Unix. Disabled by default for backwards compatibility. When enabled, XDG user environment 

85 variables (e.g. ``XDG_DATA_HOME``) are bypassed for the redirected directories. 

86 

87 """ 

88 

89 @property 

90 def appname(self) -> str | None: 

91 """The name of the application.""" 

92 return self._appname 

93 

94 @appname.setter 

95 def appname(self, value: str | None) -> None: 

96 _ensure_inside_base("appname", value) 

97 self._appname = value 

98 

99 @property 

100 def appauthor(self) -> str | Literal[False] | None: 

101 """The name of the app author or distributing body for this application. 

102 

103 Typically, it is the owning company name. Defaults to `appname`. You may pass ``False`` to disable it. 

104 

105 .. note:: 

106 

107 On Windows, the directory structure is ``<base>/<appauthor>/<appname>``. When ``appauthor`` is ``None`` (the 

108 default), it falls back to ``appname``, resulting in ``<base>/<appname>/<appname>`` (e.g. 

109 ``AppData/Local/myapp/myapp``). Pass ``appauthor=False`` to omit the author directory entirely and get 

110 ``<base>/<appname>``. 

111 

112 """ 

113 return self._appauthor 

114 

115 @appauthor.setter 

116 def appauthor(self, value: str | Literal[False] | None) -> None: 

117 _ensure_inside_base("appauthor", value) 

118 self._appauthor = value 

119 

120 @property 

121 def version(self) -> str | None: 

122 """An optional version path element to append to the path. 

123 

124 You might want to use this if you want multiple versions of your app to be able to run independently. If used, 

125 this would typically be ``<major>.<minor>``. 

126 

127 """ 

128 return self._version 

129 

130 @version.setter 

131 def version(self, value: str | None) -> None: 

132 _ensure_inside_base("version", value) 

133 self._version = value 

134 

135 def _append_app_name_and_version(self, *base: str, private: bool) -> str: 

136 path = self._join_app_name_and_version(*base) 

137 self._optionally_create_directory(path, private=private) 

138 return path 

139 

140 def _join_app_name_and_version(self, *base: str) -> str: 

141 params = list(base[1:]) 

142 if self.appname: 

143 params.append(self.appname) 

144 if self.version: 

145 params.append(self.version) 

146 return os.path.join(base[0], *params) # ruff:ignore[os-path-join] 

147 

148 def _optionally_create_directory(self, path: str, *, private: bool) -> None: 

149 if not self.ensure_exists: 

150 return 

151 _ensure_home_known(path) 

152 if private: 

153 _make_private_directories(Path(path)) 

154 else: 

155 Path(path).mkdir(parents=True, exist_ok=True) 

156 

157 def _optionally_create_media_directory(self, path: str) -> None: 

158 # Only called for a media directory the user configured. Media directories are often symlinks to removable or 

159 # network storage, so a dangling link is returned as-is, like before ensure_exists covered them. 

160 try: 

161 self._optionally_create_directory(path, private=False) 

162 except FileExistsError: 

163 if not (directory := Path(path)).is_symlink() or directory.exists(): 

164 raise 

165 

166 def _select_site_dirs(self, dirs: list[str]) -> str: 

167 # Site lists are built without touching the disk, so ensure_exists only creates what the caller gets back. 

168 selected = dirs if self.multipath else dirs[:1] 

169 if self.multipath and any(os.pathsep in path for path in selected): 

170 # Callers split the joined string on os.pathsep, so a separator inside one entry reads as two directories. 

171 msg = f"multipath cannot join site directories that contain {os.pathsep!r}, got {selected!r}" 

172 raise ValueError(msg) 

173 for path in selected: 

174 self._optionally_create_directory(path, private=False) 

175 return os.pathsep.join(selected) 

176 

177 def _create_as_yielded(self, dirs: Iterable[str]) -> Iterator[str]: 

178 for path in dirs: 

179 self._optionally_create_directory(path, private=False) 

180 yield path 

181 

182 def _first_site_dir_as_path(self, dirs: list[str]) -> Path: 

183 # A *_path property always returns the first entry, regardless of multipath, so only that entry is created. 

184 path = dirs[0] 

185 self._optionally_create_directory(path, private=False) 

186 return Path(path) 

187 

188 @property 

189 @abstractmethod 

190 def user_data_dir(self) -> str: 

191 """Data directory tied to the user.""" 

192 

193 @property 

194 @abstractmethod 

195 def site_data_dir(self) -> str: 

196 """Data directory shared by users.""" 

197 

198 @property 

199 def _site_data_dirs(self) -> list[str]: 

200 return [self.site_data_dir] 

201 

202 @property 

203 @abstractmethod 

204 def user_config_dir(self) -> str: 

205 """Config directory tied to the user.""" 

206 

207 @property 

208 @abstractmethod 

209 def site_config_dir(self) -> str: 

210 """Config directory shared by users.""" 

211 

212 @property 

213 def _site_config_dirs(self) -> list[str]: 

214 return [self.site_config_dir] 

215 

216 @property 

217 @abstractmethod 

218 def user_cache_dir(self) -> str: 

219 """Cache directory tied to the user.""" 

220 

221 @property 

222 @abstractmethod 

223 def site_cache_dir(self) -> str: 

224 """Cache directory shared by users.""" 

225 

226 @property 

227 def _site_cache_dirs(self) -> list[str]: 

228 return [self.site_cache_dir] 

229 

230 @property 

231 @abstractmethod 

232 def user_state_dir(self) -> str: 

233 """State directory tied to the user.""" 

234 

235 @property 

236 @abstractmethod 

237 def site_state_dir(self) -> str: 

238 """State directory shared by users.""" 

239 

240 @property 

241 @abstractmethod 

242 def user_log_dir(self) -> str: 

243 """Log directory tied to the user.""" 

244 

245 @property 

246 @abstractmethod 

247 def site_log_dir(self) -> str: 

248 """Log directory shared by users.""" 

249 

250 @property 

251 @abstractmethod 

252 def user_documents_dir(self) -> str: 

253 """Documents directory tied to the user.""" 

254 

255 @property 

256 @abstractmethod 

257 def user_downloads_dir(self) -> str: 

258 """Downloads directory tied to the user.""" 

259 

260 @property 

261 @abstractmethod 

262 def user_pictures_dir(self) -> str: 

263 """Pictures directory tied to the user.""" 

264 

265 @property 

266 @abstractmethod 

267 def user_videos_dir(self) -> str: 

268 """Videos directory tied to the user.""" 

269 

270 @property 

271 @abstractmethod 

272 def user_music_dir(self) -> str: 

273 """Music directory tied to the user.""" 

274 

275 @property 

276 @abstractmethod 

277 def user_desktop_dir(self) -> str: 

278 """Desktop directory tied to the user.""" 

279 

280 @property 

281 @abstractmethod 

282 def user_projects_dir(self) -> str: 

283 """Projects directory tied to the user.""" 

284 

285 @property 

286 @abstractmethod 

287 def user_publicshare_dir(self) -> str: 

288 """Public share directory tied to the user.""" 

289 

290 @property 

291 @abstractmethod 

292 def user_templates_dir(self) -> str: 

293 """Templates directory tied to the user.""" 

294 

295 @property 

296 @abstractmethod 

297 def user_fonts_dir(self) -> str: 

298 """Fonts directory tied to the user.""" 

299 

300 @property 

301 @abstractmethod 

302 def user_preference_dir(self) -> str: 

303 """Preference directory tied to the user.""" 

304 

305 @property 

306 @abstractmethod 

307 def user_bin_dir(self) -> str: 

308 """Bin directory tied to the user.""" 

309 

310 @property 

311 @abstractmethod 

312 def site_bin_dir(self) -> str: 

313 """Bin directory shared by users.""" 

314 

315 @property 

316 @abstractmethod 

317 def user_applications_dir(self) -> str: 

318 """Applications directory tied to the user.""" 

319 

320 @property 

321 @abstractmethod 

322 def site_applications_dir(self) -> str: 

323 """Applications directory shared by users.""" 

324 

325 @property 

326 def _site_applications_dirs(self) -> list[str]: 

327 return [self.site_applications_dir] 

328 

329 @property 

330 @abstractmethod 

331 def user_runtime_dir(self) -> str: 

332 """Runtime directory tied to the user.""" 

333 

334 @property 

335 @abstractmethod 

336 def site_runtime_dir(self) -> str: 

337 """Runtime directory shared by users.""" 

338 

339 @property 

340 def user_data_path(self) -> Path: 

341 """Data path tied to the user.""" 

342 return Path(self.user_data_dir) 

343 

344 @property 

345 def site_data_path(self) -> Path: 

346 """Data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``.""" 

347 return self._first_site_dir_as_path(self._site_data_dirs) 

348 

349 @property 

350 def user_config_path(self) -> Path: 

351 """Config path tied to the user.""" 

352 return Path(self.user_config_dir) 

353 

354 @property 

355 def site_config_path(self) -> Path: 

356 """Config path shared by users. Only return the first item, even if ``multipath`` is set to ``True``.""" 

357 return self._first_site_dir_as_path(self._site_config_dirs) 

358 

359 @property 

360 def user_cache_path(self) -> Path: 

361 """Cache path tied to the user.""" 

362 return Path(self.user_cache_dir) 

363 

364 @property 

365 def site_cache_path(self) -> Path: 

366 """Cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``.""" 

367 return self._first_site_dir_as_path(self._site_cache_dirs) 

368 

369 @property 

370 def user_state_path(self) -> Path: 

371 """State path tied to the user.""" 

372 return Path(self.user_state_dir) 

373 

374 @property 

375 def site_state_path(self) -> Path: 

376 """State path shared by users.""" 

377 return Path(self.site_state_dir) 

378 

379 @property 

380 def user_log_path(self) -> Path: 

381 """Log path tied to the user.""" 

382 return Path(self.user_log_dir) 

383 

384 @property 

385 def site_log_path(self) -> Path: 

386 """Log path shared by users.""" 

387 return Path(self.site_log_dir) 

388 

389 @property 

390 def user_documents_path(self) -> Path: 

391 """Documents path tied to the user.""" 

392 return Path(self.user_documents_dir) 

393 

394 @property 

395 def user_downloads_path(self) -> Path: 

396 """Downloads path tied to the user.""" 

397 return Path(self.user_downloads_dir) 

398 

399 @property 

400 def user_pictures_path(self) -> Path: 

401 """Pictures path tied to the user.""" 

402 return Path(self.user_pictures_dir) 

403 

404 @property 

405 def user_videos_path(self) -> Path: 

406 """Videos path tied to the user.""" 

407 return Path(self.user_videos_dir) 

408 

409 @property 

410 def user_music_path(self) -> Path: 

411 """Music path tied to the user.""" 

412 return Path(self.user_music_dir) 

413 

414 @property 

415 def user_desktop_path(self) -> Path: 

416 """Desktop path tied to the user.""" 

417 return Path(self.user_desktop_dir) 

418 

419 @property 

420 def user_projects_path(self) -> Path: 

421 """Projects path tied to the user.""" 

422 return Path(self.user_projects_dir) 

423 

424 @property 

425 def user_publicshare_path(self) -> Path: 

426 """Public share path tied to the user.""" 

427 return Path(self.user_publicshare_dir) 

428 

429 @property 

430 def user_templates_path(self) -> Path: 

431 """Templates path tied to the user.""" 

432 return Path(self.user_templates_dir) 

433 

434 @property 

435 def user_fonts_path(self) -> Path: 

436 """Fonts path tied to the user.""" 

437 return Path(self.user_fonts_dir) 

438 

439 @property 

440 def user_preference_path(self) -> Path: 

441 """Preference path tied to the user.""" 

442 return Path(self.user_preference_dir) 

443 

444 @property 

445 def user_bin_path(self) -> Path: 

446 """Bin path tied to the user.""" 

447 return Path(self.user_bin_dir) 

448 

449 @property 

450 def site_bin_path(self) -> Path: 

451 """Bin path shared by users.""" 

452 return Path(self.site_bin_dir) 

453 

454 @property 

455 def user_applications_path(self) -> Path: 

456 """Applications path tied to the user.""" 

457 return Path(self.user_applications_dir) 

458 

459 @property 

460 def site_applications_path(self) -> Path: 

461 """Applications path shared by users. Only return the first item, even if ``multipath`` is set to ``True``.""" 

462 return Path(self._site_applications_dirs[0]) 

463 

464 @property 

465 def user_runtime_path(self) -> Path: 

466 """Runtime path tied to the user.""" 

467 return Path(self.user_runtime_dir) 

468 

469 @property 

470 def site_runtime_path(self) -> Path: 

471 """Runtime path shared by users.""" 

472 return Path(self.site_runtime_dir) 

473 

474 def iter_config_dirs(self) -> Iterator[str]: 

475 """:yield: all user and site configuration directories.""" 

476 yield from _unique(self._iter_config_dirs()) 

477 

478 def _iter_config_dirs(self) -> Iterator[str]: 

479 if not self._use_site: 

480 yield self.user_config_dir 

481 yield from self._create_as_yielded(self._site_config_dirs) 

482 

483 def iter_data_dirs(self) -> Iterator[str]: 

484 """:yield: all user and site data directories.""" 

485 yield from _unique(self._iter_data_dirs()) 

486 

487 def _iter_data_dirs(self) -> Iterator[str]: 

488 if not self._use_site: 

489 yield self.user_data_dir 

490 yield from self._create_as_yielded(self._site_data_dirs) 

491 

492 def iter_cache_dirs(self) -> Iterator[str]: 

493 """:yield: all user and site cache directories.""" 

494 yield from _unique(self._iter_cache_dirs()) 

495 

496 def _iter_cache_dirs(self) -> Iterator[str]: 

497 if not self._use_site: 

498 yield self.user_cache_dir 

499 yield from self._create_as_yielded(self._site_cache_dirs) 

500 

501 def iter_state_dirs(self) -> Iterator[str]: 

502 """:yield: all user and site state directories.""" 

503 yield from _unique(self._iter_state_dirs()) 

504 

505 def _iter_state_dirs(self) -> Iterator[str]: 

506 if not self._use_site: 

507 yield self.user_state_dir 

508 yield self.site_state_dir 

509 

510 def iter_log_dirs(self) -> Iterator[str]: 

511 """:yield: all user and site log directories.""" 

512 yield from _unique(self._iter_log_dirs()) 

513 

514 def _iter_log_dirs(self) -> Iterator[str]: 

515 if not self._use_site: 

516 yield self.user_log_dir 

517 yield self.site_log_dir 

518 

519 def iter_runtime_dirs(self) -> Iterator[str]: 

520 """:yield: all user and site runtime directories.""" 

521 yield from _unique(self._iter_runtime_dirs()) 

522 

523 def _iter_runtime_dirs(self) -> Iterator[str]: 

524 yield self.user_runtime_dir 

525 # Root's user runtime dir is already the site one; site_runtime_dir reads the invoking user's XDG_RUNTIME_DIR. 

526 if not self._use_site: 

527 yield self.site_runtime_dir 

528 

529 @property 

530 def _use_site(self) -> bool: 

531 # Only Unix redirects root's user dirs to the site ones. The iterators then skip the user dir, since under 

532 # multipath it is the joined site string that _unique cannot match against any single entry. 

533 return False 

534 

535 def iter_config_paths(self) -> Iterator[Path]: 

536 """:yield: all user and site configuration paths.""" 

537 for path in self.iter_config_dirs(): 

538 yield Path(path) 

539 

540 def iter_data_paths(self) -> Iterator[Path]: 

541 """:yield: all user and site data paths.""" 

542 for path in self.iter_data_dirs(): 

543 yield Path(path) 

544 

545 def iter_cache_paths(self) -> Iterator[Path]: 

546 """:yield: all user and site cache paths.""" 

547 for path in self.iter_cache_dirs(): 

548 yield Path(path) 

549 

550 def iter_state_paths(self) -> Iterator[Path]: 

551 """:yield: all user and site state paths.""" 

552 for path in self.iter_state_dirs(): 

553 yield Path(path) 

554 

555 def iter_log_paths(self) -> Iterator[Path]: 

556 """:yield: all user and site log paths.""" 

557 for path in self.iter_log_dirs(): 

558 yield Path(path) 

559 

560 def iter_runtime_paths(self) -> Iterator[Path]: 

561 """:yield: all user and site runtime paths.""" 

562 for path in self.iter_runtime_dirs(): 

563 yield Path(path) 

564 

565 def place_config_file(self, name: str | os.PathLike[str]) -> Path: 

566 """Return the path of ``name`` under `user_config_path`, creating the missing directories on the way. 

567 

568 Each directory it creates, including the user directory itself, gets mode ``0o700`` whether or not 

569 `ensure_exists` is set; directories that already exist keep their mode. On Unix, root with `use_site_for_root` 

570 places the file under the site directory instead and creates the missing directories with the default mode, so 

571 other users can read them. It does not create the file. 

572 

573 :param name: file path relative to the user directory, e.g. ``"sub/app.toml"``. 

574 

575 :raises ValueError: if ``name`` is empty, absolute, has a drive or climbs out through ``..``. 

576 :raises RuntimeError: if the user directory starts with ``~`` because the home directory is unknown. 

577 :raises OSError: if it cannot create a missing directory, e.g. one on a missing drive or network share. 

578 

579 """ 

580 return self._place_file(lambda dirs: dirs.user_config_path, name) 

581 

582 def place_data_file(self, name: str | os.PathLike[str]) -> Path: 

583 """Like `place_config_file`, under `user_data_path`.""" 

584 return self._place_file(lambda dirs: dirs.user_data_path, name) 

585 

586 def place_cache_file(self, name: str | os.PathLike[str]) -> Path: 

587 """Like `place_config_file`, under `user_cache_path`.""" 

588 return self._place_file(lambda dirs: dirs.user_cache_path, name) 

589 

590 def place_state_file(self, name: str | os.PathLike[str]) -> Path: 

591 """Like `place_config_file`, under `user_state_path`.""" 

592 return self._place_file(lambda dirs: dirs.user_state_path, name) 

593 

594 def place_log_file(self, name: str | os.PathLike[str]) -> Path: 

595 """Like `place_config_file`, under `user_log_path`.""" 

596 return self._place_file(lambda dirs: dirs.user_log_path, name) 

597 

598 def place_runtime_file(self, name: str | os.PathLike[str]) -> Path: 

599 """Like `place_config_file`, under `user_runtime_path`. 

600 

601 :raises PermissionError: on Unix, if another user owns the temporary fallback directory. 

602 

603 """ 

604 return self._place_file(lambda dirs: dirs.user_runtime_path, name) 

605 

606 def _place_file(self, user_path: Callable[[PlatformDirsABC], Path], name: str | os.PathLike[str]) -> Path: 

607 _ensure_inside_base("name", relative := os.fspath(name)) 

608 if not Path(relative).parts: 

609 msg = f"name must point to a file, got {relative!r}" 

610 raise ValueError(msg) 

611 # Under ensure_exists the lookup itself would create the user directory with the default mode, not 0o700. 

612 (lookup := copy(self)).ensure_exists = False 

613 # Under multipath *_dir joins the site directories with os.pathsep, so place under the single *_path. 

614 _ensure_home_known(str(directory := user_path(lookup))) 

615 path = directory / relative 

616 if self._use_site: 

617 # Site directories keep the default mode, as under ensure_exists, so other users can still read them. 

618 path.parent.mkdir(parents=True, exist_ok=True) 

619 else: 

620 _make_private_directories(path.parent) 

621 return path 

622 

623 def find_config_file(self, name: str | os.PathLike[str]) -> Path | None: 

624 """Find the first ``name`` file in :meth:`iter_config_paths` order, without creating any directory. 

625 

626 :param name: path relative to each directory, may contain subdirectories. 

627 

628 :returns: the file, or ``None`` when no directory holds one. 

629 

630 :raises ValueError: if ``name`` is absolute, has a drive or climbs out with ``..``. 

631 

632 """ 

633 return next(self._find_files(lambda dirs: dirs.iter_config_paths(), name), None) 

634 

635 def find_config_files(self, name: str | os.PathLike[str]) -> list[Path]: 

636 """Find every ``name`` file in :meth:`iter_config_paths` order, without creating any directory. 

637 

638 :param name: see :meth:`find_config_file`. 

639 

640 :returns: the files, user directory first. 

641 

642 :raises ValueError: see :meth:`find_config_file`. 

643 

644 """ 

645 return list(self._find_files(lambda dirs: dirs.iter_config_paths(), name)) 

646 

647 def find_data_file(self, name: str | os.PathLike[str]) -> Path | None: 

648 """Like :meth:`find_config_file`, searching :meth:`iter_data_paths`.""" 

649 return next(self._find_files(lambda dirs: dirs.iter_data_paths(), name), None) 

650 

651 def find_data_files(self, name: str | os.PathLike[str]) -> list[Path]: 

652 """Like :meth:`find_config_files`, searching :meth:`iter_data_paths`.""" 

653 return list(self._find_files(lambda dirs: dirs.iter_data_paths(), name)) 

654 

655 def find_cache_file(self, name: str | os.PathLike[str]) -> Path | None: 

656 """Like :meth:`find_config_file`, searching :meth:`iter_cache_paths`.""" 

657 return next(self._find_files(lambda dirs: dirs.iter_cache_paths(), name), None) 

658 

659 def find_cache_files(self, name: str | os.PathLike[str]) -> list[Path]: 

660 """Like :meth:`find_config_files`, searching :meth:`iter_cache_paths`.""" 

661 return list(self._find_files(lambda dirs: dirs.iter_cache_paths(), name)) 

662 

663 def find_state_file(self, name: str | os.PathLike[str]) -> Path | None: 

664 """Like :meth:`find_config_file`, searching :meth:`iter_state_paths`.""" 

665 return next(self._find_files(lambda dirs: dirs.iter_state_paths(), name), None) 

666 

667 def find_state_files(self, name: str | os.PathLike[str]) -> list[Path]: 

668 """Like :meth:`find_config_files`, searching :meth:`iter_state_paths`.""" 

669 return list(self._find_files(lambda dirs: dirs.iter_state_paths(), name)) 

670 

671 def find_log_file(self, name: str | os.PathLike[str]) -> Path | None: 

672 """Like :meth:`find_config_file`, searching :meth:`iter_log_paths`.""" 

673 return next(self._find_files(lambda dirs: dirs.iter_log_paths(), name), None) 

674 

675 def find_log_files(self, name: str | os.PathLike[str]) -> list[Path]: 

676 """Like :meth:`find_config_files`, searching :meth:`iter_log_paths`.""" 

677 return list(self._find_files(lambda dirs: dirs.iter_log_paths(), name)) 

678 

679 def find_runtime_file(self, name: str | os.PathLike[str]) -> Path | None: 

680 """Like :meth:`find_config_file`, searching :meth:`iter_runtime_paths`.""" 

681 return next(self._find_runtime_files(name), None) 

682 

683 def find_runtime_files(self, name: str | os.PathLike[str]) -> list[Path]: 

684 """Like :meth:`find_config_files`, searching :meth:`iter_runtime_paths`.""" 

685 return list(self._find_runtime_files(name)) 

686 

687 def _find_runtime_files(self, name: str | os.PathLike[str]) -> Iterator[Path]: 

688 return self._find_files(lambda dirs: dirs.iter_runtime_paths(), name) 

689 

690 def _find_files( 

691 self, iter_paths: Callable[[PlatformDirsABC], Iterator[Path]], name: str | os.PathLike[str] 

692 ) -> Iterator[Path]: 

693 _ensure_inside_base("name", relative := os.fspath(name)) 

694 # The iterators create each directory they yield under ensure_exists, and a lookup must only read the disk. 

695 (lookup := copy(self)).ensure_exists = False 

696 return (path for directory in iter_paths(lookup) if (path := directory / relative).is_file()) 

697 

698 

699def _ensure_inside_base(name: str, value: str | Literal[False] | None) -> None: 

700 if not value: 

701 return 

702 # os.path.join discards the base when a later component has a root or a drive, and ``..`` climbs out of it. 

703 drive, tail = os.path.splitdrive(value) 

704 if drive or tail.startswith(("/", "\\")) or ".." in tail.replace("\\", "/").split("/"): 

705 msg = f"{name} must stay inside the base directory, got {value!r}" 

706 raise ValueError(msg) 

707 

708 

709def _ensure_home_known(path: str) -> None: 

710 # expanduser leaves "~" when the home is unknown, and creating it would make a "~" directory in the cwd. 

711 if path.startswith("~"): 

712 msg = f"could not determine the home directory, refusing to create {path!r}" 

713 raise RuntimeError(msg) 

714 

715 

716def _unique(dirs: Iterable[str]) -> Iterator[str]: 

717 """:yield: ``dirs`` in order, skipping any directory already yielded.""" 

718 # Lazy on purpose: under ensure_exists reading a site_*_dir creates it, so draining ``dirs`` up front would 

719 # create directories for a caller that stops after the first entry. 

720 seen: set[Path] = set() 

721 for path in dirs: 

722 if (normalized := Path(path)) not in seen: 

723 seen.add(normalized) 

724 yield path 

725 

726 

727def _make_private_directories(path: Path) -> None: 

728 # XDG asks for 0o700 on each directory created on the way, while Path.mkdir(parents=True) applies it to the last. 

729 try: 

730 path.mkdir(mode=0o700, exist_ok=True) 

731 except FileNotFoundError: 

732 if path.parent == path: # a missing drive or network share has no parent to create 

733 raise 

734 _make_private_directories(path.parent) 

735 path.mkdir(mode=0o700, exist_ok=True) 

736 

737 

738class RuntimeDirWarning(UserWarning): 

739 """Emitted when the Unix or macOS ``user_runtime_dir`` cannot use ``$XDG_RUNTIME_DIR``."""