From 6e66a3ff17ba0d5e30cb064c63c485f7f6ca166d Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Wed, 28 Jul 2021 22:05:29 +0200 Subject: [PATCH 1/8] Unix: Fallback to default if XDG environment variable is empty Fixes #29 using https://github.com/pypa/pip/blob/867bbb00ccb0506095e19a0e111ce06ce71a2495/tools/vendoring/patches/appdirs.patch#L47-L54 as a reference implementation. --- src/platformdirs/unix.py | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/platformdirs/unix.py b/src/platformdirs/unix.py index 02beca29..c59adead 100644 --- a/src/platformdirs/unix.py +++ b/src/platformdirs/unix.py @@ -22,10 +22,7 @@ def user_data_dir(self) -> str: :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or ``$XDG_DATA_HOME/$appname/$version`` """ - if "XDG_DATA_HOME" in os.environ: - path = os.environ["XDG_DATA_HOME"] - else: - path = os.path.expanduser("~/.local/share") + path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_DATA_HOME", "~/.local/share") return self._append_app_name_and_version(path) @property @@ -36,10 +33,9 @@ def site_data_dir(self) -> str: path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` """ # XDG default for $XDG_DATA_DIRS; only first, if multipath is False - if "XDG_DATA_DIRS" in os.environ: - path = os.environ["XDG_DATA_DIRS"] - else: - path = f"/usr/local/share{os.pathsep}/usr/share" + path = self._xdg_dir_or_fallback_with_expanded_user_dir( + "XDG_DATA_DIRS", f"/usr/local/share{os.pathsep}/usr/share" + ) return self._with_multi_path(path) def _with_multi_path(self, path: str) -> str: @@ -55,10 +51,7 @@ def user_config_dir(self) -> str: :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or ``$XDG_CONFIG_HOME/$appname/$version`` """ - if "XDG_CONFIG_HOME" in os.environ: - path = os.environ["XDG_CONFIG_HOME"] - else: - path = os.path.expanduser("~/.config") + path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_CONFIG_HOME", "~/.config") return self._append_app_name_and_version(path) @property @@ -69,10 +62,7 @@ def site_config_dir(self) -> str: path separator), e.g. ``/etc/xdg/$appname/$version`` """ # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False - if "XDG_CONFIG_DIRS" in os.environ: - path = os.environ["XDG_CONFIG_DIRS"] - else: - path = "/etc/xdg" + path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_CONFIG_DIRS", "/etc/xdg") return self._with_multi_path(path) @property @@ -81,10 +71,7 @@ def user_cache_dir(self) -> str: :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or ``~/$XDG_CACHE_HOME/$appname/$version`` """ - if "XDG_CACHE_HOME" in os.environ: - path = os.environ["XDG_CACHE_HOME"] - else: - path = os.path.expanduser("~/.cache") + path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_CACHE_HOME", "~/.cache") return self._append_app_name_and_version(path) @property @@ -93,10 +80,7 @@ def user_state_dir(self) -> str: :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or ``$XDG_STATE_HOME/$appname/$version`` """ - if "XDG_STATE_HOME" in os.environ: - path = os.environ["XDG_STATE_HOME"] - else: - path = os.path.expanduser("~/.local/state") + path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_STATE_HOME", "~/.local/state") return self._append_app_name_and_version(path) @property @@ -125,6 +109,10 @@ def _first_item_as_path_if_multipath(self, directory: str) -> Path: directory = directory.split(os.pathsep)[0] return Path(directory) + @staticmethod + def _xdg_dir_or_fallback_with_expanded_user_dir(xdg_variable: str, fallback: str) -> str: + return os.environ.get(xdg_variable) or os.path.expanduser(fallback) + __all__ = [ "Unix", From dd2417b5278aa9f0958624700bcb05c8af8480e1 Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Wed, 28 Jul 2021 22:47:59 +0200 Subject: [PATCH 2/8] Add changelog --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 761d251b..bdbc544b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ platformdirs Changelog ====================== +platformdirs 2.2.0 +------------------ +- Unix: Fallback to default if XDG environment variable is empty + platformdirs 2.1.0 ------------------ - Add ``readthedocs.org`` documentation via Sphinx From 2df00b9f057e389bdfe7f6884067a351fb4bd532 Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Wed, 28 Jul 2021 23:49:16 +0200 Subject: [PATCH 3/8] Test XDG variable handling on Linux --- tests/test_xdg_variable_handling.py | 51 +++++++++++++++++++++++++++++ whitelist.txt | 1 + 2 files changed, 52 insertions(+) create mode 100644 tests/test_xdg_variable_handling.py diff --git a/tests/test_xdg_variable_handling.py b/tests/test_xdg_variable_handling.py new file mode 100644 index 00000000..73990c74 --- /dev/null +++ b/tests/test_xdg_variable_handling.py @@ -0,0 +1,51 @@ +import os +import platform +import typing + +import pytest + +import platformdirs + + +class XDGVariable(typing.NamedTuple): + name: str + default_value: str + + +DIR_CUSTOM = "/tmp/custom-dir" +MAP_XDG_DEFAULTS_WITH_MULTIPATH = { + "user_data_dir": XDGVariable("XDG_DATA_HOME", "~/.local/share"), + "site_data_dir": XDGVariable("XDG_DATA_DIRS", f"/usr/local/share{os.pathsep}/usr/share"), + "user_config_dir": XDGVariable("XDG_CONFIG_HOME", "~/.config"), + "site_config_dir": XDGVariable("XDG_CONFIG_DIRS", "/etc/xdg"), + "user_cache_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), + "user_state_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), + "user_log_dir": XDGVariable("XDG_LOG_HOME", "~/.cache"), +} + + +@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") +def test_xdg_variable_not_set(monkeypatch, func: str) -> None: + dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0", multipath=True, opinion=False) + xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] + monkeypatch.delenv(xdg_variable.name, raising=False) + result = getattr(dirs, func) + assert result == xdg_variable.default_value + + +@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") +def test_xdg_variable_empty_value(monkeypatch, func: str) -> None: + dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0", multipath=True, opinion=False) + xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] + monkeypatch.setenv(xdg_variable.name, "") + result = getattr(dirs, func) + assert result == xdg_variable.default_value + + +@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") +def test_xdg_variable_custom_value(monkeypatch, func: str) -> None: + dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0", multipath=True, opinion=False) + xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] + monkeypatch.setenv(xdg_variable.name, DIR_CUSTOM) + result = getattr(dirs, func) + assert result == DIR_CUSTOM diff --git a/whitelist.txt b/whitelist.txt index 7428da1c..fbb2062b 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -31,6 +31,7 @@ pyjnius Runtime setenv shell32 +skipif typehints unittest winreg From 84b3259358cca003fc894eaf11b3a3cb5392ec58 Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Wed, 28 Jul 2021 23:59:59 +0200 Subject: [PATCH 4/8] XDG variable testing: Use PlatformDirs instance without appname or version --- tests/test_xdg_variable_handling.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_xdg_variable_handling.py b/tests/test_xdg_variable_handling.py index 73990c74..280951a5 100644 --- a/tests/test_xdg_variable_handling.py +++ b/tests/test_xdg_variable_handling.py @@ -24,28 +24,30 @@ class XDGVariable(typing.NamedTuple): } +@pytest.fixture() +def dirs_instance() -> platformdirs.PlatformDirs: + return platformdirs.PlatformDirs(multipath=True, opinion=False) + + @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_xdg_variable_not_set(monkeypatch, func: str) -> None: - dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0", multipath=True, opinion=False) +def test_xdg_variable_not_set(monkeypatch, dirs_instance: platformdirs.PlatformDirs, func: str) -> None: xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] monkeypatch.delenv(xdg_variable.name, raising=False) - result = getattr(dirs, func) + result = getattr(dirs_instance, func) assert result == xdg_variable.default_value @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_xdg_variable_empty_value(monkeypatch, func: str) -> None: - dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0", multipath=True, opinion=False) +def test_xdg_variable_empty_value(monkeypatch, dirs_instance: platformdirs.PlatformDirs, func: str) -> None: xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] monkeypatch.setenv(xdg_variable.name, "") - result = getattr(dirs, func) + result = getattr(dirs_instance, func) assert result == xdg_variable.default_value @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_xdg_variable_custom_value(monkeypatch, func: str) -> None: - dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0", multipath=True, opinion=False) +def test_xdg_variable_custom_value(monkeypatch, dirs_instance: platformdirs.PlatformDirs, func: str) -> None: xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] monkeypatch.setenv(xdg_variable.name, DIR_CUSTOM) - result = getattr(dirs, func) + result = getattr(dirs_instance, func) assert result == DIR_CUSTOM From c5e28c17ed7f5b9db8c384a72e8e95b42138e126 Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Thu, 29 Jul 2021 00:06:50 +0200 Subject: [PATCH 5/8] XDG variable testing: Expand user on default values --- tests/test_xdg_variable_handling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_xdg_variable_handling.py b/tests/test_xdg_variable_handling.py index 280951a5..dade82f6 100644 --- a/tests/test_xdg_variable_handling.py +++ b/tests/test_xdg_variable_handling.py @@ -34,7 +34,7 @@ def test_xdg_variable_not_set(monkeypatch, dirs_instance: platformdirs.PlatformD xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] monkeypatch.delenv(xdg_variable.name, raising=False) result = getattr(dirs_instance, func) - assert result == xdg_variable.default_value + assert result == os.path.expanduser(xdg_variable.default_value) @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") @@ -42,7 +42,7 @@ def test_xdg_variable_empty_value(monkeypatch, dirs_instance: platformdirs.Platf xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] monkeypatch.setenv(xdg_variable.name, "") result = getattr(dirs_instance, func) - assert result == xdg_variable.default_value + assert result == os.path.expanduser(xdg_variable.default_value) @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") From ad1a2476c9a69b111691667f8bc49eed53f20e1a Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Thu, 29 Jul 2021 00:07:16 +0200 Subject: [PATCH 6/8] XDG variable testing: Use correct xdg variable name for user_log_dir --- tests/test_xdg_variable_handling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_xdg_variable_handling.py b/tests/test_xdg_variable_handling.py index dade82f6..ec95fd5c 100644 --- a/tests/test_xdg_variable_handling.py +++ b/tests/test_xdg_variable_handling.py @@ -20,7 +20,7 @@ class XDGVariable(typing.NamedTuple): "site_config_dir": XDGVariable("XDG_CONFIG_DIRS", "/etc/xdg"), "user_cache_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), "user_state_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), - "user_log_dir": XDGVariable("XDG_LOG_HOME", "~/.cache"), + "user_log_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), } From ab3c6623c0eb74480c2063f406f91a032644e8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Thu, 29 Jul 2021 00:21:24 +0100 Subject: [PATCH 7/8] PR feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- src/platformdirs/unix.py | 30 +++++++++------- tests/test_unix.py | 51 +++++++++++++++++++++++++++ tests/test_xdg_variable_handling.py | 53 ----------------------------- 3 files changed, 69 insertions(+), 65 deletions(-) create mode 100644 tests/test_unix.py delete mode 100644 tests/test_xdg_variable_handling.py diff --git a/src/platformdirs/unix.py b/src/platformdirs/unix.py index c59adead..3f6afec5 100644 --- a/src/platformdirs/unix.py +++ b/src/platformdirs/unix.py @@ -22,7 +22,9 @@ def user_data_dir(self) -> str: :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or ``$XDG_DATA_HOME/$appname/$version`` """ - path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_DATA_HOME", "~/.local/share") + path = os.environ.get("XDG_DATA_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/share") return self._append_app_name_and_version(path) @property @@ -33,9 +35,9 @@ def site_data_dir(self) -> str: path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` """ # XDG default for $XDG_DATA_DIRS; only first, if multipath is False - path = self._xdg_dir_or_fallback_with_expanded_user_dir( - "XDG_DATA_DIRS", f"/usr/local/share{os.pathsep}/usr/share" - ) + path = os.environ.get("XDG_DATA_DIRS", "") + if not path.strip(): + path = f"/usr/local/share{os.pathsep}/usr/share" return self._with_multi_path(path) def _with_multi_path(self, path: str) -> str: @@ -51,7 +53,9 @@ def user_config_dir(self) -> str: :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or ``$XDG_CONFIG_HOME/$appname/$version`` """ - path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_CONFIG_HOME", "~/.config") + path = os.environ.get("XDG_CONFIG_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.config") return self._append_app_name_and_version(path) @property @@ -62,7 +66,9 @@ def site_config_dir(self) -> str: path separator), e.g. ``/etc/xdg/$appname/$version`` """ # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False - path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_CONFIG_DIRS", "/etc/xdg") + path = os.environ.get("XDG_CONFIG_DIRS", "") + if not path.strip(): + path = "/etc/xdg" return self._with_multi_path(path) @property @@ -71,7 +77,9 @@ def user_cache_dir(self) -> str: :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or ``~/$XDG_CACHE_HOME/$appname/$version`` """ - path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_CACHE_HOME", "~/.cache") + path = os.environ.get("XDG_CACHE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.cache") return self._append_app_name_and_version(path) @property @@ -80,7 +88,9 @@ def user_state_dir(self) -> str: :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or ``$XDG_STATE_HOME/$appname/$version`` """ - path = self._xdg_dir_or_fallback_with_expanded_user_dir("XDG_STATE_HOME", "~/.local/state") + path = os.environ.get("XDG_STATE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/state") return self._append_app_name_and_version(path) @property @@ -109,10 +119,6 @@ def _first_item_as_path_if_multipath(self, directory: str) -> Path: directory = directory.split(os.pathsep)[0] return Path(directory) - @staticmethod - def _xdg_dir_or_fallback_with_expanded_user_dir(xdg_variable: str, fallback: str) -> str: - return os.environ.get(xdg_variable) or os.path.expanduser(fallback) - __all__ = [ "Unix", diff --git a/tests/test_unix.py b/tests/test_unix.py new file mode 100644 index 00000000..59e60e60 --- /dev/null +++ b/tests/test_unix.py @@ -0,0 +1,51 @@ +import os +import typing + +import pytest +from _pytest.monkeypatch import MonkeyPatch + +from platformdirs.unix import Unix + + +class XDGVariable(typing.NamedTuple): + name: str + default_value: str + + +def _func_to_path(func: str) -> XDGVariable: + mapping = { + "user_data_dir": XDGVariable("XDG_DATA_HOME", "~/.local/share"), + "site_data_dir": XDGVariable("XDG_DATA_DIRS", f"/usr/local/share{os.pathsep}/usr/share"), + "user_config_dir": XDGVariable("XDG_CONFIG_HOME", "~/.config"), + "site_config_dir": XDGVariable("XDG_CONFIG_DIRS", "/etc/xdg"), + "user_cache_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), + "user_state_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), + "user_log_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), + } + return mapping[func] + + +@pytest.fixture() +def dirs_instance() -> Unix: + return Unix(multipath=True, opinion=False) + + +def test_xdg_variable_not_set(monkeypatch: MonkeyPatch, dirs_instance: Unix, func: str) -> None: + xdg_variable = _func_to_path(func) + monkeypatch.delenv(xdg_variable.name, raising=False) + result = getattr(dirs_instance, func) + assert result == os.path.expanduser(xdg_variable.default_value) + + +def test_xdg_variable_empty_value(monkeypatch: MonkeyPatch, dirs_instance: Unix, func: str) -> None: + xdg_variable = _func_to_path(func) + monkeypatch.setenv(xdg_variable.name, "") + result = getattr(dirs_instance, func) + assert result == os.path.expanduser(xdg_variable.default_value) + + +def test_xdg_variable_custom_value(monkeypatch: MonkeyPatch, dirs_instance: Unix, func: str) -> None: + xdg_variable = _func_to_path(func) + monkeypatch.setenv(xdg_variable.name, "/tmp/custom-dir") + result = getattr(dirs_instance, func) + assert result == "/tmp/custom-dir" diff --git a/tests/test_xdg_variable_handling.py b/tests/test_xdg_variable_handling.py deleted file mode 100644 index ec95fd5c..00000000 --- a/tests/test_xdg_variable_handling.py +++ /dev/null @@ -1,53 +0,0 @@ -import os -import platform -import typing - -import pytest - -import platformdirs - - -class XDGVariable(typing.NamedTuple): - name: str - default_value: str - - -DIR_CUSTOM = "/tmp/custom-dir" -MAP_XDG_DEFAULTS_WITH_MULTIPATH = { - "user_data_dir": XDGVariable("XDG_DATA_HOME", "~/.local/share"), - "site_data_dir": XDGVariable("XDG_DATA_DIRS", f"/usr/local/share{os.pathsep}/usr/share"), - "user_config_dir": XDGVariable("XDG_CONFIG_HOME", "~/.config"), - "site_config_dir": XDGVariable("XDG_CONFIG_DIRS", "/etc/xdg"), - "user_cache_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), - "user_state_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"), - "user_log_dir": XDGVariable("XDG_CACHE_HOME", "~/.cache"), -} - - -@pytest.fixture() -def dirs_instance() -> platformdirs.PlatformDirs: - return platformdirs.PlatformDirs(multipath=True, opinion=False) - - -@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_xdg_variable_not_set(monkeypatch, dirs_instance: platformdirs.PlatformDirs, func: str) -> None: - xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] - monkeypatch.delenv(xdg_variable.name, raising=False) - result = getattr(dirs_instance, func) - assert result == os.path.expanduser(xdg_variable.default_value) - - -@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_xdg_variable_empty_value(monkeypatch, dirs_instance: platformdirs.PlatformDirs, func: str) -> None: - xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] - monkeypatch.setenv(xdg_variable.name, "") - result = getattr(dirs_instance, func) - assert result == os.path.expanduser(xdg_variable.default_value) - - -@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_xdg_variable_custom_value(monkeypatch, dirs_instance: platformdirs.PlatformDirs, func: str) -> None: - xdg_variable = MAP_XDG_DEFAULTS_WITH_MULTIPATH[func] - monkeypatch.setenv(xdg_variable.name, DIR_CUSTOM) - result = getattr(dirs_instance, func) - assert result == DIR_CUSTOM From 9543cb1143457f0708039d3df091a6d8b809b6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Thu, 29 Jul 2021 08:16:40 +0100 Subject: [PATCH 8/8] Update whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- whitelist.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/whitelist.txt b/whitelist.txt index fbb2062b..c390319a 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -12,13 +12,11 @@ Dirs func highbit HKEY -impl intersphinx isfunction jnius kernel32 lru -macos multipath normpath ord @@ -28,10 +26,8 @@ pathlib pathsep platformdirs pyjnius -Runtime setenv shell32 -skipif typehints unittest winreg