platformdirs 4.12.2__tar.gz → 4.12.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {platformdirs-4.12.2 → platformdirs-4.12.4}/PKG-INFO +1 -1
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/api.py +24 -16
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/testing.py +2 -2
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/unix.py +1 -1
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/version.py +2 -2
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_api.py +13 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_unix.py +30 -4
- {platformdirs-4.12.2 → platformdirs-4.12.4}/.gitignore +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/LICENSE +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/README.md +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/pyproject.toml +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/__init__.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/__main__.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/_xdg.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/android.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/ios.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/macos.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/py.typed +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/pytest_plugin.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/src/platformdirs/windows.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/conftest.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_android.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_comp_with_appdirs.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_ios.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_macos.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_main.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_pytest_plugin.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_testing.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tests/test_windows.py +0 -0
- {platformdirs-4.12.2 → platformdirs-4.12.4}/tox.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: platformdirs
|
|
3
|
-
Version: 4.12.
|
|
3
|
+
Version: 4.12.4
|
|
4
4
|
Summary: A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`.
|
|
5
5
|
Project-URL: Changelog, https://platformdirs.readthedocs.io/en/latest/changelog.html
|
|
6
6
|
Project-URL: Documentation, https://platformdirs.readthedocs.io
|
|
@@ -555,10 +555,12 @@ class PlatformDirsABC(ABC): # ruff:ignore[too-many-public-methods]
|
|
|
555
555
|
yield Path(path)
|
|
556
556
|
|
|
557
557
|
def place_config_file(self, name: str | os.PathLike[str]) -> Path:
|
|
558
|
-
"""Return the path of ``name`` under `
|
|
558
|
+
"""Return the path of ``name`` under `user_config_path`, creating the missing directories on the way.
|
|
559
559
|
|
|
560
560
|
Each directory it creates, including the user directory itself, gets mode ``0o700`` whether or not
|
|
561
|
-
`ensure_exists` is set; directories that already exist keep their mode.
|
|
561
|
+
`ensure_exists` is set; directories that already exist keep their mode. On Unix, root with `use_site_for_root`
|
|
562
|
+
places the file under the site directory instead and creates the missing directories with the default mode, so
|
|
563
|
+
other users can read them. It does not create the file.
|
|
562
564
|
|
|
563
565
|
:param name: file path relative to the user directory, e.g. ``"sub/app.toml"``.
|
|
564
566
|
|
|
@@ -566,41 +568,47 @@ class PlatformDirsABC(ABC): # ruff:ignore[too-many-public-methods]
|
|
|
566
568
|
:raises RuntimeError: if the user directory starts with ``~`` because the home directory is unknown.
|
|
567
569
|
|
|
568
570
|
"""
|
|
569
|
-
return self._place_file(lambda dirs: dirs.
|
|
571
|
+
return self._place_file(lambda dirs: dirs.user_config_path, name)
|
|
570
572
|
|
|
571
573
|
def place_data_file(self, name: str | os.PathLike[str]) -> Path:
|
|
572
|
-
"""Like `place_config_file`, under `
|
|
573
|
-
return self._place_file(lambda dirs: dirs.
|
|
574
|
+
"""Like `place_config_file`, under `user_data_path`."""
|
|
575
|
+
return self._place_file(lambda dirs: dirs.user_data_path, name)
|
|
574
576
|
|
|
575
577
|
def place_cache_file(self, name: str | os.PathLike[str]) -> Path:
|
|
576
|
-
"""Like `place_config_file`, under `
|
|
577
|
-
return self._place_file(lambda dirs: dirs.
|
|
578
|
+
"""Like `place_config_file`, under `user_cache_path`."""
|
|
579
|
+
return self._place_file(lambda dirs: dirs.user_cache_path, name)
|
|
578
580
|
|
|
579
581
|
def place_state_file(self, name: str | os.PathLike[str]) -> Path:
|
|
580
|
-
"""Like `place_config_file`, under `
|
|
581
|
-
return self._place_file(lambda dirs: dirs.
|
|
582
|
+
"""Like `place_config_file`, under `user_state_path`."""
|
|
583
|
+
return self._place_file(lambda dirs: dirs.user_state_path, name)
|
|
582
584
|
|
|
583
585
|
def place_log_file(self, name: str | os.PathLike[str]) -> Path:
|
|
584
|
-
"""Like `place_config_file`, under `
|
|
585
|
-
return self._place_file(lambda dirs: dirs.
|
|
586
|
+
"""Like `place_config_file`, under `user_log_path`."""
|
|
587
|
+
return self._place_file(lambda dirs: dirs.user_log_path, name)
|
|
586
588
|
|
|
587
589
|
def place_runtime_file(self, name: str | os.PathLike[str]) -> Path:
|
|
588
|
-
"""Like `place_config_file`, under `
|
|
590
|
+
"""Like `place_config_file`, under `user_runtime_path`.
|
|
589
591
|
|
|
590
592
|
:raises PermissionError: on Unix, if another user owns the temporary fallback directory.
|
|
591
593
|
|
|
592
594
|
"""
|
|
593
|
-
return self._place_file(lambda dirs: dirs.
|
|
595
|
+
return self._place_file(lambda dirs: dirs.user_runtime_path, name)
|
|
594
596
|
|
|
595
|
-
def _place_file(self,
|
|
597
|
+
def _place_file(self, user_path: Callable[[PlatformDirsABC], Path], name: str | os.PathLike[str]) -> Path:
|
|
596
598
|
_ensure_inside_base("name", relative := os.fspath(name))
|
|
597
599
|
if not Path(relative).parts:
|
|
598
600
|
msg = f"name must point to a file, got {relative!r}"
|
|
599
601
|
raise ValueError(msg)
|
|
600
602
|
# Under ensure_exists the lookup itself would create the user directory with the default mode, not 0o700.
|
|
601
603
|
(lookup := copy(self)).ensure_exists = False
|
|
602
|
-
|
|
603
|
-
|
|
604
|
+
# Under multipath *_dir joins the site directories with os.pathsep, so place under the single *_path.
|
|
605
|
+
_ensure_home_known(str(directory := user_path(lookup)))
|
|
606
|
+
path = directory / relative
|
|
607
|
+
if self._use_site:
|
|
608
|
+
# Site directories keep the default mode, as under ensure_exists, so other users can still read them.
|
|
609
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
610
|
+
else:
|
|
611
|
+
_make_private_directories(path.parent)
|
|
604
612
|
return path
|
|
605
613
|
|
|
606
614
|
def find_config_file(self, name: str | os.PathLike[str]) -> Path | None:
|
|
@@ -11,7 +11,7 @@ from platformdirs.api import PlatformDirsABC
|
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
import os
|
|
14
|
-
from collections.abc import
|
|
14
|
+
from collections.abc import Generator
|
|
15
15
|
|
|
16
16
|
_APP_KINDS: Final[frozenset[str]] = frozenset({
|
|
17
17
|
"user_data",
|
|
@@ -31,7 +31,7 @@ _APP_KINDS: Final[frozenset[str]] = frozenset({
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@contextmanager
|
|
34
|
-
def isolated_dirs(root: str | os.PathLike[str]) ->
|
|
34
|
+
def isolated_dirs(root: str | os.PathLike[str]) -> Generator[Path, None, None]:
|
|
35
35
|
"""Resolve every directory of :data:`~platformdirs.PlatformDirs` under ``root`` while the context is active.
|
|
36
36
|
|
|
37
37
|
Each kind maps to ``<root>/<kind>``, where ``kind`` is the property name without ``_dir``, such as ``user_config``.
|
|
@@ -275,7 +275,7 @@ class Unix(XDGMixin, _UnixDefaults):
|
|
|
275
275
|
if not (path := _xdg_dir("XDG_RUNTIME_DIR")):
|
|
276
276
|
reason = "XDG_RUNTIME_DIR is not set"
|
|
277
277
|
else:
|
|
278
|
-
with suppress(
|
|
278
|
+
with suppress(OSError):
|
|
279
279
|
self._optionally_create_directory(path, private=True)
|
|
280
280
|
if not (problem := _runtime_dir_problem(path)):
|
|
281
281
|
return self._append_app_name_and_version(path, private=True)
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '4.12.
|
|
22
|
-
__version_tuple__ = version_tuple = (4, 12,
|
|
21
|
+
__version__ = version = '4.12.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (4, 12, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -475,6 +475,19 @@ def test_find_config_file_accepts_nested_path_like(dirs: Unix, config_paths: lis
|
|
|
475
475
|
assert dirs.find_config_file(Path("sub", "app.toml")) == nested
|
|
476
476
|
|
|
477
477
|
|
|
478
|
+
@pytest.mark.parametrize("multipath", [pytest.param(True, id="multipath"), pytest.param(False, id="single-path")])
|
|
479
|
+
@pytest.mark.parametrize("kind", [pytest.param(kind, id=kind) for kind in ("config", "data")])
|
|
480
|
+
def test_find_file_finds_root_placed_file_in_first_site_dir(
|
|
481
|
+
dirs: Unix, mocker: MockerFixture, kind: str, multipath: bool
|
|
482
|
+
) -> None:
|
|
483
|
+
mocker.patch("platformdirs.unix.getuid", return_value=0)
|
|
484
|
+
dirs.use_site_for_root = True
|
|
485
|
+
dirs.multipath = multipath
|
|
486
|
+
getattr(dirs, f"place_{kind}_file")("app.toml").touch()
|
|
487
|
+
first_site_dir = os.environ[f"XDG_{kind.upper()}_DIRS"].split(os.pathsep)[0]
|
|
488
|
+
assert getattr(dirs, f"find_{kind}_file")("app.toml") == Path(first_site_dir, "find-files-test", "app.toml")
|
|
489
|
+
|
|
490
|
+
|
|
478
491
|
@pytest.mark.parametrize("suffix", _SUFFIXES)
|
|
479
492
|
@pytest.mark.parametrize("kind", _KINDS)
|
|
480
493
|
def test_find_creates_no_directory_with_ensure_exists(dirs: Unix, tmp_path: Path, kind: str, suffix: str) -> None:
|
|
@@ -400,6 +400,10 @@ _PROBLEMS: typing.Final = {
|
|
|
400
400
|
"mode-755": "has mode 0755, not 0700",
|
|
401
401
|
"mode-500": "has mode 0500, not 0700",
|
|
402
402
|
}
|
|
403
|
+
_UNREACHABLE: typing.Final = {
|
|
404
|
+
"file": "Not a directory",
|
|
405
|
+
"symlink-loop": "Too many levels of symbolic links",
|
|
406
|
+
}
|
|
403
407
|
_DEFAULT_PREFIXES: typing.Final = {
|
|
404
408
|
"linux": "run/user",
|
|
405
409
|
"freebsd": "var/run/user",
|
|
@@ -472,17 +476,20 @@ def test_runtime_dir_xdg_runtime_dir_of_other_user_warns(
|
|
|
472
476
|
|
|
473
477
|
@_POSIX_ONLY
|
|
474
478
|
@pytest.mark.usefixtures("runtime_uid")
|
|
479
|
+
@pytest.mark.parametrize("kind", list(_UNREACHABLE))
|
|
480
|
+
@pytest.mark.parametrize("ensure_exists", [pytest.param(False, id="lazy"), pytest.param(True, id="ensure-exists")])
|
|
475
481
|
def test_runtime_dir_unreachable_xdg_runtime_dir_warns(
|
|
476
|
-
runtime_temp_dir: Path, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
|
|
482
|
+
runtime_temp_dir: Path, temp_dir: Path, monkeypatch: pytest.MonkeyPatch, kind: str, ensure_exists: bool
|
|
477
483
|
) -> None:
|
|
478
|
-
(blocker := runtime_temp_dir / "blocker")
|
|
484
|
+
_create(blocker := runtime_temp_dir / "blocker", kind)
|
|
479
485
|
monkeypatch.setenv("XDG_RUNTIME_DIR", str(blocker / "session"))
|
|
480
486
|
with pytest.warns(RuntimeDirWarning) as record:
|
|
481
|
-
|
|
487
|
+
assert Unix(appname="app", ensure_exists=ensure_exists).user_runtime_dir == str(temp_dir / "app")
|
|
482
488
|
expected: typing.Final = (
|
|
483
|
-
f"XDG_RUNTIME_DIR {blocker / 'session'} cannot be checked:
|
|
489
|
+
f"XDG_RUNTIME_DIR {blocker / 'session'} cannot be checked: {_UNREACHABLE[kind]}, falling back to {temp_dir}"
|
|
484
490
|
)
|
|
485
491
|
assert [str(warning.message) for warning in record] == [expected]
|
|
492
|
+
assert (temp_dir / "app").is_dir() is ensure_exists
|
|
486
493
|
|
|
487
494
|
|
|
488
495
|
@_POSIX_ONLY
|
|
@@ -602,6 +609,8 @@ def _create(path: Path, kind: str) -> None:
|
|
|
602
609
|
path.symlink_to(target)
|
|
603
610
|
case "file":
|
|
604
611
|
path.touch()
|
|
612
|
+
case "symlink-loop":
|
|
613
|
+
path.symlink_to(path)
|
|
605
614
|
|
|
606
615
|
|
|
607
616
|
def test_ensure_exists_creates_folder(monkeypatch: pytest.MonkeyPatch, posix_tmp_path: str) -> None:
|
|
@@ -1226,6 +1235,23 @@ def test_user_path_site_redirect(
|
|
|
1226
1235
|
assert getattr(dirs, prop) == Path(base) / ("applications" if prop == "user_applications_path" else "foo/1.0")
|
|
1227
1236
|
|
|
1228
1237
|
|
|
1238
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="Windows ignores POSIX mode bits")
|
|
1239
|
+
@pytest.mark.parametrize("uid", [pytest.param(0, id="root"), pytest.param(1000, id="user")], indirect=True)
|
|
1240
|
+
@pytest.mark.parametrize("kind", ["config", "data"])
|
|
1241
|
+
def test_place_file_with_use_site_for_root_keeps_site_dir_readable(
|
|
1242
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, uid: int, kind: str
|
|
1243
|
+
) -> None:
|
|
1244
|
+
monkeypatch.setenv(f"XDG_{kind.upper()}_HOME", str(tmp_path / "user"))
|
|
1245
|
+
monkeypatch.setenv(f"XDG_{kind.upper()}_DIRS", str(tmp_path / "site"))
|
|
1246
|
+
getattr(Unix(appname="foo", use_site_for_root=True), f"place_{kind}_file")("sub/app.toml")
|
|
1247
|
+
(reference := tmp_path / "reference").mkdir()
|
|
1248
|
+
expected: typing.Final = stat.S_IMODE(reference.stat().st_mode) if uid == 0 else 0o700
|
|
1249
|
+
created: typing.Final = (tmp_path / ("site" if uid == 0 else "user")).rglob("*")
|
|
1250
|
+
assert {path.name: oct(stat.S_IMODE(path.stat().st_mode)) for path in created} == dict.fromkeys(
|
|
1251
|
+
["foo", "sub"], oct(expected)
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
|
|
1229
1255
|
@pytest.fixture
|
|
1230
1256
|
def uid(request: pytest.FixtureRequest, mocker: MockerFixture) -> int:
|
|
1231
1257
|
value: typing.Final = typing.cast("int", request.param)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|