From df22becd95a551b078db0c6b070ea3d40896560c Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 12:18:24 -0400 Subject: [PATCH 01/15] fix(Unix.user_runtime_dir): nonexistent runtime dir This fixes cases where XDG_RUNTIME_DIR is unset and the default runtime dir does not exist by getting a directory using Python's built-in tempfile. Fixes #368 --- pyproject.toml | 1 + src/platformdirs/unix.py | 19 ++++++++++++------- tests/test_unix.py | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 80b93ff3..d8ae7227 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ optional-dependencies.docs = [ optional-dependencies.test = [ "appdirs==1.4.4", "covdefaults>=2.3", + "pyfakefs>=5.9.2", "pytest>=8.3.4", "pytest-cov>=6", "pytest-mock>=3.14", diff --git a/src/platformdirs/unix.py b/src/platformdirs/unix.py index fc75d8d0..53726a98 100644 --- a/src/platformdirs/unix.py +++ b/src/platformdirs/unix.py @@ -6,6 +6,7 @@ import sys from configparser import ConfigParser from pathlib import Path +from tempfile import gettempdir from typing import TYPE_CHECKING, NoReturn from .api import PlatformDirsABC @@ -175,13 +176,17 @@ def user_runtime_dir(self) -> str: is not set. """ path = os.environ.get("XDG_RUNTIME_DIR", "") - if not path.strip(): - if sys.platform.startswith(("freebsd", "openbsd", "netbsd")): - path = f"/var/run/user/{getuid()}" - if not Path(path).exists(): - path = f"/tmp/runtime-{getuid()}" # noqa: S108 - else: - path = f"/run/user/{getuid()}" + if path.strip(): + return self._append_app_name_and_version(path) + + if sys.platform.startswith(("freebsd", "openbsd", "netbsd")): + path = f"/var/run/user/{getuid()}" + else: + path = f"/run/user/{getuid()}" + + if not os.access(path, os.W_OK): + path = f"{gettempdir()}/runtime-{getuid()}" + return self._append_app_name_and_version(path) @property diff --git a/tests/test_unix.py b/tests/test_unix.py index 326f8d7e..e1b89dbc 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -4,6 +4,7 @@ import os import sys import typing +from tempfile import gettempdir import pytest @@ -13,6 +14,7 @@ if typing.TYPE_CHECKING: from pathlib import Path + from pyfakefs.fake_filesystem import FakeFilesystem from pytest_mock import MockerFixture @@ -97,7 +99,7 @@ def _func_to_path(func: str) -> XDGVariable | None: "user_cache_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), "user_state_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), "user_log_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), - "user_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", "/run/user/1234"), + "user_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", "/tmp/runtime-1234"), # noqa: S108 "site_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", "/run"), } return mapping.get(func) @@ -154,10 +156,10 @@ def test_platform_on_bsd(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, assert Unix().site_runtime_dir == "/var/run" - mocker.patch("pathlib.Path.exists", return_value=True) + mocker.patch("os.access", return_value=True) assert Unix().user_runtime_dir == "/var/run/user/1234" - mocker.patch("pathlib.Path.exists", return_value=False) + mocker.patch("os.access", return_value=False) assert Unix().user_runtime_dir == "/tmp/runtime-1234" # noqa: S108 @@ -173,6 +175,35 @@ def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixtur sys.modules["platformdirs.unix"] = prev_unix +@pytest.mark.usefixtures("_getuid") +@pytest.mark.parametrize( + ("platform", "default_dir"), + [ + ("freebsd", "/var/run/user/1234"), + ("linux", "/run/user/1234"), + ], +) +def test_xdg_runtime_dir_unset( + monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, fs: FakeFilesystem, platform: str, default_dir: str +) -> None: + monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) + mocker.patch("sys.platform", platform) + + fs.create_dir(default_dir) + + assert Unix().user_runtime_dir.startswith(default_dir) + + # If the default directory isn't writable, we shouldn't use it. + fs.chmod(default_dir, 0o000) + assert not Unix().user_runtime_dir.startswith(default_dir) + assert Unix().user_runtime_dir.startswith(gettempdir()) + + # If the runtime directory doesn't exist, we shouldn't use it. + fs.rmdir(default_dir) + assert not Unix().user_runtime_dir.startswith(default_dir) + assert Unix().user_runtime_dir.startswith(gettempdir()) + + def test_ensure_exists_creates_folder(mocker: MockerFixture, tmp_path: Path) -> None: mocker.patch.dict(os.environ, {"XDG_DATA_HOME": str(tmp_path)}) data_path = Unix(appname="acme", ensure_exists=True).user_data_path From 28d5d80c3e19cd4c0be9e149fc83412618c99305 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 12:22:06 -0400 Subject: [PATCH 02/15] fix: docstring --- src/platformdirs/unix.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/platformdirs/unix.py b/src/platformdirs/unix.py index 53726a98..260f15fd 100644 --- a/src/platformdirs/unix.py +++ b/src/platformdirs/unix.py @@ -168,12 +168,11 @@ def user_desktop_dir(self) -> str: @property def user_runtime_dir(self) -> str: """ - :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or - ``$XDG_RUNTIME_DIR/$appname/$version``. + :return: runtime directory tied to the user, e.g. ``$XDG_RUNTIME_DIR/$appname/$version``. - For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if - exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR`` - is not set. + If ``$XDG_RUNTIME_DIR`` is unset, it tries the platform default location of that runtime directory + (``/var/run/user/$(id -u)`` on FreeBSD/OpenBSD/NetBSD, ``/run/user/$(id -u)`` otherwise). + If the default location is not writable, it uses a temporary directory instead. """ path = os.environ.get("XDG_RUNTIME_DIR", "") if path.strip(): From c6f435a8e962488af31e9e7ed9cfe6fc7386c437 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 12:34:20 -0400 Subject: [PATCH 03/15] fix: ci on windows/mac --- tests/test_unix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_unix.py b/tests/test_unix.py index e1b89dbc..d185f3a3 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -99,7 +99,7 @@ def _func_to_path(func: str) -> XDGVariable | None: "user_cache_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), "user_state_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), "user_log_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), - "user_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", "/tmp/runtime-1234"), # noqa: S108 + "user_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", f"{gettempdir()}/runtime-1234"), "site_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", "/run"), } return mapping.get(func) @@ -160,7 +160,7 @@ def test_platform_on_bsd(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, assert Unix().user_runtime_dir == "/var/run/user/1234" mocker.patch("os.access", return_value=False) - assert Unix().user_runtime_dir == "/tmp/runtime-1234" # noqa: S108 + assert Unix().user_runtime_dir == f"{gettempdir()}/runtime-1234" def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: From 569d313e13b51fa3a86e50bb00841abbcbcbe03e Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 12:57:02 -0400 Subject: [PATCH 04/15] fix: reload after tests that modify behaviour --- tests/test_unix.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_unix.py b/tests/test_unix.py index d185f3a3..dc938886 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -18,6 +18,14 @@ from pytest_mock import MockerFixture +@pytest.fixture +def _reload_after_test() -> typing.Iterator[None]: + global Unix # noqa: PLW0603 - we need to rewrite this full import. + yield + importlib.reload(unix) + Unix = unix.Unix + + @pytest.mark.parametrize( "prop", [ @@ -175,7 +183,7 @@ def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixtur sys.modules["platformdirs.unix"] = prev_unix -@pytest.mark.usefixtures("_getuid") +@pytest.mark.usefixtures("_getuid", "_reload_after_test") @pytest.mark.parametrize( ("platform", "default_dir"), [ From 52eaeac6e10ec7286c1a8379f83a06397048c6f1 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 13:07:28 -0400 Subject: [PATCH 05/15] fix: ignore unix runtimedir on Windows --- tests/test_unix.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/test_unix.py b/tests/test_unix.py index dc938886..a5bb09c8 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -18,14 +18,6 @@ from pytest_mock import MockerFixture -@pytest.fixture -def _reload_after_test() -> typing.Iterator[None]: - global Unix # noqa: PLW0603 - we need to rewrite this full import. - yield - importlib.reload(unix) - Unix = unix.Unix - - @pytest.mark.parametrize( "prop", [ @@ -168,7 +160,7 @@ def test_platform_on_bsd(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, assert Unix().user_runtime_dir == "/var/run/user/1234" mocker.patch("os.access", return_value=False) - assert Unix().user_runtime_dir == f"{gettempdir()}/runtime-1234" + assert Unix().user_runtime_dir == "/tmp/runtime-1234" # noqa: S108 def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: @@ -183,7 +175,11 @@ def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixtur sys.modules["platformdirs.unix"] = prev_unix -@pytest.mark.usefixtures("_getuid", "_reload_after_test") +@pytest.mark.skipif( + sys.platform == "win32", + reason="Running on Windows would require multiple reloads and make this test excessively complex.", +) +@pytest.mark.usefixtures("_getuid") @pytest.mark.parametrize( ("platform", "default_dir"), [ From 52cbd55fb58ff982d9089501ae2a0f9233904d14 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 13:11:31 -0400 Subject: [PATCH 06/15] fix: tempdir on bsd MacOS runners use a different default tempdir --- tests/test_unix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_unix.py b/tests/test_unix.py index a5bb09c8..46911e88 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -153,6 +153,7 @@ def test_xdg_variable_custom_value(monkeypatch: pytest.MonkeyPatch, dirs_instanc def test_platform_on_bsd(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) + mocker.patch("tempfile.tempdir", "/tmp") # noqa: S108 assert Unix().site_runtime_dir == "/var/run" From 0a3999df5e0732af2f6dbd3b877e0d94ac7fab8d Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 22 Aug 2025 13:32:08 -0400 Subject: [PATCH 07/15] ? --- tests/test_unix.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_unix.py b/tests/test_unix.py index 46911e88..794d7828 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -18,6 +18,12 @@ from pytest_mock import MockerFixture +@pytest.fixture(autouse=True) +def _reload_after_test() -> typing.Iterator[None]: + yield + importlib.reload(unix) + + @pytest.mark.parametrize( "prop", [ @@ -176,10 +182,6 @@ def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixtur sys.modules["platformdirs.unix"] = prev_unix -@pytest.mark.skipif( - sys.platform == "win32", - reason="Running on Windows would require multiple reloads and make this test excessively complex.", -) @pytest.mark.usefixtures("_getuid") @pytest.mark.parametrize( ("platform", "default_dir"), From dd3f1100a749e56dee23eab62eea359638e341c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 06:37:55 -0700 Subject: [PATCH 08/15] Bump the all group with 2 updates (#392) Bumps the all group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) Updates `actions/download-artifact` from 5 to 6 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check.yaml | 6 +++--- .github/workflows/release.yaml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index e1035249..7f1dc543 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -75,7 +75,7 @@ jobs: os.rename(f"report{os.sep}.coverage.${{ matrix.py }}", f"report{os.sep}.coverage.${{ matrix.py }}-{sys.platform}") shell: python - name: Upload coverage data - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: include-hidden-files: true name: coverage-${{ matrix.os }}-${{ matrix.py }} @@ -105,7 +105,7 @@ jobs: hatch -v env create coverage hatch run coverage:pip freeze - name: Download coverage data - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: report pattern: coverage-* @@ -113,7 +113,7 @@ jobs: - name: Combine and report coverage run: hatch run coverage:run - name: Upload HTML report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: html-report path: report/html diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f28785cf..298e91df 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: - name: Build package run: uv build --python 3.13 --python-preference only-managed --sdist --wheel . --out-dir dist - name: Store the distribution packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.dists-artifact-name }} path: dist/* @@ -38,7 +38,7 @@ jobs: id-token: write steps: - name: Download all the dists - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: ${{ env.dists-artifact-name }} path: dist/ From c1244cdd792f2f485edba3acac88fcaed581df04 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:00:13 -0700 Subject: [PATCH 09/15] [pre-commit.ci] pre-commit autoupdate (#393) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abc45146..a547c487 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.14.1" + rev: "v0.14.2" hooks: - id: ruff-format - id: ruff From 1042060bfdab215c73d464772fb730523e04ad06 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:48:51 -0800 Subject: [PATCH 10/15] [pre-commit.ci] pre-commit autoupdate (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.2 → v0.14.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.2...v0.14.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a547c487..70745211 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.14.2" + rev: "v0.14.3" hooks: - id: ruff-format - id: ruff From dc4dc3db98671a032cbfbbab9b3c1b015c3b31bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 10:26:10 -0800 Subject: [PATCH 11/15] [pre-commit.ci] pre-commit autoupdate (#397) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70745211..de73a0ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.1 + rev: 0.35.0 hooks: - id: check-github-workflows args: ["--verbose"] @@ -15,11 +15,11 @@ repos: - id: codespell additional_dependencies: ["tomli>=2.2.1"] - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.11.0" + rev: "v2.11.1" hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.14.3" + rev: "v0.14.5" hooks: - id: ruff-format - id: ruff From fb05b8206c6de63c86695b325950ef7d131c94f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 06:43:43 -0800 Subject: [PATCH 12/15] Bump actions/checkout from 5 to 6 in the all group (#399) Bumps the all group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check.yaml | 6 +++--- .github/workflows/release.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 7f1dc543..e2e9c281 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -32,7 +32,7 @@ jobs: - macos-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install the latest version of uv @@ -89,7 +89,7 @@ jobs: steps: - name: Let us have colors run: echo "FORCE_COLOR=true" >> "$GITHUB_ENV" - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install the latest version of uv @@ -133,7 +133,7 @@ jobs: - { "name": "docs", "target": "build" } - { "name": "readme", "target": "run" } steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install the latest version of uv diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 298e91df..40d6c583 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install the latest version of uv From 36183fdb21d0bda12f11b005e56262e462c857de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:57:20 -0800 Subject: [PATCH 13/15] [pre-commit.ci] pre-commit autoupdate (#400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.5 → v0.14.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.5...v0.14.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de73a0ac..2d4b01e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.14.5" + rev: "v0.14.6" hooks: - id: ruff-format - id: ruff From 469ace4f0860f59e76123d5923ba3bb07ec06b08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:08:02 -0800 Subject: [PATCH 14/15] [pre-commit.ci] pre-commit autoupdate (#401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.6 → v0.14.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.6...v0.14.7) - [github.com/rbubley/mirrors-prettier: v3.6.2 → v3.7.3](https://github.com/rbubley/mirrors-prettier/compare/v3.6.2...v3.7.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d4b01e3..d9015466 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,13 +19,13 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.14.6" + rev: "v0.14.7" hooks: - id: ruff-format - id: ruff args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"] - repo: https://github.com/rbubley/mirrors-prettier - rev: "v3.6.2" + rev: "v3.7.3" hooks: - id: prettier args: ["--print-width=120", "--prose-wrap=always"] From a2c2e0b1509b10c0d76f5d484241472bd9a8a479 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:51:54 +0900 Subject: [PATCH 15/15] Fix no-ctypes fallback on windows (#403) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/platformdirs/windows.py | 8 +++++++- tests/test_api.py | 12 ++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/platformdirs/windows.py b/src/platformdirs/windows.py index d7bc9609..8d523a9c 100644 --- a/src/platformdirs/windows.py +++ b/src/platformdirs/windows.py @@ -188,6 +188,9 @@ def get_win_folder_from_registry(csidl_name: str) -> str: for all CSIDL_* names. """ + machine_names = { + "CSIDL_COMMON_APPDATA", + } shell_folder_name = { "CSIDL_APPDATA": "AppData", "CSIDL_COMMON_APPDATA": "Common AppData", @@ -205,7 +208,10 @@ def get_win_folder_from_registry(csidl_name: str) -> str: raise NotImplementedError import winreg # noqa: PLC0415 - key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") + # Use HKEY_LOCAL_MACHINE for system-wide folders, HKEY_CURRENT_USER for user-specific folders + hkey = winreg.HKEY_LOCAL_MACHINE if csidl_name in machine_names else winreg.HKEY_CURRENT_USER + + key = winreg.OpenKey(hkey, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") directory, _ = winreg.QueryValueEx(key, shell_folder_name) return str(directory) diff --git a/tests/test_api.py b/tests/test_api.py index bd3d8e2a..3199a8e0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -99,15 +99,15 @@ def _fake_import(name: str, *args: Any, **kwargs: Any) -> ModuleType: # noqa: A return builtin_import(name, *args, **kwargs) -def mock_import(func: Callable[[], None]) -> Callable[[], None]: +def mock_import(func: Callable[..., None]) -> Callable[..., None]: @functools.wraps(func) - def wrap() -> None: + def wrap(*args: Any, **kwargs: Any) -> None: # noqa: ANN401 platformdirs_module_items = [item for item in sys.modules.items() if item[0].startswith("platformdirs")] try: builtins.__import__ = _fake_import for name, _ in platformdirs_module_items: del sys.modules[name] - return func() + return func(*args, **kwargs) finally: # restore original modules builtins.__import__ = builtin_import @@ -118,11 +118,15 @@ def wrap() -> None: @mock_import -def test_no_ctypes() -> None: +def test_no_ctypes(func: str) -> None: import platformdirs # noqa: PLC0415 assert platformdirs + dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0") + result = getattr(dirs, func) + assert isinstance(result, str) + def test_mypy_subclassing() -> None: # Ensure that PlatformDirs / AppDirs is seen as a valid superclass by mypy