From cde5f392e21e5c1ed9c320178d2d1e34d928e1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Fri, 13 Feb 2026 15:56:23 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(windows):=20add=20WIN=5FPD=5FO?= =?UTF-8?q?VERRIDE=5F*=20env=20var=20overrides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows the folder resolution (ctypes/registry) provides no way for users to redirect directories at runtime. This is needed when large data (ML models, package caches) should live on a different drive without changing the system-wide APPDATA/LOCALAPPDATA variables. Each CSIDL folder can now be overridden via a WIN_PD_OVERRIDE_ prefixed environment variable (e.g. WIN_PD_OVERRIDE_LOCAL_APPDATA), checked before falling back to the existing resolution. The lru_cache on get_win_folder is removed so overrides are picked up dynamically. Closes #347 --- docs/platforms.rst | 30 +++++++++++++++--------------- src/platformdirs/windows.py | 6 +++--- tests/test_windows.py | 8 ++++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/platforms.rst b/docs/platforms.rst index b13caaa..0ba5fcb 100644 --- a/docs/platforms.rst +++ b/docs/platforms.rst @@ -318,12 +318,12 @@ Environment variable overrides Unlike Linux/macOS where ``XDG_*`` variables are a platform standard, Windows has no built-in convention for overriding folder locations at the application level. To fill this gap, -``platformdirs`` checks ``PLATFORMDIRS_*`` environment variables before querying the Shell Folder -APIs. This is useful when large data (ML models, package caches) should live on a different drive -without changing the system-wide ``APPDATA`` / ``LOCALAPPDATA`` variables that other applications -rely on. +``platformdirs`` checks ``WIN_PD_OVERRIDE_*`` environment variables before querying the Shell +Folder APIs. This is useful when large data (ML models, package caches) should live on a different +drive without changing the system-wide ``APPDATA`` / ``LOCALAPPDATA`` variables that other +applications rely on. -The override variable name is ``PLATFORMDIRS_`` followed by the CSIDL suffix: +The override variable name is ``WIN_PD_OVERRIDE_`` followed by the CSIDL suffix: .. list-table:: :widths: 40 60 @@ -331,23 +331,23 @@ The override variable name is ``PLATFORMDIRS_`` followed by the CSIDL suffix: * - Environment variable - Overrides - * - ``PLATFORMDIRS_APPDATA`` + * - ``WIN_PD_OVERRIDE_APPDATA`` - Roaming user data (``AppData\Roaming``) - * - ``PLATFORMDIRS_LOCAL_APPDATA`` + * - ``WIN_PD_OVERRIDE_LOCAL_APPDATA`` - Local user data, config, cache, state (``AppData\Local``) - * - ``PLATFORMDIRS_COMMON_APPDATA`` + * - ``WIN_PD_OVERRIDE_COMMON_APPDATA`` - Site-wide data, config, cache, state (``ProgramData``) - * - ``PLATFORMDIRS_PERSONAL`` + * - ``WIN_PD_OVERRIDE_PERSONAL`` - Documents - * - ``PLATFORMDIRS_DOWNLOADS`` + * - ``WIN_PD_OVERRIDE_DOWNLOADS`` - Downloads - * - ``PLATFORMDIRS_MYPICTURES`` + * - ``WIN_PD_OVERRIDE_MYPICTURES`` - Pictures - * - ``PLATFORMDIRS_MYVIDEO`` + * - ``WIN_PD_OVERRIDE_MYVIDEO`` - Videos - * - ``PLATFORMDIRS_MYMUSIC`` + * - ``WIN_PD_OVERRIDE_MYMUSIC`` - Music - * - ``PLATFORMDIRS_DESKTOPDIRECTORY`` + * - ``WIN_PD_OVERRIDE_DESKTOPDIRECTORY`` - Desktop Example — redirect cache to a separate drive: @@ -355,7 +355,7 @@ Example — redirect cache to a separate drive: .. code-block:: python import os - os.environ["PLATFORMDIRS_LOCAL_APPDATA"] = r"X:\appdata" + os.environ["WIN_PD_OVERRIDE_LOCAL_APPDATA"] = r"X:\appdata" import platformdirs print(platformdirs.user_cache_dir("MyApp", "Acme")) diff --git a/src/platformdirs/windows.py b/src/platformdirs/windows.py index 675b665..ff2b729 100644 --- a/src/platformdirs/windows.py +++ b/src/platformdirs/windows.py @@ -324,12 +324,12 @@ def _pick_get_win_folder() -> Callable[[str], str]: def get_win_folder(csidl_name: str) -> str: """ - Get a Windows folder path, checking for ``PLATFORMDIRS_*`` environment variable overrides first. + Get a Windows folder path, checking for ``WIN_PD_OVERRIDE_*`` environment variable overrides first. - For example, ``CSIDL_LOCAL_APPDATA`` can be overridden by setting ``PLATFORMDIRS_LOCAL_APPDATA``. + For example, ``CSIDL_LOCAL_APPDATA`` can be overridden by setting ``WIN_PD_OVERRIDE_LOCAL_APPDATA``. """ - env_var = f"PLATFORMDIRS_{csidl_name.removeprefix('CSIDL_')}" + env_var = f"WIN_PD_OVERRIDE_{csidl_name.removeprefix('CSIDL_')}" if override := os.environ.get(env_var, "").strip(): return override return _resolve_win_folder(csidl_name) diff --git a/tests/test_windows.py b/tests/test_windows.py index fb13d2b..3c39c85 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -318,23 +318,23 @@ def test_pick_get_win_folder_ctypes(mocker: MockerFixture) -> None: def test_get_win_folder_override(monkeypatch: pytest.MonkeyPatch, csidl_name: str, env_suffix: str) -> None: override_path = r"X:\custom\override" monkeypatch.setattr("platformdirs.windows._resolve_win_folder", lambda _csidl: _WIN_FOLDERS[_csidl]) - monkeypatch.setenv(f"PLATFORMDIRS_{env_suffix}", override_path) + monkeypatch.setenv(f"WIN_PD_OVERRIDE_{env_suffix}", override_path) assert get_win_folder(csidl_name) == override_path def test_get_win_folder_override_whitespace_only_ignored(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("platformdirs.windows._resolve_win_folder", lambda csidl: _WIN_FOLDERS[csidl]) - monkeypatch.setenv("PLATFORMDIRS_LOCAL_APPDATA", " ") + monkeypatch.setenv("WIN_PD_OVERRIDE_LOCAL_APPDATA", " ") assert get_win_folder("CSIDL_LOCAL_APPDATA") == _WIN_FOLDERS["CSIDL_LOCAL_APPDATA"] def test_get_win_folder_override_not_set_falls_back(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("platformdirs.windows._resolve_win_folder", lambda csidl: _WIN_FOLDERS[csidl]) - monkeypatch.delenv("PLATFORMDIRS_LOCAL_APPDATA", raising=False) + monkeypatch.delenv("WIN_PD_OVERRIDE_LOCAL_APPDATA", raising=False) assert get_win_folder("CSIDL_LOCAL_APPDATA") == _WIN_FOLDERS["CSIDL_LOCAL_APPDATA"] def test_get_win_folder_override_strips_whitespace(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("platformdirs.windows._resolve_win_folder", lambda csidl: _WIN_FOLDERS[csidl]) - monkeypatch.setenv("PLATFORMDIRS_LOCAL_APPDATA", " X:\\custom ") + monkeypatch.setenv("WIN_PD_OVERRIDE_LOCAL_APPDATA", " X:\\custom ") assert get_win_folder("CSIDL_LOCAL_APPDATA") == r"X:\custom"