diff --git a/src/platformdirs/windows.py b/src/platformdirs/windows.py index 07b4548..d7bc960 100644 --- a/src/platformdirs/windows.py +++ b/src/platformdirs/windows.py @@ -2,7 +2,6 @@ from __future__ import annotations -import ctypes import os import sys from functools import lru_cache @@ -217,6 +216,8 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str: # Use 'CSIDL_PROFILE' (40) and append the default folder 'Downloads' instead. # https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid + import ctypes # noqa: PLC0415 + csidl_const = { "CSIDL_APPDATA": 26, "CSIDL_COMMON_APPDATA": 35, @@ -249,8 +250,13 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str: def _pick_get_win_folder() -> Callable[[str], str]: - if hasattr(ctypes, "windll"): - return get_win_folder_via_ctypes + try: + import ctypes # noqa: PLC0415 + except ImportError: + pass + else: + if hasattr(ctypes, "windll"): + return get_win_folder_via_ctypes try: import winreg # noqa: PLC0415, F401 except ImportError: diff --git a/tests/test_api.py b/tests/test_api.py index 8f3b455..cb28028 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,14 +1,23 @@ from __future__ import annotations +import builtins +import functools import inspect import sys from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable import pytest import platformdirs from platformdirs.android import Android +builtin_import = builtins.__import__ + + +if TYPE_CHECKING: + from types import ModuleType + def test_package_metadata() -> None: assert hasattr(platformdirs, "__version__") @@ -80,3 +89,34 @@ def test_android_active( # noqa: PLR0913 assert platformdirs._set_platform_dir_class() is Android # noqa: SLF001 else: assert platformdirs._set_platform_dir_class() is not Android # noqa: SLF001 + + +def _fake_import(name: str, *args: Any, **kwargs: Any) -> ModuleType: # noqa: ANN401 + if name == "ctypes": + raise ModuleNotFoundError("No module named %s" % name) + return builtin_import(name, *args, **kwargs) + + +def mock_import(func: Callable[[], None]) -> Callable[[], None]: + @functools.wraps(func) + def wrap() -> None: + 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() + finally: + # restore original modules + builtins.__import__ = builtin_import + for name, module in platformdirs_module_items: + sys.modules[name] = module + + return wrap + + +@mock_import +def test_no_ctypes() -> None: + import platformdirs # noqa: PLC0415 + + assert platformdirs