platformdirs-4.9.4/tox.toml0000644000000000000000000000771513615410400012677 0ustar00requires = [ "tox>=4.47" ] env_list = [ "3.14", "3.13", "3.12", "3.11", "3.10", "pypy3.11", "coverage", "docs", "fix", "pkg_meta", "type" ] skip_missing_interpreters = true [env_run_base] description = "run the tests with pytest under {env_name}" package = "wheel" wheel_build_env = ".pkg" dependency_groups = [ "test" ] pass_env = [ "PYTEST_*", "SSL_CERT_FILE" ] set_env.COVERAGE_FILE = { replace = "env", name = "COVERAGE_FILE", default = "{work_dir}{/}.coverage.{env_name}" } commands = [ [ "pytest", "--durations", "10", "--junitxml", "{env_tmp_dir}{/}junit.xml", { replace = "posargs", extend = true, default = [ "--no-cov-on-fail", "--cov", "{env_site_packages_dir}{/}platformdirs", "--cov", "{tox_root}{/}tests", "--cov-config", "{tox_root}{/}pyproject.toml", "--cov-context", "test", "--cov-report", "term-missing:skip-covered", "--cov-report", "html:{env_tmp_dir}{/}htmlcov", "--cov-report", "xml:{env_tmp_dir}{/}coverage.xml", ] }, "tests", ], { replace = "posargs", extend = true, default = [ [ "diff-cover", "--compare-branch", { replace = "env", name = "DIFF_AGAINST", default = "origin/main" }, "{env_tmp_dir}{/}coverage.xml", ], ] }, ] [env.coverage] description = "combine coverage files and generate diff (against DIFF_AGAINST defaulting to origin/main)" skip_install = true dependency_groups = [ "coverage" ] parallel_show_output = true pass_env = [ { replace = "ref", of = [ "env_run_base", "pass_env", ], extend = true }, "DIFF_AGAINST", ] set_env.COVERAGE_FILE = { replace = "env", name = "COVERAGE_FILE", default = "{work_dir}{/}.coverage" } commands = [ [ "coverage", "combine" ], [ "coverage", "report", "--skip-covered", "--show-missing" ], [ "coverage", "xml", "-o", "{env_tmp_dir}{/}coverage.xml" ], [ "coverage", "html", "-d", "{work_dir}{/}htmlcov" ], [ "diff-cover", "--compare-branch", { replace = "env", name = "DIFF_AGAINST", default = "origin/main" }, "{env_tmp_dir}{/}coverage.xml", ], ] depends = [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.11" ] [env.docs] description = "build documentation" dependency_groups = [ "docs" ] commands = [ [ "python", "-c", "import glob; import subprocess; subprocess.check_call(['proselint', 'check'] + glob.glob('docs/*.rst'))", ], [ "sphinx-build", "-d", "{env_tmp_dir}{/}docs_tree", "docs", "{env:READTHEDOCS_OUTPUT:{work_dir}{/}docs_out}/html", "--color", "-b", "html", { replace = "posargs", default = [], extend = true }, "-W", ], [ "python", "-c", 'print(r"documentation available under file://{work_dir}{/}docs_out{/}html{/}index.html")', ], ] [env.fix] description = "format the code base to adhere to our styles, and complain about what we cannot do automatically" skip_install = true dependency_groups = [ "fix" ] pass_env = [ { replace = "ref", of = [ "env_run_base", "pass_env", ], extend = true }, "DISABLE_PRE_COMMIT_UV_PATCH", "PROGRAMDATA", ] commands = [ [ "pre-commit", "run", "--all-files", "--show-diff-on-failure", { replace = "posargs", extend = true } ] ] [env.pkg_meta] description = "check that the long description is valid" skip_install = true dependency_groups = [ "pkg-meta" ] commands = [ [ "uv", "build", "--sdist", "--wheel", "--out-dir", "{env_tmp_dir}", ".", ], [ "twine", "check", "{env_tmp_dir}{/}*", ], [ "check-wheel-contents", "--no-config", "{env_tmp_dir}", ], ] [env.type] description = "run type check on code base" dependency_groups = [ "type" ] commands = [ [ "ty", "check", "--output-format", "concise", "--error-on-warning", "." ] ] [env.dev] description = "dev environment with all deps at {envdir}" package = "editable" dependency_groups = [ "dev" ] commands = [ [ "uv", "pip", "list", "--format=columns" ], [ "python", "-c", 'print(r"{env_python}")' ] ] platformdirs-4.9.4/src/platformdirs/__init__.py0000644000000000000000000007377613615410400016610 0ustar00"""Utilities for determining application-specific dirs. Provides convenience functions (e.g. :func:`user_data_dir`, :func:`user_config_path`), a :data:`PlatformDirs` class that auto-detects the current platform, and the :class:`~platformdirs.api.PlatformDirsABC` base class. See for details and usage. """ from __future__ import annotations import os import sys from typing import TYPE_CHECKING from .api import PlatformDirsABC from .version import __version__ from .version import __version_tuple__ as __version_info__ if TYPE_CHECKING: from pathlib import Path from typing import Literal if sys.platform == "win32": from platformdirs.windows import Windows as _Result elif sys.platform == "darwin": from platformdirs.macos import MacOS as _Result else: from platformdirs.unix import Unix as _Result def _set_platform_dir_class() -> type[PlatformDirsABC]: if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system": if os.getenv("SHELL") or os.getenv("PREFIX"): return _Result from platformdirs.android import _android_folder # noqa: PLC0415 if _android_folder() is not None: from platformdirs.android import Android # noqa: PLC0415 return Android # return to avoid redefinition of a result return _Result if TYPE_CHECKING: # Work around mypy issue: https://github.com/python/mypy/issues/10962 PlatformDirs = _Result else: PlatformDirs = _set_platform_dir_class() #: Currently active platform AppDirs = PlatformDirs #: Backwards compatibility with appdirs def user_data_dir( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: data directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_data_dir def site_data_dir( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, multipath: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param multipath: See `multipath `. :param ensure_exists: See `ensure_exists `. :returns: data directory shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, multipath=multipath, ensure_exists=ensure_exists, ).site_data_dir def user_config_dir( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: config directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_config_dir def site_config_dir( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, multipath: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param multipath: See `multipath `. :param ensure_exists: See `ensure_exists `. :returns: config directory shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, multipath=multipath, ensure_exists=ensure_exists, ).site_config_dir def user_cache_dir( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: cache directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_cache_dir def site_cache_dir( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :returns: cache directory shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_cache_dir def user_state_dir( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: state directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_state_dir def site_state_dir( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param ensure_exists: See `ensure_exists `. :returns: state directory shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, ensure_exists=ensure_exists, ).site_state_dir def user_log_dir( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: log directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_log_dir def site_log_dir( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :returns: log directory shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_log_dir def user_documents_dir() -> str: """:returns: documents directory tied to the user""" return PlatformDirs().user_documents_dir def user_downloads_dir() -> str: """:returns: downloads directory tied to the user""" return PlatformDirs().user_downloads_dir def user_pictures_dir() -> str: """:returns: pictures directory tied to the user""" return PlatformDirs().user_pictures_dir def user_videos_dir() -> str: """:returns: videos directory tied to the user""" return PlatformDirs().user_videos_dir def user_music_dir() -> str: """:returns: music directory tied to the user""" return PlatformDirs().user_music_dir def user_desktop_dir() -> str: """:returns: desktop directory tied to the user""" return PlatformDirs().user_desktop_dir def user_bin_dir() -> str: """:returns: bin directory tied to the user""" return PlatformDirs().user_bin_dir def site_bin_dir() -> str: """:returns: bin directory shared by users""" return PlatformDirs().site_bin_dir def user_applications_dir() -> str: """:returns: applications directory tied to the user""" return PlatformDirs().user_applications_dir def site_applications_dir( multipath: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param multipath: See `multipath `. :param ensure_exists: See `ensure_exists `. :returns: applications directory shared by users """ return PlatformDirs( multipath=multipath, ensure_exists=ensure_exists, ).site_applications_dir def user_runtime_dir( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: runtime directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_runtime_dir def site_runtime_dir( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> str: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :returns: runtime directory shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_runtime_dir def user_data_path( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: data path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_data_path def site_data_path( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, multipath: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param multipath: See `multipath `. :param ensure_exists: See `ensure_exists `. :returns: data path shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, multipath=multipath, ensure_exists=ensure_exists, ).site_data_path def user_config_path( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: config path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_config_path def site_config_path( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, multipath: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param multipath: See `multipath `. :param ensure_exists: See `ensure_exists `. :returns: config path shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, multipath=multipath, ensure_exists=ensure_exists, ).site_config_path def site_cache_path( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :returns: cache path shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_cache_path def user_cache_path( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: cache path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_cache_path def user_state_path( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: state path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_state_path def site_state_path( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param ensure_exists: See `ensure_exists `. :returns: state path shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, ensure_exists=ensure_exists, ).site_state_path def user_log_path( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: log path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_log_path def site_log_path( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :returns: log path shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_log_path def user_documents_path() -> Path: """:returns: documents path tied to the user""" return PlatformDirs().user_documents_path def user_downloads_path() -> Path: """:returns: downloads path tied to the user""" return PlatformDirs().user_downloads_path def user_pictures_path() -> Path: """:returns: pictures path tied to the user""" return PlatformDirs().user_pictures_path def user_videos_path() -> Path: """:returns: videos path tied to the user""" return PlatformDirs().user_videos_path def user_music_path() -> Path: """:returns: music path tied to the user""" return PlatformDirs().user_music_path def user_desktop_path() -> Path: """:returns: desktop path tied to the user""" return PlatformDirs().user_desktop_path def user_bin_path() -> Path: """:returns: bin path tied to the user""" return PlatformDirs().user_bin_path def site_bin_path() -> Path: """:returns: bin path shared by users""" return PlatformDirs().site_bin_path def user_applications_path() -> Path: """:returns: applications path tied to the user""" return PlatformDirs().user_applications_path def site_applications_path( multipath: bool = False, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param multipath: See `multipath `. :param ensure_exists: See `ensure_exists `. :returns: applications path shared by users """ return PlatformDirs( multipath=multipath, ensure_exists=ensure_exists, ).site_applications_path def user_runtime_path( # noqa: PLR0913, PLR0917 appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :param use_site_for_root: See `use_site_for_root `. :returns: runtime path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, use_site_for_root=use_site_for_root, ).user_runtime_path def site_runtime_path( appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 ) -> Path: """:param appname: See `appname `. :param appauthor: See `appauthor `. :param version: See `version `. :param opinion: See `opinion `. :param ensure_exists: See `ensure_exists `. :returns: runtime path shared by users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_runtime_path __all__ = [ "AppDirs", "PlatformDirs", "PlatformDirsABC", "__version__", "__version_info__", "site_applications_dir", "site_applications_path", "site_bin_dir", "site_bin_path", "site_cache_dir", "site_cache_path", "site_config_dir", "site_config_path", "site_data_dir", "site_data_path", "site_log_dir", "site_log_path", "site_runtime_dir", "site_runtime_path", "site_state_dir", "site_state_path", "user_applications_dir", "user_applications_path", "user_bin_dir", "user_bin_path", "user_cache_dir", "user_cache_path", "user_config_dir", "user_config_path", "user_data_dir", "user_data_path", "user_desktop_dir", "user_desktop_path", "user_documents_dir", "user_documents_path", "user_downloads_dir", "user_downloads_path", "user_log_dir", "user_log_path", "user_music_dir", "user_music_path", "user_pictures_dir", "user_pictures_path", "user_runtime_dir", "user_runtime_path", "user_state_dir", "user_state_path", "user_videos_dir", "user_videos_path", ] platformdirs-4.9.4/src/platformdirs/__main__.py0000644000000000000000000000314113615410400016544 0ustar00"""Main entry point.""" from __future__ import annotations from platformdirs import PlatformDirs, __version__ PROPS = ( "user_data_dir", "user_config_dir", "user_cache_dir", "user_state_dir", "user_log_dir", "user_documents_dir", "user_downloads_dir", "user_pictures_dir", "user_videos_dir", "user_music_dir", "user_bin_dir", "site_bin_dir", "user_applications_dir", "user_runtime_dir", "site_data_dir", "site_config_dir", "site_cache_dir", "site_state_dir", "site_log_dir", "site_applications_dir", "site_runtime_dir", ) def main() -> None: """Run the main entry point.""" app_name = "MyApp" app_author = "MyCompany" print(f"-- platformdirs {__version__} --") # noqa: T201 print("-- app dirs (with optional 'version')") # noqa: T201 dirs = PlatformDirs(app_name, app_author, version="1.0") for prop in PROPS: print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201 print("\n-- app dirs (without optional 'version')") # noqa: T201 dirs = PlatformDirs(app_name, app_author) for prop in PROPS: print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201 print("\n-- app dirs (without optional 'appauthor')") # noqa: T201 dirs = PlatformDirs(app_name) for prop in PROPS: print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201 print("\n-- app dirs (with disabled 'appauthor')") # noqa: T201 dirs = PlatformDirs(app_name, appauthor=False) for prop in PROPS: print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201 if __name__ == "__main__": main() platformdirs-4.9.4/src/platformdirs/_xdg.py0000644000000000000000000001447113615410400015755 0ustar00"""XDG environment variable mixin for Unix and macOS.""" from __future__ import annotations import os from .api import PlatformDirsABC class XDGMixin(PlatformDirsABC): """Mixin that checks XDG environment variables, falling back to platform-specific defaults via ``super()``.""" @property def user_data_dir(self) -> str: """:returns: data directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default""" if path := os.environ.get("XDG_DATA_HOME", "").strip(): return self._append_app_name_and_version(path) return super().user_data_dir @property def _site_data_dirs(self) -> list[str]: if xdg_dirs := os.environ.get("XDG_DATA_DIRS", "").strip(): return [self._append_app_name_and_version(p) for p in xdg_dirs.split(os.pathsep) if p.strip()] return super()._site_data_dirs # type: ignore[misc] @property def site_data_dir(self) -> str: """:returns: data directories shared by users, from ``$XDG_DATA_DIRS`` if set, else platform default""" dirs = self._site_data_dirs return os.pathsep.join(dirs) if self.multipath else dirs[0] @property def user_config_dir(self) -> str: """:returns: config directory tied to the user, from ``$XDG_CONFIG_HOME`` if set, else platform default""" if path := os.environ.get("XDG_CONFIG_HOME", "").strip(): return self._append_app_name_and_version(path) return super().user_config_dir @property def _site_config_dirs(self) -> list[str]: if xdg_dirs := os.environ.get("XDG_CONFIG_DIRS", "").strip(): return [self._append_app_name_and_version(p) for p in xdg_dirs.split(os.pathsep) if p.strip()] return super()._site_config_dirs # type: ignore[misc] @property def site_config_dir(self) -> str: """:returns: config directories shared by users, from ``$XDG_CONFIG_DIRS`` if set, else platform default""" dirs = self._site_config_dirs return os.pathsep.join(dirs) if self.multipath else dirs[0] @property def user_cache_dir(self) -> str: """:returns: cache directory tied to the user, from ``$XDG_CACHE_HOME`` if set, else platform default""" if path := os.environ.get("XDG_CACHE_HOME", "").strip(): return self._append_app_name_and_version(path) return super().user_cache_dir @property def user_state_dir(self) -> str: """:returns: state directory tied to the user, from ``$XDG_STATE_HOME`` if set, else platform default""" if path := os.environ.get("XDG_STATE_HOME", "").strip(): return self._append_app_name_and_version(path) return super().user_state_dir @property def user_runtime_dir(self) -> str: """:returns: runtime directory tied to the user, from ``$XDG_RUNTIME_DIR`` if set, else platform default""" if path := os.environ.get("XDG_RUNTIME_DIR", "").strip(): return self._append_app_name_and_version(path) return super().user_runtime_dir @property def site_runtime_dir(self) -> str: """:returns: runtime directory shared by users, from ``$XDG_RUNTIME_DIR`` if set, else platform default""" if path := os.environ.get("XDG_RUNTIME_DIR", "").strip(): return self._append_app_name_and_version(path) return super().site_runtime_dir @property def user_documents_dir(self) -> str: """:returns: documents directory tied to the user, from ``$XDG_DOCUMENTS_DIR`` if set, else platform default""" if path := os.environ.get("XDG_DOCUMENTS_DIR", "").strip(): return os.path.expanduser(path) # noqa: PTH111 return super().user_documents_dir @property def user_downloads_dir(self) -> str: """:returns: downloads directory tied to the user, from ``$XDG_DOWNLOAD_DIR`` if set, else platform default""" if path := os.environ.get("XDG_DOWNLOAD_DIR", "").strip(): return os.path.expanduser(path) # noqa: PTH111 return super().user_downloads_dir @property def user_pictures_dir(self) -> str: """:returns: pictures directory tied to the user, from ``$XDG_PICTURES_DIR`` if set, else platform default""" if path := os.environ.get("XDG_PICTURES_DIR", "").strip(): return os.path.expanduser(path) # noqa: PTH111 return super().user_pictures_dir @property def user_videos_dir(self) -> str: """:returns: videos directory tied to the user, from ``$XDG_VIDEOS_DIR`` if set, else platform default""" if path := os.environ.get("XDG_VIDEOS_DIR", "").strip(): return os.path.expanduser(path) # noqa: PTH111 return super().user_videos_dir @property def user_music_dir(self) -> str: """:returns: music directory tied to the user, from ``$XDG_MUSIC_DIR`` if set, else platform default""" if path := os.environ.get("XDG_MUSIC_DIR", "").strip(): return os.path.expanduser(path) # noqa: PTH111 return super().user_music_dir @property def user_desktop_dir(self) -> str: """:returns: desktop directory tied to the user, from ``$XDG_DESKTOP_DIR`` if set, else platform default""" if path := os.environ.get("XDG_DESKTOP_DIR", "").strip(): return os.path.expanduser(path) # noqa: PTH111 return super().user_desktop_dir @property def user_applications_dir(self) -> str: """:returns: applications directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default""" if path := os.environ.get("XDG_DATA_HOME", "").strip(): return os.path.join(os.path.expanduser(path), "applications") # noqa: PTH111, PTH118 return super().user_applications_dir @property def _site_applications_dirs(self) -> list[str]: if xdg_dirs := os.environ.get("XDG_DATA_DIRS", "").strip(): return [os.path.join(p, "applications") for p in xdg_dirs.split(os.pathsep) if p.strip()] # noqa: PTH118 return super()._site_applications_dirs # type: ignore[misc] @property def site_applications_dir(self) -> str: """:returns: applications directories shared by users, from ``$XDG_DATA_DIRS`` if set, else platform default""" dirs = self._site_applications_dirs return os.pathsep.join(dirs) if self.multipath else dirs[0] __all__ = [ "XDGMixin", ] platformdirs-4.9.4/src/platformdirs/android.py0000644000000000000000000002453713615410400016460 0ustar00"""Android.""" from __future__ import annotations import os import re import sys from functools import lru_cache from typing import TYPE_CHECKING, cast from .api import PlatformDirsABC class Android(PlatformDirsABC): # noqa: PLR0904 """Platform directories for Android. Follows the guidance `from here `_. Directories are typically located under the app's private storage (``/data/user///``). Makes use of the `appname `, `version `, `opinion `, `ensure_exists `. """ @property def user_data_dir(self) -> str: """:returns: data directory tied to the user, e.g. ``/data/user///files/``""" return self._append_app_name_and_version(cast("str", _android_folder()), "files") @property def site_data_dir(self) -> str: """:returns: data directory shared by users, same as `user_data_dir`""" return self.user_data_dir @property def user_config_dir(self) -> str: """:returns: config directory tied to the user, e.g. ``/data/user///shared_prefs/``""" return self._append_app_name_and_version(cast("str", _android_folder()), "shared_prefs") @property def site_config_dir(self) -> str: """:returns: config directory shared by users, same as `user_config_dir`""" return self.user_config_dir @property def user_cache_dir(self) -> str: """:returns: cache directory tied to the user, e.g.,``/data/user///cache/``""" return self._append_app_name_and_version(cast("str", _android_folder()), "cache") @property def site_cache_dir(self) -> str: """:returns: cache directory shared by users, same as `user_cache_dir`""" return self.user_cache_dir @property def user_state_dir(self) -> str: """:returns: state directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_state_dir(self) -> str: """:returns: state directory shared by users, same as `user_state_dir`""" return self.user_state_dir @property def user_log_dir(self) -> str: """:returns: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it, e.g. ``/data/user///cache//log``""" path = self.user_cache_dir if self.opinion: path = os.path.join(path, "log") # noqa: PTH118 self._optionally_create_directory(path) return path @property def site_log_dir(self) -> str: """:returns: log directory shared by users, same as `user_log_dir`""" return self.user_log_dir @property def user_documents_dir(self) -> str: """:returns: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``""" return _android_documents_folder() @property def user_downloads_dir(self) -> str: """:returns: downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``""" return _android_downloads_folder() @property def user_pictures_dir(self) -> str: """:returns: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``""" return _android_pictures_folder() @property def user_videos_dir(self) -> str: """:returns: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``""" return _android_videos_folder() @property def user_music_dir(self) -> str: """:returns: music directory tied to the user e.g. ``/storage/emulated/0/Music``""" return _android_music_folder() @property def user_desktop_dir(self) -> str: """:returns: desktop directory tied to the user e.g. ``/storage/emulated/0/Desktop``""" return "/storage/emulated/0/Desktop" @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, e.g. ``/data/user///files/bin``""" return os.path.join(cast("str", _android_folder()), "files", "bin") # noqa: PTH118 @property def site_bin_dir(self) -> str: """:returns: bin directory shared by users, same as `user_bin_dir`""" return self.user_bin_dir @property def user_applications_dir(self) -> str: """:returns: applications directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_applications_dir(self) -> str: """:returns: applications directory shared by users, same as `user_applications_dir`""" return self.user_applications_dir @property def user_runtime_dir(self) -> str: """:returns: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, e.g. ``/data/user///cache//tmp``""" path = self.user_cache_dir if self.opinion: path = os.path.join(path, "tmp") # noqa: PTH118 self._optionally_create_directory(path) return path @property def site_runtime_dir(self) -> str: """:returns: runtime directory shared by users, same as `user_runtime_dir`""" return self.user_runtime_dir @lru_cache(maxsize=1) def _android_folder() -> str | None: # noqa: C901 """:returns: base folder for the Android OS or None if it cannot be found""" result: str | None = None # type checker isn't happy with our "import android", just don't do this when type checking see # https://stackoverflow.com/a/61394121 if not TYPE_CHECKING: try: # First try to get a path to android app using python4android (if available)... from android import mActivity # noqa: PLC0415 context = cast("android.content.Context", mActivity.getApplicationContext()) # noqa: F821 result = context.getFilesDir().getParentFile().getAbsolutePath() except Exception: # noqa: BLE001 result = None if result is None: try: # ...and fall back to using plain pyjnius, if python4android isn't available or doesn't deliver any useful # result... from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import] context = autoclass("android.content.Context") result = context.getFilesDir().getParentFile().getAbsolutePath() except Exception: # noqa: BLE001 result = None if result is None: # and if that fails, too, find an android folder looking at path on the sys.path # warning: only works for apps installed under /data, not adopted storage etc. pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files") for path in sys.path: if pattern.match(path): result = path.split("/files")[0] break else: result = None if result is None: # one last try: find an android folder looking at path on the sys.path taking adopted storage paths into # account pattern = re.compile(r"/mnt/expand/[a-fA-F0-9-]{36}/(data|user/\d+)/(.+)/files") for path in sys.path: if pattern.match(path): result = path.split("/files")[0] break else: result = None return result @lru_cache(maxsize=1) def _android_documents_folder() -> str: """:returns: documents folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import] context = autoclass("android.content.Context") environment = autoclass("android.os.Environment") documents_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOCUMENTS).getAbsolutePath() except Exception: # noqa: BLE001 documents_dir = "/storage/emulated/0/Documents" return documents_dir @lru_cache(maxsize=1) def _android_downloads_folder() -> str: """:returns: downloads folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import] context = autoclass("android.content.Context") environment = autoclass("android.os.Environment") downloads_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOWNLOADS).getAbsolutePath() except Exception: # noqa: BLE001 downloads_dir = "/storage/emulated/0/Downloads" return downloads_dir @lru_cache(maxsize=1) def _android_pictures_folder() -> str: """:returns: pictures folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import] context = autoclass("android.content.Context") environment = autoclass("android.os.Environment") pictures_dir: str = context.getExternalFilesDir(environment.DIRECTORY_PICTURES).getAbsolutePath() except Exception: # noqa: BLE001 pictures_dir = "/storage/emulated/0/Pictures" return pictures_dir @lru_cache(maxsize=1) def _android_videos_folder() -> str: """:returns: videos folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import] context = autoclass("android.content.Context") environment = autoclass("android.os.Environment") videos_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DCIM).getAbsolutePath() except Exception: # noqa: BLE001 videos_dir = "/storage/emulated/0/DCIM/Camera" return videos_dir @lru_cache(maxsize=1) def _android_music_folder() -> str: """:returns: music folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import] context = autoclass("android.content.Context") environment = autoclass("android.os.Environment") music_dir: str = context.getExternalFilesDir(environment.DIRECTORY_MUSIC).getAbsolutePath() except Exception: # noqa: BLE001 music_dir = "/storage/emulated/0/Music" return music_dir __all__ = [ "Android", ] platformdirs-4.9.4/src/platformdirs/api.py0000644000000000000000000003067613615410400015612 0ustar00"""Base API.""" from __future__ import annotations import os from abc import ABC, abstractmethod from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Iterator from typing import Literal class PlatformDirsABC(ABC): # noqa: PLR0904 """Abstract base class defining all platform directory properties, their :class:`~pathlib.Path` variants, and iterators. Platform-specific subclasses (e.g. :class:`~platformdirs.windows.Windows`, :class:`~platformdirs.macos.MacOS`, :class:`~platformdirs.unix.Unix`) implement the abstract properties to return the appropriate paths for each operating system. """ def __init__( # noqa: PLR0913, PLR0917 self, appname: str | None = None, appauthor: str | Literal[False] | None = None, version: str | None = None, roaming: bool = False, # noqa: FBT001, FBT002 multipath: bool = False, # noqa: FBT001, FBT002 opinion: bool = True, # noqa: FBT001, FBT002 ensure_exists: bool = False, # noqa: FBT001, FBT002 use_site_for_root: bool = False, # noqa: FBT001, FBT002 ) -> None: """Create a new platform directory. :param appname: See `appname`. :param appauthor: See `appauthor`. :param version: See `version`. :param roaming: See `roaming`. :param multipath: See `multipath`. :param opinion: See `opinion`. :param ensure_exists: See `ensure_exists`. :param use_site_for_root: See `use_site_for_root`. """ self.appname = appname #: The name of the application. self.appauthor = appauthor """The name of the app author or distributing body for this application. Typically, it is the owning company name. Defaults to `appname`. You may pass ``False`` to disable it. """ self.version = version """An optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ``.``. """ self.roaming = roaming """Whether to use the roaming appdata directory on Windows. That means that for users on a Windows network setup for roaming profiles, this user data will be synced on login (see `here `_). """ self.multipath = multipath """An optional parameter which indicates that the entire list of data dirs should be returned. By default, the first item would only be returned. Only affects ``site_data_dir`` and ``site_config_dir`` on Unix and macOS. """ self.opinion = opinion """Whether to use opinionated values. When enabled, appends an additional subdirectory for certain directories: e.g. ``Cache`` for cache and ``Logs`` for logs on Windows, ``log`` for logs on Unix. """ self.ensure_exists = ensure_exists """Optionally create the directory (and any missing parents) upon access if it does not exist. By default, no directories are created. """ self.use_site_for_root = use_site_for_root """Whether to redirect ``user_*_dir`` calls to their ``site_*_dir`` equivalents when running as root (uid 0). Only has an effect on Unix. Disabled by default for backwards compatibility. When enabled, XDG user environment variables (e.g. ``XDG_DATA_HOME``) are bypassed for the redirected directories. """ def _append_app_name_and_version(self, *base: str) -> str: params = list(base[1:]) if self.appname: params.append(self.appname) if self.version: params.append(self.version) path = os.path.join(base[0], *params) # noqa: PTH118 self._optionally_create_directory(path) return path def _optionally_create_directory(self, path: str) -> None: if self.ensure_exists: Path(path).mkdir(parents=True, exist_ok=True) def _first_item_as_path_if_multipath(self, directory: str) -> Path: if self.multipath: # If multipath is True, the first path is returned. directory = directory.partition(os.pathsep)[0] return Path(directory) @property @abstractmethod def user_data_dir(self) -> str: """:returns: data directory tied to the user""" @property @abstractmethod def site_data_dir(self) -> str: """:returns: data directory shared by users""" @property @abstractmethod def user_config_dir(self) -> str: """:returns: config directory tied to the user""" @property @abstractmethod def site_config_dir(self) -> str: """:returns: config directory shared by users""" @property @abstractmethod def user_cache_dir(self) -> str: """:returns: cache directory tied to the user""" @property @abstractmethod def site_cache_dir(self) -> str: """:returns: cache directory shared by users""" @property @abstractmethod def user_state_dir(self) -> str: """:returns: state directory tied to the user""" @property @abstractmethod def site_state_dir(self) -> str: """:returns: state directory shared by users""" @property @abstractmethod def user_log_dir(self) -> str: """:returns: log directory tied to the user""" @property @abstractmethod def site_log_dir(self) -> str: """:returns: log directory shared by users""" @property @abstractmethod def user_documents_dir(self) -> str: """:returns: documents directory tied to the user""" @property @abstractmethod def user_downloads_dir(self) -> str: """:returns: downloads directory tied to the user""" @property @abstractmethod def user_pictures_dir(self) -> str: """:returns: pictures directory tied to the user""" @property @abstractmethod def user_videos_dir(self) -> str: """:returns: videos directory tied to the user""" @property @abstractmethod def user_music_dir(self) -> str: """:returns: music directory tied to the user""" @property @abstractmethod def user_desktop_dir(self) -> str: """:returns: desktop directory tied to the user""" @property @abstractmethod def user_bin_dir(self) -> str: """:returns: bin directory tied to the user""" @property @abstractmethod def site_bin_dir(self) -> str: """:returns: bin directory shared by users""" @property @abstractmethod def user_applications_dir(self) -> str: """:returns: applications directory tied to the user""" @property @abstractmethod def site_applications_dir(self) -> str: """:returns: applications directory shared by users""" @property @abstractmethod def user_runtime_dir(self) -> str: """:returns: runtime directory tied to the user""" @property @abstractmethod def site_runtime_dir(self) -> str: """:returns: runtime directory shared by users""" @property def user_data_path(self) -> Path: """:returns: data path tied to the user""" return Path(self.user_data_dir) @property def site_data_path(self) -> Path: """:returns: data path shared by users""" return Path(self.site_data_dir) @property def user_config_path(self) -> Path: """:returns: config path tied to the user""" return Path(self.user_config_dir) @property def site_config_path(self) -> Path: """:returns: config path shared by users""" return Path(self.site_config_dir) @property def user_cache_path(self) -> Path: """:returns: cache path tied to the user""" return Path(self.user_cache_dir) @property def site_cache_path(self) -> Path: """:returns: cache path shared by users""" return Path(self.site_cache_dir) @property def user_state_path(self) -> Path: """:returns: state path tied to the user""" return Path(self.user_state_dir) @property def site_state_path(self) -> Path: """:returns: state path shared by users""" return Path(self.site_state_dir) @property def user_log_path(self) -> Path: """:returns: log path tied to the user""" return Path(self.user_log_dir) @property def site_log_path(self) -> Path: """:returns: log path shared by users""" return Path(self.site_log_dir) @property def user_documents_path(self) -> Path: """:returns: documents path tied to the user""" return Path(self.user_documents_dir) @property def user_downloads_path(self) -> Path: """:returns: downloads path tied to the user""" return Path(self.user_downloads_dir) @property def user_pictures_path(self) -> Path: """:returns: pictures path tied to the user""" return Path(self.user_pictures_dir) @property def user_videos_path(self) -> Path: """:returns: videos path tied to the user""" return Path(self.user_videos_dir) @property def user_music_path(self) -> Path: """:returns: music path tied to the user""" return Path(self.user_music_dir) @property def user_desktop_path(self) -> Path: """:returns: desktop path tied to the user""" return Path(self.user_desktop_dir) @property def user_bin_path(self) -> Path: """:returns: bin path tied to the user""" return Path(self.user_bin_dir) @property def site_bin_path(self) -> Path: """:returns: bin path shared by users""" return Path(self.site_bin_dir) @property def user_applications_path(self) -> Path: """:returns: applications path tied to the user""" return Path(self.user_applications_dir) @property def site_applications_path(self) -> Path: """:returns: applications path shared by users""" return Path(self.site_applications_dir) @property def user_runtime_path(self) -> Path: """:returns: runtime path tied to the user""" return Path(self.user_runtime_dir) @property def site_runtime_path(self) -> Path: """:returns: runtime path shared by users""" return Path(self.site_runtime_dir) def iter_config_dirs(self) -> Iterator[str]: """:yield: all user and site configuration directories.""" yield self.user_config_dir yield self.site_config_dir def iter_data_dirs(self) -> Iterator[str]: """:yield: all user and site data directories.""" yield self.user_data_dir yield self.site_data_dir def iter_cache_dirs(self) -> Iterator[str]: """:yield: all user and site cache directories.""" yield self.user_cache_dir yield self.site_cache_dir def iter_state_dirs(self) -> Iterator[str]: """:yield: all user and site state directories.""" yield self.user_state_dir yield self.site_state_dir def iter_log_dirs(self) -> Iterator[str]: """:yield: all user and site log directories.""" yield self.user_log_dir yield self.site_log_dir def iter_runtime_dirs(self) -> Iterator[str]: """:yield: all user and site runtime directories.""" yield self.user_runtime_dir yield self.site_runtime_dir def iter_config_paths(self) -> Iterator[Path]: """:yield: all user and site configuration paths.""" for path in self.iter_config_dirs(): yield Path(path) def iter_data_paths(self) -> Iterator[Path]: """:yield: all user and site data paths.""" for path in self.iter_data_dirs(): yield Path(path) def iter_cache_paths(self) -> Iterator[Path]: """:yield: all user and site cache paths.""" for path in self.iter_cache_dirs(): yield Path(path) def iter_state_paths(self) -> Iterator[Path]: """:yield: all user and site state paths.""" for path in self.iter_state_dirs(): yield Path(path) def iter_log_paths(self) -> Iterator[Path]: """:yield: all user and site log paths.""" for path in self.iter_log_dirs(): yield Path(path) def iter_runtime_paths(self) -> Iterator[Path]: """:yield: all user and site runtime paths.""" for path in self.iter_runtime_dirs(): yield Path(path) platformdirs-4.9.4/src/platformdirs/macos.py0000644000000000000000000001727513615410400016143 0ustar00"""macOS.""" from __future__ import annotations import os.path import sys from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Iterator from ._xdg import XDGMixin from .api import PlatformDirsABC if TYPE_CHECKING: from pathlib import Path class _MacOSDefaults(PlatformDirsABC): # noqa: PLR0904 """Default platform directories for macOS without XDG environment variable overrides. Follows the guidance from `Apple's File System Programming Guide `_. The XDG env var handling is in :class:`~platformdirs._xdg.XDGMixin`. """ @property def user_data_dir(self) -> str: """:returns: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support")) # noqa: PTH111 @property def _site_data_dirs(self) -> list[str]: is_homebrew = "/opt/python" in sys.prefix homebrew_prefix = sys.prefix.split("/opt/python")[0] if is_homebrew else "" path_list = [self._append_app_name_and_version(f"{homebrew_prefix}/share")] if is_homebrew else [] path_list.append(self._append_app_name_and_version("/Library/Application Support")) return path_list @property def site_data_path(self) -> Path: """:returns: data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``""" return self._first_item_as_path_if_multipath(self.site_data_dir) @property def user_config_dir(self) -> str: """:returns: config directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def _site_config_dirs(self) -> list[str]: return self._site_data_dirs @property def user_cache_dir(self) -> str: """:returns: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches")) # noqa: PTH111 @property def site_cache_dir(self) -> str: """:returns: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``. If we're using a Python binary managed by `Homebrew `_, the directory will be under the Homebrew prefix, e.g. ``$homebrew_prefix/var/cache/$appname/$version``. If `multipath ` is enabled, and we're in Homebrew, the response is a multi-path string separated by ":", e.g. ``$homebrew_prefix/var/cache/$appname/$version:/Library/Caches/$appname/$version``""" is_homebrew = "/opt/python" in sys.prefix homebrew_prefix = sys.prefix.split("/opt/python")[0] if is_homebrew else "" path_list = [self._append_app_name_and_version(f"{homebrew_prefix}/var/cache")] if is_homebrew else [] path_list.append(self._append_app_name_and_version("/Library/Caches")) if self.multipath: return os.pathsep.join(path_list) return path_list[0] @property def site_cache_path(self) -> Path: """:returns: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``""" return self._first_item_as_path_if_multipath(self.site_cache_dir) @property def user_state_dir(self) -> str: """:returns: state directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_state_dir(self) -> str: """:returns: state directory shared by users, same as `site_data_dir`""" return self.site_data_dir @property def user_log_dir(self) -> str: """:returns: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) # noqa: PTH111 @property def site_log_dir(self) -> str: """:returns: log directory shared by users, e.g. ``/Library/Logs/$appname/$version``""" return self._append_app_name_and_version("/Library/Logs") @property def user_documents_dir(self) -> str: """:returns: documents directory tied to the user, e.g. ``~/Documents``""" return os.path.expanduser("~/Documents") # noqa: PTH111 @property def user_downloads_dir(self) -> str: """:returns: downloads directory tied to the user, e.g. ``~/Downloads``""" return os.path.expanduser("~/Downloads") # noqa: PTH111 @property def user_pictures_dir(self) -> str: """:returns: pictures directory tied to the user, e.g. ``~/Pictures``""" return os.path.expanduser("~/Pictures") # noqa: PTH111 @property def user_videos_dir(self) -> str: """:returns: videos directory tied to the user, e.g. ``~/Movies``""" return os.path.expanduser("~/Movies") # noqa: PTH111 @property def user_music_dir(self) -> str: """:returns: music directory tied to the user, e.g. ``~/Music``""" return os.path.expanduser("~/Music") # noqa: PTH111 @property def user_desktop_dir(self) -> str: """:returns: desktop directory tied to the user, e.g. ``~/Desktop``""" return os.path.expanduser("~/Desktop") # noqa: PTH111 @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, e.g. ``~/.local/bin``""" return os.path.expanduser("~/.local/bin") # noqa: PTH111 @property def site_bin_dir(self) -> str: """:returns: bin directory shared by users, e.g. ``/usr/local/bin``""" return "/usr/local/bin" @property def user_applications_dir(self) -> str: """:returns: applications directory tied to the user, e.g. ``~/Applications``""" return os.path.expanduser("~/Applications") # noqa: PTH111 @property def _site_applications_dirs(self) -> list[str]: return ["/Applications"] @property def site_applications_dir(self) -> str: """:returns: applications directory shared by users, e.g. ``/Applications``""" dirs = self._site_applications_dirs return os.pathsep.join(dirs) if self.multipath else dirs[0] @property def user_runtime_dir(self) -> str: """:returns: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) # noqa: PTH111 @property def site_runtime_dir(self) -> str: """:returns: runtime directory shared by users, same as `user_runtime_dir`""" return self.user_runtime_dir def iter_config_dirs(self) -> Iterator[str]: """:yield: all user and site configuration directories.""" yield self.user_config_dir yield from self._site_config_dirs def iter_data_dirs(self) -> Iterator[str]: """:yield: all user and site data directories.""" yield self.user_data_dir yield from self._site_data_dirs class MacOS(XDGMixin, _MacOSDefaults): """Platform directories for the macOS operating system. Follows the guidance from `Apple documentation `_. Makes use of the `appname `, `version `, `ensure_exists `. XDG environment variables (e.g. ``$XDG_DATA_HOME``) are supported and take precedence over macOS defaults. """ __all__ = [ "MacOS", ] platformdirs-4.9.4/src/platformdirs/py.typed0000644000000000000000000000000013615410400016140 0ustar00platformdirs-4.9.4/src/platformdirs/unix.py0000644000000000000000000002763513615410400016025 0ustar00"""Unix.""" from __future__ import annotations import os import sys from configparser import ConfigParser from functools import cached_property from pathlib import Path from tempfile import gettempdir from typing import TYPE_CHECKING, NoReturn from ._xdg import XDGMixin from .api import PlatformDirsABC if TYPE_CHECKING: from collections.abc import Iterator if sys.platform == "win32": def getuid() -> NoReturn: msg = "should only be used on Unix" raise RuntimeError(msg) else: from os import getuid class _UnixDefaults(PlatformDirsABC): # noqa: PLR0904 """Default directories for Unix/Linux without XDG environment variable overrides. The XDG env var handling is in :class:`~platformdirs._xdg.XDGMixin`. """ @cached_property def _use_site(self) -> bool: return self.use_site_for_root and getuid() == 0 @property def user_data_dir(self) -> str: """:returns: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or ``$XDG_DATA_HOME/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/.local/share")) # noqa: PTH111 @property def _site_data_dirs(self) -> list[str]: return [self._append_app_name_and_version("/usr/local/share"), self._append_app_name_and_version("/usr/share")] @property def user_config_dir(self) -> str: """:returns: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or ``$XDG_CONFIG_HOME/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/.config")) # noqa: PTH111 @property def _site_config_dirs(self) -> list[str]: return [self._append_app_name_and_version("/etc/xdg")] @property def user_cache_dir(self) -> str: """:returns: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or ``$XDG_CACHE_HOME/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/.cache")) # noqa: PTH111 @property def site_cache_dir(self) -> str: """:returns: cache directory shared by users, e.g. ``/var/cache/$appname/$version``""" return self._append_app_name_and_version("/var/cache") @property def user_state_dir(self) -> str: """:returns: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or ``$XDG_STATE_HOME/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/.local/state")) # noqa: PTH111 @property def site_state_dir(self) -> str: """:returns: state directory shared by users, e.g. ``/var/lib/$appname/$version``""" return self._append_app_name_and_version("/var/lib") @property def user_log_dir(self) -> str: """:returns: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it""" path = self.user_state_dir if self.opinion: path = os.path.join(path, "log") # noqa: PTH118 self._optionally_create_directory(path) return path @property def site_log_dir(self) -> str: """:returns: log directory shared by users, e.g. ``/var/log/$appname/$version`` Unlike `user_log_dir`, ``opinion`` has no effect since ``/var/log`` is inherently a log directory. """ return self._append_app_name_and_version("/var/log") @property def user_documents_dir(self) -> str: """:returns: documents directory tied to the user, e.g. ``~/Documents``""" return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents") @property def user_downloads_dir(self) -> str: """:returns: downloads directory tied to the user, e.g. ``~/Downloads``""" return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads") @property def user_pictures_dir(self) -> str: """:returns: pictures directory tied to the user, e.g. ``~/Pictures``""" return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures") @property def user_videos_dir(self) -> str: """:returns: videos directory tied to the user, e.g. ``~/Videos``""" return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos") @property def user_music_dir(self) -> str: """:returns: music directory tied to the user, e.g. ``~/Music``""" return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music") @property def user_desktop_dir(self) -> str: """:returns: desktop directory tied to the user, e.g. ``~/Desktop``""" return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop") @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, e.g. ``~/.local/bin``""" return os.path.expanduser("~/.local/bin") # noqa: PTH111 @property def site_bin_dir(self) -> str: """:returns: bin directory shared by users, e.g. ``/usr/local/bin``""" return "/usr/local/bin" @property def user_applications_dir(self) -> str: """:returns: applications directory tied to the user, e.g. ``~/.local/share/applications``""" return os.path.join(os.path.expanduser("~/.local/share"), "applications") # noqa: PTH111, PTH118 @property def _site_applications_dirs(self) -> list[str]: return [os.path.join(p, "applications") for p in ["/usr/local/share", "/usr/share"]] # noqa: PTH118 @property def site_applications_dir(self) -> str: """:returns: applications directory shared by users, e.g. ``/usr/share/applications``""" dirs = self._site_applications_dirs return os.pathsep.join(dirs) if self.multipath else dirs[0] @property def user_runtime_dir(self) -> str: """:returns: runtime directory tied to the user, e.g. ``$XDG_RUNTIME_DIR/$appname/$version``. If ``$XDG_RUNTIME_DIR`` is unset, tries the platform default (``/tmp/run/user/$(id -u)`` on OpenBSD, ``/var/run/user/$(id -u)`` on FreeBSD/NetBSD, ``/run/user/$(id -u)`` otherwise). If the default is not writable, falls back to a temporary directory. """ if sys.platform.startswith("openbsd"): path = f"/tmp/run/user/{getuid()}" # noqa: S108 elif sys.platform.startswith(("freebsd", "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 def site_runtime_dir(self) -> str: """:returns: runtime directory shared by users, e.g. ``/run/$appname/$version`` or ``$XDG_RUNTIME_DIR/$appname/$version``. Note that this behaves almost exactly like `user_runtime_dir` if ``$XDG_RUNTIME_DIR`` is set, but will fall back to paths associated to the root user instead of a regular logged-in user if it's not set. If you wish to ensure that a logged-in root user path is returned e.g. ``/run/user/0``, use `user_runtime_dir` instead. For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/$appname/$version`` if ``$XDG_RUNTIME_DIR`` is not set. """ if sys.platform.startswith(("freebsd", "openbsd", "netbsd")): path = "/var/run" else: path = "/run" return self._append_app_name_and_version(path) @property def site_data_path(self) -> Path: """:returns: data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``""" return self._first_item_as_path_if_multipath(self.site_data_dir) @property def site_config_path(self) -> Path: """:returns: config path shared by users, returns the first item, even if ``multipath`` is set to ``True``""" return self._first_item_as_path_if_multipath(self.site_config_dir) @property def site_cache_path(self) -> Path: """:returns: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``""" return self._first_item_as_path_if_multipath(self.site_cache_dir) def iter_config_dirs(self) -> Iterator[str]: """:yield: all user and site configuration directories.""" yield self.user_config_dir yield from self._site_config_dirs def iter_data_dirs(self) -> Iterator[str]: """:yield: all user and site data directories.""" yield self.user_data_dir yield from self._site_data_dirs class Unix(XDGMixin, _UnixDefaults): """On Unix/Linux, we follow the `XDG Basedir Spec `_. The spec allows overriding directories with environment variables. The examples shown are the default values, alongside the name of the environment variable that overrides them. Makes use of the `appname `, `version `, `multipath `, `opinion `, `ensure_exists `. """ @property def user_data_dir(self) -> str: """:returns: data directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_data_dir if self._use_site else super().user_data_dir @property def user_config_dir(self) -> str: """:returns: config directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_config_dir if self._use_site else super().user_config_dir @property def user_cache_dir(self) -> str: """:returns: cache directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_cache_dir if self._use_site else super().user_cache_dir @property def user_state_dir(self) -> str: """:returns: state directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_state_dir if self._use_site else super().user_state_dir @property def user_log_dir(self) -> str: """:returns: log directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_log_dir if self._use_site else super().user_log_dir @property def user_applications_dir(self) -> str: """:returns: applications directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_applications_dir if self._use_site else super().user_applications_dir @property def user_runtime_dir(self) -> str: """:returns: runtime directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_runtime_dir if self._use_site else super().user_runtime_dir @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, or site equivalent when root with ``use_site_for_root``""" return self.site_bin_dir if self._use_site else super().user_bin_dir def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str: if media_dir := _get_user_dirs_folder(env_var): return media_dir return os.path.expanduser(fallback_tilde_path) # noqa: PTH111 def _get_user_dirs_folder(key: str) -> str | None: """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/. """ config_home = os.environ.get("XDG_CONFIG_HOME", "").strip() or os.path.expanduser("~/.config") # noqa: PTH111 user_dirs_config_path = Path(config_home) / "user-dirs.dirs" if user_dirs_config_path.exists(): parser = ConfigParser() with user_dirs_config_path.open() as stream: parser.read_string(f"[top]\n{stream.read()}") if key not in parser["top"]: return None path = parser["top"][key].strip('"') return path.replace("$HOME", os.path.expanduser("~")) # noqa: PTH111 return None __all__ = [ "Unix", ] platformdirs-4.9.4/src/platformdirs/version.py0000644000000000000000000000130013615410400016504 0ustar00# file generated by setuptools-scm # don't change, don't track in version control __all__ = [ "__version__", "__version_tuple__", "version", "version_tuple", "__commit_id__", "commit_id", ] TYPE_CHECKING = False if TYPE_CHECKING: from typing import Tuple from typing import Union VERSION_TUPLE = Tuple[Union[int, str], ...] COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE commit_id: COMMIT_ID __commit_id__: COMMIT_ID __version__ = version = '4.9.4' __version_tuple__ = version_tuple = (4, 9, 4) __commit_id__ = commit_id = None platformdirs-4.9.4/src/platformdirs/windows.py0000644000000000000000000003434413615410400016527 0ustar00"""Windows.""" from __future__ import annotations import os import sys from typing import TYPE_CHECKING, Final from .api import PlatformDirsABC if TYPE_CHECKING: from collections.abc import Callable # Not exposed by CPython; defined in the Windows SDK (shlobj_core.h) _KF_FLAG_DONT_VERIFY: Final[int] = 0x00004000 class Windows(PlatformDirsABC): # noqa: PLR0904 """`MSDN on where to store app data files `_. Makes use of the `appname `, `appauthor `, `version `, `roaming `, `opinion `, `ensure_exists `. """ @property def user_data_dir(self) -> str: r""":returns: data directory tied to the user, e.g. ``%USERPROFILE%\AppData\Local\$appauthor\$appname`` (not roaming) or ``%USERPROFILE%\AppData\Roaming\$appauthor\$appname`` (roaming)""" const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA" path = os.path.normpath(get_win_folder(const)) return self._append_parts(path) def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str: params = [] if self.appname: if self.appauthor is not False: author = self.appauthor or self.appname params.append(author) params.append(self.appname) if opinion_value is not None and self.opinion: params.append(opinion_value) if self.version: params.append(self.version) path = os.path.join(path, *params) # noqa: PTH118 self._optionally_create_directory(path) return path @property def site_data_dir(self) -> str: r""":returns: data directory shared by users, e.g. ``C:\ProgramData\$appauthor\$appname``""" path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA")) return self._append_parts(path) @property def user_config_dir(self) -> str: """:returns: config directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_config_dir(self) -> str: """:returns: config directory shared by users, same as `site_data_dir`""" return self.site_data_dir @property def user_cache_dir(self) -> str: r""":returns: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g. ``%USERPROFILE%\AppData\Local\$appauthor\$appname\Cache\$version``""" path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA")) return self._append_parts(path, opinion_value="Cache") @property def site_cache_dir(self) -> str: r""":returns: cache directory shared by users, e.g. ``C:\ProgramData\$appauthor\$appname\Cache\$version``""" path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA")) return self._append_parts(path, opinion_value="Cache") @property def user_state_dir(self) -> str: """:returns: state directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_state_dir(self) -> str: """:returns: state directory shared by users, same as `site_data_dir`""" return self.site_data_dir @property def user_log_dir(self) -> str: """:returns: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it""" path = self.user_data_dir if self.opinion: path = os.path.join(path, "Logs") # noqa: PTH118 self._optionally_create_directory(path) return path @property def site_log_dir(self) -> str: """:returns: log directory shared by users, same as `site_data_dir` if not opinionated else ``Logs`` in it""" path = self.site_data_dir if self.opinion: path = os.path.join(path, "Logs") # noqa: PTH118 self._optionally_create_directory(path) return path @property def user_documents_dir(self) -> str: r""":returns: documents directory tied to the user e.g. ``%USERPROFILE%\Documents``""" return os.path.normpath(get_win_folder("CSIDL_PERSONAL")) @property def user_downloads_dir(self) -> str: r""":returns: downloads directory tied to the user e.g. ``%USERPROFILE%\Downloads``""" return os.path.normpath(get_win_folder("CSIDL_DOWNLOADS")) @property def user_pictures_dir(self) -> str: r""":returns: pictures directory tied to the user e.g. ``%USERPROFILE%\Pictures``""" return os.path.normpath(get_win_folder("CSIDL_MYPICTURES")) @property def user_videos_dir(self) -> str: r""":returns: videos directory tied to the user e.g. ``%USERPROFILE%\Videos``""" return os.path.normpath(get_win_folder("CSIDL_MYVIDEO")) @property def user_music_dir(self) -> str: r""":returns: music directory tied to the user e.g. ``%USERPROFILE%\Music``""" return os.path.normpath(get_win_folder("CSIDL_MYMUSIC")) @property def user_desktop_dir(self) -> str: r""":returns: desktop directory tied to the user, e.g. ``%USERPROFILE%\Desktop``""" return os.path.normpath(get_win_folder("CSIDL_DESKTOPDIRECTORY")) @property def user_bin_dir(self) -> str: r""":returns: bin directory tied to the user, e.g. ``%LOCALAPPDATA%\Programs``""" return os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Programs")) # noqa: PTH118 @property def site_bin_dir(self) -> str: """:returns: bin directory shared by users, e.g. ``C:\\ProgramData\bin``""" return os.path.normpath(os.path.join(get_win_folder("CSIDL_COMMON_APPDATA"), "bin")) # noqa: PTH118 @property def user_applications_dir(self) -> str: r""":returns: applications directory tied to the user, e.g. ``Start Menu\Programs``""" return os.path.normpath(get_win_folder("CSIDL_PROGRAMS")) @property def site_applications_dir(self) -> str: r""":returns: applications directory shared by users, e.g. ``C:\ProgramData\Microsoft\Windows\Start Menu\Programs``""" return os.path.normpath(get_win_folder("CSIDL_COMMON_PROGRAMS")) @property def user_runtime_dir(self) -> str: r""":returns: runtime directory tied to the user, e.g. ``%USERPROFILE%\AppData\Local\Temp\$appauthor\$appname``""" path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) # noqa: PTH118 return self._append_parts(path) @property def site_runtime_dir(self) -> str: """:returns: runtime directory shared by users, same as `user_runtime_dir`""" return self.user_runtime_dir def get_win_folder_from_env_vars(csidl_name: str) -> str: """Get folder from environment variables.""" result = get_win_folder_if_csidl_name_not_env_var(csidl_name) if result is not None: return result env_var_name = { "CSIDL_APPDATA": "APPDATA", "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE", "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA", }.get(csidl_name) if env_var_name is None: msg = f"Unknown CSIDL name: {csidl_name}" raise ValueError(msg) result = os.environ.get(env_var_name) if result is None: msg = f"Unset environment variable: {env_var_name}" raise ValueError(msg) return result def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None: # noqa: PLR0911 """Get a folder for a CSIDL name that does not exist as an environment variable.""" if csidl_name == "CSIDL_PERSONAL": return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents") # noqa: PTH118 if csidl_name == "CSIDL_DOWNLOADS": return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Downloads") # noqa: PTH118 if csidl_name == "CSIDL_MYPICTURES": return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures") # noqa: PTH118 if csidl_name == "CSIDL_MYVIDEO": return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos") # noqa: PTH118 if csidl_name == "CSIDL_MYMUSIC": return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music") # noqa: PTH118 if csidl_name == "CSIDL_PROGRAMS": return os.path.join( # noqa: PTH118 os.path.normpath(os.environ["APPDATA"]), "Microsoft", "Windows", "Start Menu", "Programs", ) if csidl_name == "CSIDL_COMMON_PROGRAMS": return os.path.join( # noqa: PTH118 os.path.normpath(os.environ.get("PROGRAMDATA", os.environ.get("ALLUSERSPROFILE", "C:\\ProgramData"))), "Microsoft", "Windows", "Start Menu", "Programs", ) return None def get_win_folder_from_registry(csidl_name: str) -> str: """Get folder from the registry. This is a fallback technique at best. I'm not sure if using the registry for these guarantees us the correct answer for all CSIDL_* names. """ machine_names = { "CSIDL_COMMON_APPDATA", "CSIDL_COMMON_PROGRAMS", } shell_folder_name = { "CSIDL_APPDATA": "AppData", "CSIDL_COMMON_APPDATA": "Common AppData", "CSIDL_LOCAL_APPDATA": "Local AppData", "CSIDL_PERSONAL": "Personal", "CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}", "CSIDL_MYPICTURES": "My Pictures", "CSIDL_MYVIDEO": "My Video", "CSIDL_MYMUSIC": "My Music", "CSIDL_PROGRAMS": "Programs", "CSIDL_COMMON_PROGRAMS": "Common Programs", }.get(csidl_name) if shell_folder_name is None: msg = f"Unknown CSIDL name: {csidl_name}" raise ValueError(msg) if sys.platform != "win32": # only needed for mypy type checker to know that this code runs only on Windows raise NotImplementedError import winreg # noqa: PLC0415 # 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) _KNOWN_FOLDER_GUIDS: dict[str, str] = { "CSIDL_APPDATA": "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}", "CSIDL_COMMON_APPDATA": "{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}", "CSIDL_LOCAL_APPDATA": "{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}", "CSIDL_PERSONAL": "{FDD39AD0-238F-46AF-ADB4-6C85480369C7}", "CSIDL_MYPICTURES": "{33E28130-4E1E-4676-835A-98395C3BC3BB}", "CSIDL_MYVIDEO": "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}", "CSIDL_MYMUSIC": "{4BD8D571-6D19-48D3-BE97-422220080E43}", "CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}", "CSIDL_DESKTOPDIRECTORY": "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}", "CSIDL_PROGRAMS": "{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}", "CSIDL_COMMON_PROGRAMS": "{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}", } def get_win_folder_via_ctypes(csidl_name: str) -> str: """Get folder via :func:`SHGetKnownFolderPath`. See https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath. """ if sys.platform != "win32": # only needed for type checker to know that this code runs only on Windows raise NotImplementedError from ctypes import HRESULT, POINTER, Structure, WinDLL, byref, create_unicode_buffer, wintypes # noqa: PLC0415 class _GUID(Structure): _fields_ = [ ("Data1", wintypes.DWORD), ("Data2", wintypes.WORD), ("Data3", wintypes.WORD), ("Data4", wintypes.BYTE * 8), ] ole32 = WinDLL("ole32") ole32.CLSIDFromString.restype = HRESULT ole32.CLSIDFromString.argtypes = [wintypes.LPCOLESTR, POINTER(_GUID)] ole32.CoTaskMemFree.restype = None ole32.CoTaskMemFree.argtypes = [wintypes.LPVOID] shell32 = WinDLL("shell32") shell32.SHGetKnownFolderPath.restype = HRESULT shell32.SHGetKnownFolderPath.argtypes = [POINTER(_GUID), wintypes.DWORD, wintypes.HANDLE, POINTER(wintypes.LPWSTR)] kernel32 = WinDLL("kernel32") kernel32.GetShortPathNameW.restype = wintypes.DWORD kernel32.GetShortPathNameW.argtypes = [wintypes.LPWSTR, wintypes.LPWSTR, wintypes.DWORD] folder_guid = _KNOWN_FOLDER_GUIDS.get(csidl_name) if folder_guid is None: msg = f"Unknown CSIDL name: {csidl_name}" raise ValueError(msg) guid = _GUID() ole32.CLSIDFromString(folder_guid, byref(guid)) path_ptr = wintypes.LPWSTR() shell32.SHGetKnownFolderPath(byref(guid), _KF_FLAG_DONT_VERIFY, None, byref(path_ptr)) result = path_ptr.value ole32.CoTaskMemFree(path_ptr) if result is None: msg = f"SHGetKnownFolderPath returned NULL for {csidl_name}" raise ValueError(msg) if any(ord(c) > 255 for c in result): # noqa: PLR2004 buf = create_unicode_buffer(1024) if kernel32.GetShortPathNameW(result, buf, 1024): result = buf.value return result def _pick_get_win_folder() -> Callable[[str], str]: """Select the best method to resolve Windows folder paths: ctypes, then registry, then environment variables.""" try: import ctypes # noqa: PLC0415, F401 except ImportError: pass else: return get_win_folder_via_ctypes try: import winreg # noqa: PLC0415, F401 except ImportError: return get_win_folder_from_env_vars else: return get_win_folder_from_registry _resolve_win_folder = _pick_get_win_folder() def get_win_folder(csidl_name: str) -> str: """Get a Windows folder path, checking for ``WIN_PD_OVERRIDE_*`` environment variable overrides first. For example, ``CSIDL_LOCAL_APPDATA`` can be overridden by setting ``WIN_PD_OVERRIDE_LOCAL_APPDATA``. """ 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) __all__ = [ "Windows", ] platformdirs-4.9.4/tests/conftest.py0000644000000000000000000000172313615410400014522 0ustar00from __future__ import annotations from typing import TYPE_CHECKING, cast import pytest if TYPE_CHECKING: from _pytest.fixtures import SubRequest PROPS = ( "user_data_dir", "user_config_dir", "user_cache_dir", "user_state_dir", "user_log_dir", "user_documents_dir", "user_downloads_dir", "user_pictures_dir", "user_videos_dir", "user_music_dir", "user_bin_dir", "site_bin_dir", "user_applications_dir", "user_runtime_dir", "site_data_dir", "site_config_dir", "site_cache_dir", "site_state_dir", "site_log_dir", "site_applications_dir", "site_runtime_dir", ) @pytest.fixture(params=PROPS) def func(request: SubRequest) -> str: return cast("str", request.param) @pytest.fixture(params=PROPS) def func_path(request: SubRequest) -> str: prop = cast("str", request.param) return prop.replace("_dir", "_path") @pytest.fixture def props() -> tuple[str, ...]: return PROPS platformdirs-4.9.4/tests/test_android.py0000644000000000000000000001623513615410400015360 0ustar00from __future__ import annotations import sys from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock import pytest from platformdirs.android import Android if TYPE_CHECKING: from pathlib import Path from pytest_mock import MockerFixture @pytest.mark.parametrize( "params", [ {}, {"appname": "foo"}, {"appname": "foo", "appauthor": "bar"}, {"appname": "foo", "appauthor": "bar", "version": "v1.0"}, {"appname": "foo", "appauthor": "bar", "version": "v1.0", "opinion": False}, ], ids=[ "no_args", "app_name", "app_name_with_app_author", "app_name_author_version", "app_name_author_version_false_opinion", ], ) def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> None: mocker.patch("platformdirs.android._android_folder", return_value="/data/data/com.example", autospec=True) mocker.patch("platformdirs.android.os.path.join", lambda *args: "/".join(args)) result = getattr(Android(**params), func) suffix_elements = [] if "appname" in params: suffix_elements.append(params["appname"]) if "version" in params: suffix_elements.append(params["version"]) if suffix_elements: suffix_elements.insert(0, "") suffix = "/".join(suffix_elements) val = "/tmp" # noqa: S108 expected_map = { "user_data_dir": f"/data/data/com.example/files{suffix}", "site_data_dir": f"/data/data/com.example/files{suffix}", "user_config_dir": f"/data/data/com.example/shared_prefs{suffix}", "site_config_dir": f"/data/data/com.example/shared_prefs{suffix}", "user_cache_dir": f"/data/data/com.example/cache{suffix}", "site_cache_dir": f"/data/data/com.example/cache{suffix}", "user_state_dir": f"/data/data/com.example/files{suffix}", "site_state_dir": f"/data/data/com.example/files{suffix}", "user_log_dir": f"/data/data/com.example/cache{suffix}{'' if params.get('opinion', True) is False else '/log'}", "site_log_dir": f"/data/data/com.example/cache{suffix}{'' if params.get('opinion', True) is False else '/log'}", "user_documents_dir": "/storage/emulated/0/Documents", "user_downloads_dir": "/storage/emulated/0/Downloads", "user_pictures_dir": "/storage/emulated/0/Pictures", "user_videos_dir": "/storage/emulated/0/DCIM/Camera", "user_music_dir": "/storage/emulated/0/Music", "user_desktop_dir": "/storage/emulated/0/Desktop", "user_bin_dir": "/data/data/com.example/files/bin", "site_bin_dir": "/data/data/com.example/files/bin", "user_applications_dir": f"/data/data/com.example/files{suffix}", "site_applications_dir": f"/data/data/com.example/files{suffix}", "user_runtime_dir": f"/data/data/com.example/cache{suffix}{'' if not params.get('opinion', True) else val}", "site_runtime_dir": f"/data/data/com.example/cache{suffix}{'' if not params.get('opinion', True) else val}", } expected = expected_map[func] assert result == expected def test_android_folder_from_jnius(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch) -> None: from platformdirs import PlatformDirs # noqa: PLC0415 from platformdirs.android import _android_folder # noqa: PLC0415 mocker.patch.dict(sys.modules, {"android": MagicMock(side_effect=ModuleNotFoundError)}) monkeypatch.delitem(__import__("sys").modules, "android") _android_folder.cache_clear() if PlatformDirs is Android: import jnius # pragma: no cover # noqa: PLC0415 autoclass = mocker.spy(jnius, "autoclass") # pragma: no cover else: parent = MagicMock(return_value=MagicMock(getAbsolutePath=MagicMock(return_value="/A"))) # pragma: no cover context = MagicMock(getFilesDir=MagicMock(return_value=MagicMock(getParentFile=parent))) # pragma: no cover autoclass = MagicMock(return_value=context) # pragma: no cover mocker.patch.dict(sys.modules, {"jnius": MagicMock(autoclass=autoclass)}) # pragma: no cover result = _android_folder() assert result == "/A" assert autoclass.call_count == 1 assert autoclass.call_args[0] == ("android.content.Context",) assert _android_folder() is result assert autoclass.call_count == 1 def test_android_folder_from_p4a(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch) -> None: from platformdirs.android import _android_folder # noqa: PLC0415 mocker.patch.dict(sys.modules, {"jnius": MagicMock(side_effect=ModuleNotFoundError)}) monkeypatch.delitem(__import__("sys").modules, "jnius") _android_folder.cache_clear() get_absolute_path = MagicMock(return_value="/A") get_parent_file = MagicMock(getAbsolutePath=get_absolute_path) get_files_dir = MagicMock(getParentFile=MagicMock(return_value=get_parent_file)) get_application_context = MagicMock(getFilesDir=MagicMock(return_value=get_files_dir)) m_activity = MagicMock(getApplicationContext=MagicMock(return_value=get_application_context)) mocker.patch.dict(sys.modules, {"android": MagicMock(mActivity=m_activity)}) result = _android_folder() assert result == "/A" assert get_absolute_path.call_count == 1 assert _android_folder() is result assert get_absolute_path.call_count == 1 @pytest.mark.parametrize( "path", [ "/data/user/1/a/files", "/data/data/a/files", "/mnt/expand/8e06fc2f-a86a-44e8-81ce-109e0eedd5ed/user/1/a/files", ], ) def test_android_folder_from_sys_path(mocker: MockerFixture, path: str, monkeypatch: pytest.MonkeyPatch) -> None: mocker.patch.dict(sys.modules, {"jnius": MagicMock(side_effect=ModuleNotFoundError)}) monkeypatch.delitem(__import__("sys").modules, "jnius") mocker.patch.dict(sys.modules, {"android": MagicMock(side_effect=ModuleNotFoundError)}) monkeypatch.delitem(__import__("sys").modules, "android") from platformdirs.android import _android_folder # noqa: PLC0415 _android_folder.cache_clear() monkeypatch.setattr(sys, "path", ["/A", "/B", path]) result = _android_folder() assert result == path[: -len("/files")] def test_android_folder_not_found(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch) -> None: mocker.patch.dict(sys.modules, {"jnius": MagicMock(autoclass=MagicMock(side_effect=ModuleNotFoundError))}) from platformdirs.android import _android_folder # noqa: PLC0415 _android_folder.cache_clear() monkeypatch.setattr(sys, "path", []) assert _android_folder() is None @pytest.mark.parametrize( ("prop", "subdir"), [ ("user_log_dir", "log"), ("user_runtime_dir", "tmp"), ], ) def test_android_ensure_exists_creates_opinion_subdir( mocker: MockerFixture, tmp_path: Path, prop: str, subdir: str, ) -> None: from pathlib import Path # noqa: PLC0415 mocker.patch("platformdirs.android._android_folder", return_value=str(tmp_path), autospec=True) cache_dir = tmp_path / "cache" cache_dir.mkdir() dirs = Android(appname="myapp", ensure_exists=True) result = getattr(dirs, prop) expected = str(cache_dir / "myapp" / subdir) assert result == expected assert Path(result).is_dir() platformdirs-4.9.4/tests/test_api.py0000644000000000000000000001132513615410400014504 0ustar00from __future__ import annotations import builtins import functools import inspect import sys from pathlib import Path from typing import TYPE_CHECKING, Any import pytest import platformdirs from platformdirs.android import Android builtin_import = builtins.__import__ if TYPE_CHECKING: from collections.abc import Callable, Mapping, Sequence from types import ModuleType def test_package_metadata() -> None: assert hasattr(platformdirs, "__version__") assert hasattr(platformdirs, "__version_info__") def test_method_result_is_str(func: str) -> None: method = getattr(platformdirs, func) result = method() assert isinstance(result, str) def test_property_result_is_str(func: str) -> None: dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0") result = getattr(dirs, func) assert isinstance(result, str) def test_method_result_is_path(func_path: str) -> None: method = getattr(platformdirs, func_path) result = method() assert isinstance(result, Path) def test_property_result_is_path(func_path: str) -> None: dirs = platformdirs.PlatformDirs("MyApp", "MyCompany", version="1.0") result = getattr(dirs, func_path) assert isinstance(result, Path) def test_function_interface_is_in_sync(func: str) -> None: function_dir = getattr(platformdirs, func) function_path = getattr(platformdirs, func.replace("_dir", "_path")) assert inspect.isfunction(function_dir) assert inspect.isfunction(function_path) function_dir_signature = inspect.Signature.from_callable(function_dir) function_path_signature = inspect.Signature.from_callable(function_path) assert function_dir_signature.parameters == function_path_signature.parameters @pytest.mark.parametrize("root", ["A", "/system", None]) @pytest.mark.parametrize("data", ["D", "/data", None]) @pytest.mark.parametrize("path", ["/data/data/a/files", "/C"]) @pytest.mark.parametrize("shell", ["/data/data/com.app/files/usr/bin/sh", "/usr/bin/sh", None]) @pytest.mark.parametrize("prefix", ["/data/data/com.termux/files/usr", None]) def test_android_active( # noqa: PLR0913 monkeypatch: pytest.MonkeyPatch, root: str | None, data: str | None, path: str, shell: str | None, prefix: str | None, ) -> None: for env_var, value in {"ANDROID_DATA": data, "ANDROID_ROOT": root, "SHELL": shell, "PREFIX": prefix}.items(): if value is None: monkeypatch.delenv(env_var, raising=False) else: monkeypatch.setenv(env_var, value) from platformdirs.android import _android_folder # noqa: PLC0415 _android_folder.cache_clear() monkeypatch.setattr(sys, "path", ["/A", "/B", path]) expected = ( root == "/system" and data == "/data" and shell is None and prefix is None and _android_folder() is not None ) if expected: 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, globals: Mapping[str, object] | None = None, # noqa: A002 locals: Mapping[str, object] | None = None, # noqa: A002 fromlist: Sequence[str] | None = (), level: int = 0, ) -> ModuleType: if name == "ctypes": msg = f"No module named {name}" raise ModuleNotFoundError(msg) return builtin_import(name, globals, locals, fromlist, level) def mock_import(func: Callable[..., None]) -> Callable[..., None]: @functools.wraps(func) 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 # ty: ignore[invalid-assignment] for name, _ in platformdirs_module_items: del sys.modules[name] return func(*args, **kwargs) 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(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 # This is a static type-checking test to ensure we work around # the following mypy issue: https://github.com/python/mypy/issues/10962 class PlatformDirsSubclass(platformdirs.PlatformDirs): ... class AppDirsSubclass(platformdirs.AppDirs): ... platformdirs-4.9.4/tests/test_comp_with_appdirs.py0000644000000000000000000000451513615410400017451 0ustar00from __future__ import annotations import sys from inspect import getmembers, isfunction from typing import Any import appdirs import pytest import platformdirs def test_has_backward_compatible_class() -> None: from platformdirs import AppDirs # noqa: PLC0415 assert AppDirs is platformdirs.PlatformDirs def test_has_all_functions() -> None: # Get all public function names from appdirs appdirs_function_names = [f[0] for f in getmembers(appdirs, isfunction) if not f[0].startswith("_")] # Exception will be raised if any appdirs functions aren't in platformdirs. for function_name in appdirs_function_names: getattr(platformdirs, function_name) def test_has_all_properties() -> None: # Get names of all the properties of appdirs.AppDirs appdirs_property_names = [p[0] for p in getmembers(appdirs.AppDirs, lambda member: isinstance(member, property))] # Exception will be raised if any appdirs.AppDirs properties aren't in platformdirs.AppDirs for property_name in appdirs_property_names: getattr(platformdirs.AppDirs, property_name) @pytest.mark.parametrize( "params", [ {}, {"appname": "foo"}, {"appname": "foo", "appauthor": "bar"}, {"appname": "foo", "appauthor": "bar", "version": "v1.0"}, ], ids=[ "no_args", "app_name", "app_name_with_app_author", "app_name_author_version", ], ) def test_compatibility(params: dict[str, Any], func: str) -> None: # Only test functions that are part of appdirs if getattr(appdirs, func, None) is None: pytest.skip(f"`{func}` does not exist in `appdirs`") if sys.platform == "darwin": msg = { # pragma: no cover "user_log_dir": "without appname produces NoneType error", } if func in msg: # pragma: no cover pytest.skip(f"`appdirs.{func}` {msg[func]} on macOS") # pragma: no cover elif sys.platform != "win32": msg = { # pragma: no cover "user_log_dir": "Uses XDG_STATE_DIR instead of appdirs.user_data_dir per the XDG spec", } if func in msg: # pragma: no cover pytest.skip(f"`appdirs.{func}` {msg[func]} on Unix") # pragma: no cover new = getattr(platformdirs, func)(*params) old = getattr(appdirs, func)(*params) assert new == old.rstrip("/") platformdirs-4.9.4/tests/test_macos.py0000644000000000000000000003142313615410400015036 0ustar00from __future__ import annotations import os import sys from pathlib import Path from typing import TYPE_CHECKING, Any import pytest from platformdirs.macos import MacOS if TYPE_CHECKING: from pytest_mock import MockerFixture _XDG_ENV_VARS = ( "XDG_DATA_HOME", "XDG_DATA_DIRS", "XDG_CONFIG_HOME", "XDG_CONFIG_DIRS", "XDG_CACHE_HOME", "XDG_STATE_HOME", "XDG_RUNTIME_DIR", "XDG_DOCUMENTS_DIR", "XDG_DOWNLOAD_DIR", "XDG_PICTURES_DIR", "XDG_VIDEOS_DIR", "XDG_MUSIC_DIR", "XDG_DESKTOP_DIR", ) @pytest.fixture(autouse=True) def _fix_os_pathsep(mocker: MockerFixture) -> None: """If we're not running on macOS, set `os.pathsep` to what it should be on macOS.""" if sys.platform != "darwin": # pragma: darwin no cover mocker.patch("os.pathsep", ":") mocker.patch("os.path.pathsep", ":") @pytest.fixture def _clear_xdg_env(monkeypatch: pytest.MonkeyPatch) -> None: for var in _XDG_ENV_VARS: monkeypatch.delenv(var, raising=False) @pytest.mark.parametrize( "params", [ pytest.param({}, id="no_args"), pytest.param({"appname": "foo"}, id="app_name"), pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"), ], ) @pytest.mark.usefixtures("_clear_xdg_env") def test_macos(mocker: MockerFixture, params: dict[str, Any], func: str) -> None: py_version = sys.version_info builtin_py_prefix = ( "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework" f"/Versions/{py_version.major}.{py_version.minor}" ) mocker.patch("sys.prefix", builtin_py_prefix) result = getattr(MacOS(**params), func) home = str(Path("~").expanduser()) suffix_elements = tuple(params[i] for i in ("appname", "version") if i in params) suffix = os.sep.join(("", *suffix_elements)) if suffix_elements else "" # noqa: PTH118 expected_map = { "user_data_dir": f"{home}/Library/Application Support{suffix}", "site_data_dir": f"/Library/Application Support{suffix}", "user_config_dir": f"{home}/Library/Application Support{suffix}", "site_config_dir": f"/Library/Application Support{suffix}", "user_cache_dir": f"{home}/Library/Caches{suffix}", "site_cache_dir": f"/Library/Caches{suffix}", "user_state_dir": f"{home}/Library/Application Support{suffix}", "site_state_dir": f"/Library/Application Support{suffix}", "user_log_dir": f"{home}/Library/Logs{suffix}", "site_log_dir": f"/Library/Logs{suffix}", "user_documents_dir": f"{home}/Documents", "user_downloads_dir": f"{home}/Downloads", "user_pictures_dir": f"{home}/Pictures", "user_videos_dir": f"{home}/Movies", "user_music_dir": f"{home}/Music", "user_desktop_dir": f"{home}/Desktop", "user_bin_dir": f"{home}/.local/bin", "site_bin_dir": "/usr/local/bin", "user_applications_dir": f"{home}/Applications", "site_applications_dir": "/Applications", "user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}", "site_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}", } expected = expected_map[func] assert result == expected @pytest.mark.parametrize( "params", [ pytest.param({}, id="no_args"), pytest.param({"appname": "foo"}, id="app_name"), pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"), ], ) @pytest.mark.parametrize( "site_func", [ "site_data_dir", "site_config_dir", "site_cache_dir", "site_runtime_dir", "site_cache_path", "site_data_path", ], ) @pytest.mark.parametrize("multipath", [pytest.param(True, id="multipath"), pytest.param(False, id="singlepath")]) @pytest.mark.usefixtures("_clear_xdg_env") def test_macos_homebrew(mocker: MockerFixture, params: dict[str, Any], multipath: bool, site_func: str) -> None: test_data = [ { "sys_prefix": "/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13", "homebrew_prefix": "/opt/homebrew", }, { "sys_prefix": "/usr/local/opt/python@3.13/Frameworks/Python.framework/Versions/3.13", "homebrew_prefix": "/usr/local", }, { "sys_prefix": "/myown/arbitrary/prefix/opt/python@3.13/Frameworks/Python.framework/Versions/3.13", "homebrew_prefix": "/myown/arbitrary/prefix", }, ] for prefix in test_data: mocker.patch("sys.prefix", prefix["sys_prefix"]) result = getattr(MacOS(multipath=multipath, **params), site_func) home = str(Path("~").expanduser()) suffix_elements = tuple(params[i] for i in ("appname", "version") if i in params) suffix = os.sep.join(("", *suffix_elements)) if suffix_elements else "" # noqa: PTH118 expected_path_map = { "site_cache_path": Path(f"{prefix['homebrew_prefix']}/var/cache{suffix}"), "site_data_path": Path(f"{prefix['homebrew_prefix']}/share{suffix}"), } expected_map = { "site_data_dir": f"{prefix['homebrew_prefix']}/share{suffix}", "site_config_dir": f"{prefix['homebrew_prefix']}/share{suffix}", "site_cache_dir": f"{prefix['homebrew_prefix']}/var/cache{suffix}", "site_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}", } if multipath: expected_map["site_data_dir"] += f":/Library/Application Support{suffix}" expected_map["site_config_dir"] += f":/Library/Application Support{suffix}" expected_map["site_cache_dir"] += f":/Library/Caches{suffix}" expected = expected_path_map[site_func] if site_func.endswith("_path") else expected_map[site_func] assert result == expected @pytest.mark.parametrize( ("env_var", "prop", "xdg_path"), [ pytest.param("XDG_DATA_HOME", "user_data_dir", "/custom/data", id="user_data_dir"), pytest.param("XDG_CONFIG_HOME", "user_config_dir", "/custom/config", id="user_config_dir"), pytest.param("XDG_CACHE_HOME", "user_cache_dir", "/custom/cache", id="user_cache_dir"), pytest.param("XDG_STATE_HOME", "user_state_dir", "/custom/state", id="user_state_dir"), pytest.param("XDG_RUNTIME_DIR", "user_runtime_dir", "/custom/runtime", id="user_runtime_dir"), pytest.param("XDG_RUNTIME_DIR", "site_runtime_dir", "/custom/runtime", id="site_runtime_dir"), ], ) @pytest.mark.parametrize( "params", [ pytest.param({}, id="no_args"), pytest.param({"appname": "foo"}, id="app_name"), pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"), ], ) def test_macos_xdg_env_vars( monkeypatch: pytest.MonkeyPatch, env_var: str, prop: str, xdg_path: str, params: dict[str, Any], ) -> None: monkeypatch.setenv(env_var, xdg_path) result = getattr(MacOS(**params), prop) suffix_elements = tuple(params[i] for i in ("appname", "version") if i in params) suffix = os.sep.join(("", *suffix_elements)) if suffix_elements else "" # noqa: PTH118 assert result == f"{xdg_path}{suffix}" @pytest.mark.parametrize( ("env_var", "prop"), [ pytest.param("XDG_DATA_DIRS", "site_data_dir", id="site_data_dir"), pytest.param("XDG_CONFIG_DIRS", "site_config_dir", id="site_config_dir"), ], ) @pytest.mark.parametrize("multipath", [pytest.param(True, id="multipath"), pytest.param(False, id="singlepath")]) def test_macos_xdg_site_dirs( monkeypatch: pytest.MonkeyPatch, env_var: str, prop: str, multipath: bool, ) -> None: monkeypatch.setenv(env_var, "/custom/first:/custom/second") result = getattr(MacOS(multipath=multipath), prop) if multipath: assert result == "/custom/first:/custom/second" else: assert result == "/custom/first" @pytest.mark.parametrize( ("env_var", "prop"), [ pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", id="user_documents_dir"), pytest.param("XDG_DOWNLOAD_DIR", "user_downloads_dir", id="user_downloads_dir"), pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", id="user_pictures_dir"), pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", id="user_videos_dir"), pytest.param("XDG_MUSIC_DIR", "user_music_dir", id="user_music_dir"), pytest.param("XDG_DESKTOP_DIR", "user_desktop_dir", id="user_desktop_dir"), ], ) def test_macos_xdg_media_dirs(monkeypatch: pytest.MonkeyPatch, env_var: str, prop: str) -> None: monkeypatch.setenv(env_var, "/custom/media") assert getattr(MacOS(), prop) == "/custom/media" @pytest.mark.parametrize( ("env_var", "prop"), [ pytest.param("XDG_DATA_HOME", "user_data_dir", id="user_data_dir"), pytest.param("XDG_CONFIG_HOME", "user_config_dir", id="user_config_dir"), pytest.param("XDG_CACHE_HOME", "user_cache_dir", id="user_cache_dir"), pytest.param("XDG_STATE_HOME", "user_state_dir", id="user_state_dir"), pytest.param("XDG_RUNTIME_DIR", "user_runtime_dir", id="user_runtime_dir"), pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", id="user_documents_dir"), pytest.param("XDG_DOWNLOAD_DIR", "user_downloads_dir", id="user_downloads_dir"), pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", id="user_pictures_dir"), pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", id="user_videos_dir"), pytest.param("XDG_MUSIC_DIR", "user_music_dir", id="user_music_dir"), pytest.param("XDG_DESKTOP_DIR", "user_desktop_dir", id="user_desktop_dir"), ], ) @pytest.mark.usefixtures("_clear_xdg_env") def test_macos_xdg_empty_falls_back( monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, env_var: str, prop: str ) -> None: py_version = sys.version_info builtin_py_prefix = ( "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework" f"/Versions/{py_version.major}.{py_version.minor}" ) mocker.patch("sys.prefix", builtin_py_prefix) monkeypatch.setenv(env_var, "") home = str(Path("~").expanduser()) expected_map = { "user_data_dir": f"{home}/Library/Application Support", "user_config_dir": f"{home}/Library/Application Support", "user_cache_dir": f"{home}/Library/Caches", "user_state_dir": f"{home}/Library/Application Support", "user_runtime_dir": f"{home}/Library/Caches/TemporaryItems", "user_documents_dir": f"{home}/Documents", "user_downloads_dir": f"{home}/Downloads", "user_pictures_dir": f"{home}/Pictures", "user_videos_dir": f"{home}/Movies", "user_music_dir": f"{home}/Music", "user_desktop_dir": f"{home}/Desktop", "user_bin_dir": f"{home}/.local/bin", "site_bin_dir": "/usr/local/bin", "user_applications_dir": f"{home}/Applications", } assert getattr(MacOS(), prop) == expected_map[prop] def test_iter_data_dirs_xdg(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("XDG_DATA_HOME", "/xdg/data") monkeypatch.setenv("XDG_DATA_DIRS", "/xdg/share1:/xdg/share2") dirs = list(MacOS().iter_data_dirs()) assert dirs == ["/xdg/data", "/xdg/share1", "/xdg/share2"] def test_iter_config_dirs_xdg(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("XDG_CONFIG_HOME", "/xdg/config") monkeypatch.setenv("XDG_CONFIG_DIRS", "/xdg/etc1:/xdg/etc2") dirs = list(MacOS().iter_config_dirs()) assert dirs == ["/xdg/config", "/xdg/etc1", "/xdg/etc2"] @pytest.mark.usefixtures("_clear_xdg_env") def test_iter_data_dirs_homebrew(mocker: MockerFixture) -> None: mocker.patch("sys.prefix", "/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13") dirs = list(MacOS().iter_data_dirs()) home = str(Path("~").expanduser()) assert dirs == [f"{home}/Library/Application Support", "/opt/homebrew/share", "/Library/Application Support"] @pytest.mark.usefixtures("_clear_xdg_env") def test_iter_config_dirs_homebrew(mocker: MockerFixture) -> None: mocker.patch("sys.prefix", "/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13") dirs = list(MacOS().iter_config_dirs()) home = str(Path("~").expanduser()) assert dirs == [f"{home}/Library/Application Support", "/opt/homebrew/share", "/Library/Application Support"] @pytest.mark.usefixtures("_clear_xdg_env") def test_iter_data_dirs_no_homebrew(mocker: MockerFixture) -> None: py_version = sys.version_info builtin_py_prefix = ( "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework" f"/Versions/{py_version.major}.{py_version.minor}" ) mocker.patch("sys.prefix", builtin_py_prefix) dirs = list(MacOS().iter_data_dirs()) home = str(Path("~").expanduser()) assert dirs == [f"{home}/Library/Application Support", "/Library/Application Support"] platformdirs-4.9.4/tests/test_main.py0000644000000000000000000000075013615410400014657 0ustar00from __future__ import annotations import sys from subprocess import check_output # noqa: S404 from platformdirs import __version__ from platformdirs.__main__ import PROPS def test_props_same_as_test(props: tuple[str, ...]) -> None: assert props == PROPS def test_run_as_module() -> None: out = check_output([sys.executable, "-m", "platformdirs"], text=True) assert out.startswith(f"-- platformdirs {__version__} --") for prop in PROPS: assert prop in out platformdirs-4.9.4/tests/test_unix.py0000644000000000000000000004103213615410400014714 0ustar00from __future__ import annotations import importlib import os import sys import typing from tempfile import gettempdir import pytest from platformdirs import unix from platformdirs.unix import Unix if typing.TYPE_CHECKING: from pathlib import Path from pytest_mock import MockerFixture @pytest.fixture(autouse=True) def _reload_after_test() -> typing.Iterator[None]: yield importlib.reload(unix) @pytest.mark.parametrize( "prop", [ "user_documents_dir", "user_downloads_dir", "user_pictures_dir", "user_videos_dir", "user_music_dir", "user_desktop_dir", ], ) def test_user_media_dir(mocker: MockerFixture, prop: str) -> None: example_path = "/home/example/ExampleMediaFolder" mock = mocker.patch("platformdirs.unix._get_user_dirs_folder") mock.return_value = example_path assert getattr(Unix(), prop) == example_path @pytest.mark.parametrize( ("env_var", "prop"), [ pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", id="user_documents_dir"), pytest.param("XDG_DOWNLOAD_DIR", "user_downloads_dir", id="user_downloads_dir"), pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", id="user_pictures_dir"), pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", id="user_videos_dir"), pytest.param("XDG_MUSIC_DIR", "user_music_dir", id="user_music_dir"), pytest.param("XDG_DESKTOP_DIR", "user_desktop_dir", id="user_desktop_dir"), ], ) def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str) -> None: # Mock media dir not being in user-dirs.dirs file mock = mocker.patch("platformdirs.unix._get_user_dirs_folder") mock.return_value = None example_path = "/home/example/ExampleMediaFolder" mocker.patch.dict(os.environ, {env_var: example_path}) assert getattr(Unix(), prop) == example_path @pytest.mark.parametrize( ("env_var", "prop", "default_abs_path"), [ pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", "/home/example/Documents", id="user_documents_dir"), pytest.param("XDG_DOWNLOAD_DIR", "user_downloads_dir", "/home/example/Downloads", id="user_downloads_dir"), pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", "/home/example/Pictures", id="user_pictures_dir"), pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", "/home/example/Videos", id="user_videos_dir"), pytest.param("XDG_MUSIC_DIR", "user_music_dir", "/home/example/Music", id="user_music_dir"), pytest.param("XDG_DESKTOP_DIR", "user_desktop_dir", "/home/example/Desktop", id="user_desktop_dir"), ], ) def test_user_media_dir_default(mocker: MockerFixture, env_var: str, prop: str, default_abs_path: str) -> None: # Mock media dir not being in user-dirs.dirs file mock = mocker.patch("platformdirs.unix._get_user_dirs_folder") mock.return_value = None # Mock no XDG env variable being set mocker.patch.dict(os.environ, {env_var: ""}) # Mock home directory mocker.patch.dict(os.environ, {"HOME": "/home/example"}) # Mock home directory for running the test on Windows mocker.patch.dict(os.environ, {"USERPROFILE": "/home/example"}) assert getattr(Unix(), prop) == default_abs_path class XDGVariable(typing.NamedTuple): name: str default_value: str def _func_to_path(func: str) -> XDGVariable | None: 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_STATE_HOME", "~/.local/state"), "user_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", f"{gettempdir()}/runtime-1234"), "user_bin_dir": None, "site_bin_dir": None, "user_applications_dir": None, "site_applications_dir": None, "site_log_dir": None, "site_state_dir": None, "site_runtime_dir": XDGVariable( "XDG_RUNTIME_DIR", "/var/run" if sys.platform.startswith(("freebsd", "openbsd", "netbsd")) else "/run" ), } return mapping.get(func) @pytest.fixture def dirs_instance() -> Unix: return Unix(multipath=True, opinion=False) @pytest.fixture def _getuid(mocker: MockerFixture) -> None: mocker.patch("platformdirs.unix.getuid", return_value=1234) @pytest.mark.usefixtures("_getuid") def test_xdg_variable_not_set(monkeypatch: pytest.MonkeyPatch, dirs_instance: Unix, func: str) -> None: xdg_variable = _func_to_path(func) if xdg_variable is None: return monkeypatch.delenv(xdg_variable.name, raising=False) result = getattr(dirs_instance, func) assert result == os.path.expanduser(xdg_variable.default_value) # noqa: PTH111 @pytest.mark.usefixtures("_getuid") def test_xdg_variable_empty_value(monkeypatch: pytest.MonkeyPatch, dirs_instance: Unix, func: str) -> None: xdg_variable = _func_to_path(func) if xdg_variable is None: return monkeypatch.setenv(xdg_variable.name, "") result = getattr(dirs_instance, func) assert result == os.path.expanduser(xdg_variable.default_value) # noqa: PTH111 @pytest.mark.usefixtures("_getuid") def test_xdg_variable_custom_value(monkeypatch: pytest.MonkeyPatch, dirs_instance: Unix, func: str) -> None: xdg_variable = _func_to_path(func) if xdg_variable is None: return monkeypatch.setenv(xdg_variable.name, "/custom-dir") result = getattr(dirs_instance, func) assert result == "/custom-dir" @pytest.mark.parametrize("opinion", [True, False]) def test_site_log_dir_fixed_path(opinion: bool) -> None: result = Unix(appname="foo", opinion=opinion).site_log_dir assert result == os.path.join("/var/log", "foo") # noqa: PTH118 def test_site_state_dir_fixed_path() -> None: result = Unix(appname="foo").site_state_dir assert result == os.path.join("/var/lib", "foo") # noqa: PTH118 @pytest.mark.usefixtures("_getuid") @pytest.mark.parametrize("platform", [pytest.param("freebsd", id="freebsd"), pytest.param("netbsd", id="netbsd")]) def test_freebsd_netbsd_site_runtime_dir(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) assert Unix().site_runtime_dir == "/var/run" @pytest.mark.usefixtures("_getuid") @pytest.mark.parametrize("platform", [pytest.param("freebsd", id="freebsd"), pytest.param("netbsd", id="netbsd")]) def test_freebsd_netbsd_user_runtime_dir_writable( monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str ) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) mocker.patch("os.access", return_value=True) assert Unix().user_runtime_dir == "/var/run/user/1234" @pytest.mark.usefixtures("_getuid") @pytest.mark.parametrize("platform", [pytest.param("freebsd", id="freebsd"), pytest.param("netbsd", id="netbsd")]) def test_freebsd_netbsd_user_runtime_dir_not_writable( monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str ) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) mocker.patch("os.access", return_value=False) mocker.patch("tempfile.tempdir", "/tmp") # noqa: S108 assert Unix().user_runtime_dir == "/tmp/runtime-1234" # noqa: S108 @pytest.mark.usefixtures("_getuid") def test_openbsd_site_runtime_dir(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", "openbsd") assert Unix().site_runtime_dir == "/var/run" @pytest.mark.usefixtures("_getuid") def test_openbsd_user_runtime_dir_writable(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", "openbsd") mocker.patch("os.access", return_value=True) assert Unix().user_runtime_dir == "/tmp/run/user/1234" # noqa: S108 @pytest.mark.usefixtures("_getuid") def test_openbsd_user_runtime_dir_not_writable(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", "openbsd") mocker.patch("os.access", return_value=False) mocker.patch("tempfile.tempdir", "/tmp") # noqa: S108 assert Unix().user_runtime_dir == "/tmp/runtime-1234" # noqa: S108 def test_platform_on_win32(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", "win32") prev_unix = unix importlib.reload(unix) try: with pytest.raises(RuntimeError, match="should only be used on Unix"): unix.Unix().user_runtime_dir # noqa: B018 finally: 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_writable( monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str, default_dir: str ) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) mocker.patch("os.access", return_value=True) assert Unix().user_runtime_dir == default_dir @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_not_writable( monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str, default_dir: str ) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) mocker.patch("os.access", return_value=False) mocker.patch("tempfile.tempdir", "/tmp") # noqa: S108 result = Unix().user_runtime_dir assert not result.startswith(default_dir) assert result == "/tmp/runtime-1234" # noqa: S108 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 assert data_path.exists() def test_folder_not_created_without_ensure_exists(mocker: MockerFixture, tmp_path: Path) -> None: mocker.patch.dict(os.environ, {"XDG_DATA_HOME": str(tmp_path)}) data_path = Unix(appname="acme", ensure_exists=False).user_data_path assert not data_path.exists() def test_iter_data_dirs_xdg(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("XDG_DATA_HOME", "/xdg/data") monkeypatch.setenv("XDG_DATA_DIRS", f"/xdg/share1{os.pathsep}/xdg/share2") dirs = list(Unix().iter_data_dirs()) assert dirs == ["/xdg/data", "/xdg/share1", "/xdg/share2"] def test_iter_config_dirs_xdg(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("XDG_CONFIG_HOME", "/xdg/config") monkeypatch.setenv("XDG_CONFIG_DIRS", f"/xdg/etc1{os.pathsep}/xdg/etc2") dirs = list(Unix().iter_config_dirs()) assert dirs == ["/xdg/config", "/xdg/etc1", "/xdg/etc2"] def test_user_media_dir_from_user_dirs_file( mocker: MockerFixture, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.delenv("XDG_DOCUMENTS_DIR", raising=False) config_dir = tmp_path / ".config" config_dir.mkdir() user_dirs_file = config_dir / "user-dirs.dirs" user_dirs_file.write_text('XDG_DOCUMENTS_DIR="$HOME/MyDocs"\n') monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path)) mocker.patch.dict(os.environ, {"XDG_CONFIG_HOME": ""}) assert Unix().user_documents_dir == f"{tmp_path}/MyDocs" def test_user_media_dir_missing_key_in_user_dirs_file( mocker: MockerFixture, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.delenv("XDG_DOCUMENTS_DIR", raising=False) config_dir = tmp_path / ".config" config_dir.mkdir() user_dirs_file = config_dir / "user-dirs.dirs" user_dirs_file.write_text('XDG_DESKTOP_DIR="$HOME/Desktop"\n') monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path)) mocker.patch.dict(os.environ, {"XDG_CONFIG_HOME": ""}) assert Unix().user_documents_dir == f"{tmp_path}/Documents" def test_user_media_dir_no_user_dirs_file( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.delenv("XDG_DOCUMENTS_DIR", raising=False) monkeypatch.setenv("HOME", "/nonexistent/path") monkeypatch.setenv("USERPROFILE", "/nonexistent/path") monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) assert Unix().user_documents_dir == "/nonexistent/path/Documents" def test_user_dirs_respects_xdg_config_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("XDG_DOCUMENTS_DIR", raising=False) custom_config = tmp_path / "custom_config" custom_config.mkdir() user_dirs_file = custom_config / "user-dirs.dirs" user_dirs_file.write_text('XDG_DOCUMENTS_DIR="$HOME/CustomDocs"\n') monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path)) monkeypatch.setenv("XDG_CONFIG_HOME", str(custom_config)) assert Unix().user_documents_dir == f"{tmp_path}/CustomDocs" _SITE_REDIRECT_CASES: list[tuple[str, str]] = [ ("user_data_dir", os.path.join("/usr/local/share", "foo")), # noqa: PTH118 ("user_config_dir", os.path.join("/etc/xdg", "foo")), # noqa: PTH118 ("user_cache_dir", os.path.join("/var/cache", "foo")), # noqa: PTH118 ("user_state_dir", os.path.join("/var/lib", "foo")), # noqa: PTH118 ("user_log_dir", os.path.join("/var/log", "foo")), # noqa: PTH118 ( "user_runtime_dir", os.path.join( # noqa: PTH118 "/var/run" if sys.platform.startswith(("freebsd", "openbsd", "netbsd")) else "/run", "foo", ), ), ("user_bin_dir", "/usr/local/bin"), ] @pytest.mark.parametrize(("prop", "expected"), _SITE_REDIRECT_CASES) def test_use_site_for_root_as_root( mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch, prop: str, expected: str ) -> None: mocker.patch("platformdirs.unix.getuid", return_value=0) monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) result = getattr(Unix(appname="foo", use_site_for_root=True), prop) assert result == expected @pytest.mark.parametrize(("prop", "expected"), _SITE_REDIRECT_CASES) def test_use_site_for_root_as_non_root( mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch, prop: str, expected: str ) -> None: mocker.patch("platformdirs.unix.getuid", return_value=1000) monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("os.access", return_value=True) dirs = Unix(appname="foo", use_site_for_root=True) result = getattr(dirs, prop) assert result != expected @pytest.mark.parametrize(("prop", "expected"), _SITE_REDIRECT_CASES) def test_use_site_for_root_disabled_as_root( mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch, prop: str, expected: str ) -> None: mocker.patch("platformdirs.unix.getuid", return_value=0) monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("os.access", return_value=True) dirs = Unix(appname="foo", use_site_for_root=False) result = getattr(dirs, prop) assert result != expected @pytest.mark.parametrize( ("xdg_var", "prop", "expected_site"), [ ("XDG_DATA_HOME", "user_data_dir", os.path.join("/usr/local/share", "foo")), # noqa: PTH118 ("XDG_CONFIG_HOME", "user_config_dir", os.path.join("/etc/xdg", "foo")), # noqa: PTH118 ("XDG_CACHE_HOME", "user_cache_dir", os.path.join("/var/cache", "foo")), # noqa: PTH118 ("XDG_STATE_HOME", "user_state_dir", os.path.join("/var/lib", "foo")), # noqa: PTH118 ("XDG_STATE_HOME", "user_log_dir", os.path.join("/var/log", "foo")), # noqa: PTH118 ], ) def test_use_site_for_root_bypasses_xdg_user_vars( mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch, xdg_var: str, prop: str, expected_site: str ) -> None: mocker.patch("platformdirs.unix.getuid", return_value=0) monkeypatch.setenv(xdg_var, "/custom/xdg/path") monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) result = getattr(Unix(appname="foo", use_site_for_root=True), prop) assert result == expected_site platformdirs-4.9.4/tests/test_windows.py0000644000000000000000000003477313615410400015441 0ustar00from __future__ import annotations import ctypes import importlib import os import sys from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock import pytest from platformdirs import windows from platformdirs.windows import ( _KF_FLAG_DONT_VERIFY, _KNOWN_FOLDER_GUIDS, Windows, get_win_folder, get_win_folder_from_env_vars, get_win_folder_if_csidl_name_not_env_var, ) if TYPE_CHECKING: from pytest_mock import MockerFixture _WIN_FOLDERS: dict[str, str] = { "CSIDL_APPDATA": r"C:\Users\Test\AppData\Roaming", "CSIDL_LOCAL_APPDATA": r"C:\Users\Test\AppData\Local", "CSIDL_COMMON_APPDATA": r"C:\ProgramData", "CSIDL_PERSONAL": r"C:\Users\Test\Documents", "CSIDL_DOWNLOADS": r"C:\Users\Test\Downloads", "CSIDL_MYPICTURES": r"C:\Users\Test\Pictures", "CSIDL_MYVIDEO": r"C:\Users\Test\Videos", "CSIDL_MYMUSIC": r"C:\Users\Test\Music", "CSIDL_DESKTOPDIRECTORY": r"C:\Users\Test\Desktop", "CSIDL_PROGRAMS": r"C:\Users\Test\AppData\Roaming\Microsoft\Windows\Start Menu\Programs", "CSIDL_COMMON_PROGRAMS": r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs", } _LOCAL = os.path.normpath(_WIN_FOLDERS["CSIDL_LOCAL_APPDATA"]) _COMMON = os.path.normpath(_WIN_FOLDERS["CSIDL_COMMON_APPDATA"]) @pytest.fixture(autouse=True) def _mock_get_win_folder(mocker: MockerFixture) -> None: mocker.patch("platformdirs.windows.get_win_folder", side_effect=lambda csidl: _WIN_FOLDERS[csidl]) @pytest.mark.parametrize( "params", [ pytest.param({}, id="no_args"), pytest.param({"appname": "foo"}, id="app_name"), pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"), ], ) def test_windows(params: dict[str, Any], func: str) -> None: result = getattr(Windows(**params), func) suffix_parts = [] if appname := params.get("appname"): suffix_parts.extend((appname, appname)) if version := params.get("version"): suffix_parts.append(version) local = os.path.join(_LOCAL, *suffix_parts) if suffix_parts else _LOCAL # noqa: PTH118 common = os.path.join(_COMMON, *suffix_parts) if suffix_parts else _COMMON # noqa: PTH118 temp = os.path.join(_LOCAL, "Temp", *suffix_parts) if suffix_parts else os.path.join(_LOCAL, "Temp") # noqa: PTH118 cache_local = os.path.join( # noqa: PTH118 _LOCAL, *suffix_parts[:2], *(["Cache"] if suffix_parts else []), *suffix_parts[2:] ) cache_common = os.path.join( # noqa: PTH118 _COMMON, *suffix_parts[:2], *(["Cache"] if suffix_parts else []), *suffix_parts[2:] ) log = os.path.join(_LOCAL, *suffix_parts, "Logs") # noqa: PTH118 log_common = os.path.join(_COMMON, *suffix_parts, "Logs") # noqa: PTH118 expected_map = { "user_data_dir": local, "site_data_dir": common, "user_config_dir": local, "site_config_dir": common, "user_cache_dir": cache_local, "site_cache_dir": cache_common, "user_state_dir": local, "site_state_dir": common, "user_log_dir": log, "site_log_dir": log_common, "user_documents_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_PERSONAL"]), "user_downloads_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_DOWNLOADS"]), "user_pictures_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_MYPICTURES"]), "user_videos_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_MYVIDEO"]), "user_music_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_MYMUSIC"]), "user_desktop_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_DESKTOPDIRECTORY"]), "user_bin_dir": os.path.join(_LOCAL, "Programs"), # noqa: PTH118 "site_bin_dir": os.path.join(_COMMON, "bin"), # noqa: PTH118 "user_applications_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_PROGRAMS"]), "site_applications_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_COMMON_PROGRAMS"]), "user_runtime_dir": temp, "site_runtime_dir": temp, } assert result == expected_map[func] def test_roaming_uses_appdata(mocker: MockerFixture) -> None: mock = mocker.patch("platformdirs.windows.get_win_folder", side_effect=lambda csidl: _WIN_FOLDERS[csidl]) _result = Windows(appname="foo", roaming=True).user_data_dir mock.assert_called_with("CSIDL_APPDATA") def test_non_roaming_uses_local_appdata(mocker: MockerFixture) -> None: mock = mocker.patch("platformdirs.windows.get_win_folder", side_effect=lambda csidl: _WIN_FOLDERS[csidl]) _result = Windows(appname="foo", roaming=False).user_data_dir mock.assert_called_with("CSIDL_LOCAL_APPDATA") def test_appauthor_false_skips_author() -> None: result = Windows(appname="foo", appauthor=False).user_data_dir assert result == os.path.join(_LOCAL, "foo") # noqa: PTH118 def test_appauthor_explicit() -> None: result = Windows(appname="foo", appauthor="bar").user_data_dir assert result == os.path.join(_LOCAL, "bar", "foo") # noqa: PTH118 @pytest.mark.parametrize( ("csidl_name", "env_var", "value"), [ pytest.param("CSIDL_APPDATA", "APPDATA", r"C:\Users\Test\AppData\Roaming", id="appdata"), pytest.param("CSIDL_LOCAL_APPDATA", "LOCALAPPDATA", r"C:\Users\Test\AppData\Local", id="local_appdata"), pytest.param("CSIDL_COMMON_APPDATA", "ALLUSERSPROFILE", r"C:\ProgramData", id="common_appdata"), ], ) def test_get_win_folder_from_env_vars_direct( monkeypatch: pytest.MonkeyPatch, csidl_name: str, env_var: str, value: str ) -> None: monkeypatch.setenv(env_var, value) assert get_win_folder_from_env_vars(csidl_name) == value _USERPROFILE_CSIDL_PARAMS = [ pytest.param("CSIDL_PERSONAL", "Documents", id="personal"), pytest.param("CSIDL_DOWNLOADS", "Downloads", id="downloads"), pytest.param("CSIDL_MYPICTURES", "Pictures", id="pictures"), pytest.param("CSIDL_MYVIDEO", "Videos", id="video"), pytest.param("CSIDL_MYMUSIC", "Music", id="music"), ] @pytest.mark.parametrize(("csidl_name", "subfolder"), _USERPROFILE_CSIDL_PARAMS) def test_get_win_folder_from_env_vars_user_folders( monkeypatch: pytest.MonkeyPatch, csidl_name: str, subfolder: str ) -> None: monkeypatch.setenv("USERPROFILE", r"C:\Users\Test") assert get_win_folder_from_env_vars(csidl_name).endswith(subfolder) def test_get_win_folder_from_env_vars_programs(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("APPDATA", r"C:\Users\Test\AppData\Roaming") result = get_win_folder_from_env_vars("CSIDL_PROGRAMS") assert result.endswith("Programs") def test_get_win_folder_from_env_vars_unknown() -> None: with pytest.raises(ValueError, match="Unknown CSIDL name"): get_win_folder_from_env_vars("CSIDL_BOGUS") def test_get_win_folder_from_env_vars_unset(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("APPDATA", raising=False) with pytest.raises(ValueError, match="Unset environment variable"): get_win_folder_from_env_vars("CSIDL_APPDATA") def test_get_win_folder_if_csidl_name_not_env_var_returns_none() -> None: assert get_win_folder_if_csidl_name_not_env_var("CSIDL_APPDATA") is None @pytest.mark.parametrize(("csidl_name", "subfolder"), _USERPROFILE_CSIDL_PARAMS) def test_get_win_folder_if_csidl_name_not_env_var( monkeypatch: pytest.MonkeyPatch, csidl_name: str, subfolder: str ) -> None: monkeypatch.setenv("USERPROFILE", r"C:\Users\Test") result = get_win_folder_if_csidl_name_not_env_var(csidl_name) assert result is not None assert result.endswith(subfolder) def test_get_win_folder_if_csidl_name_not_env_var_programs(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("APPDATA", r"C:\Users\Test\AppData\Roaming") result = get_win_folder_if_csidl_name_not_env_var("CSIDL_PROGRAMS") assert result is not None assert result.endswith("Programs") def _setup_ctypes_mocks(mocker: MockerFixture, *, win_dll: MagicMock | None = None) -> None: """Mock ctypes internals so get_win_folder_via_ctypes can be tested on non-Windows.""" for attr in ("HRESULT", "WinDLL"): if not hasattr(ctypes, attr): setattr(ctypes, attr, MagicMock()) if win_dll is not None: ctypes.WinDLL = win_dll # type: ignore[attr-defined] mocker.patch("sys.platform", "win32") mocker.patch("ctypes.POINTER", return_value=MagicMock()) def _cleanup_ctypes_mocks() -> None: for attr in ("HRESULT", "WinDLL"): if isinstance(getattr(ctypes, attr, None), MagicMock): delattr(ctypes, attr) @pytest.mark.skipif(sys.platform != "win32", reason="real ctypes test only runs on Windows") @pytest.mark.parametrize("csidl_name", list(_KNOWN_FOLDER_GUIDS.keys()), ids=list(_KNOWN_FOLDER_GUIDS.keys())) def test_get_win_folder_via_ctypes_real(csidl_name: str) -> None: importlib.reload(windows) from platformdirs.windows import get_win_folder_via_ctypes as fresh_fn # noqa: PLC0415 result = fresh_fn(csidl_name) assert isinstance(result, str) assert len(result) > 0 @pytest.mark.skipif(sys.platform == "win32", reason="mock-based flag inspection only runs on non-Windows") def test_get_win_folder_via_ctypes_passes_dont_verify_flag(mocker: MockerFixture) -> None: _setup_ctypes_mocks(mocker) mock_ole32 = MagicMock() mock_shell32 = MagicMock() mock_kernel32 = MagicMock() ctypes.WinDLL = MagicMock( # type: ignore[attr-defined] side_effect=lambda name: {"ole32": mock_ole32, "shell32": mock_shell32, "kernel32": mock_kernel32}[name], ) mocker.patch("ctypes.byref", side_effect=lambda x: x) mock_path_ptr = MagicMock() mock_path_ptr.value = r"C:\Users\Test\AppData\Local" mocker.patch("ctypes.wintypes.LPWSTR", return_value=mock_path_ptr) try: importlib.reload(windows) from platformdirs.windows import get_win_folder_via_ctypes as fresh_fn # noqa: PLC0415 result = fresh_fn("CSIDL_LOCAL_APPDATA") assert result == r"C:\Users\Test\AppData\Local" mock_shell32.SHGetKnownFolderPath.assert_called_once() flags_arg = mock_shell32.SHGetKnownFolderPath.call_args[0][1] assert flags_arg == _KF_FLAG_DONT_VERIFY finally: _cleanup_ctypes_mocks() def test_get_win_folder_via_ctypes_unknown_csidl(mocker: MockerFixture) -> None: if sys.platform != "win32": _setup_ctypes_mocks(mocker, win_dll=MagicMock(side_effect=lambda _name: MagicMock())) try: importlib.reload(windows) from platformdirs.windows import get_win_folder_via_ctypes as fresh_fn # noqa: PLC0415 with pytest.raises(ValueError, match="Unknown CSIDL name"): fresh_fn("CSIDL_BOGUS") finally: if sys.platform != "win32": _cleanup_ctypes_mocks() @pytest.mark.skipif(sys.platform == "win32", reason="cannot force NULL from real SHGetKnownFolderPath") def test_get_win_folder_via_ctypes_null_result(mocker: MockerFixture) -> None: _setup_ctypes_mocks(mocker) mock_ole32 = MagicMock() mock_shell32 = MagicMock() mock_kernel32 = MagicMock() ctypes.WinDLL = MagicMock( # type: ignore[attr-defined] side_effect=lambda name: {"ole32": mock_ole32, "shell32": mock_shell32, "kernel32": mock_kernel32}[name], ) mocker.patch("ctypes.byref", side_effect=lambda x: x) mock_path_ptr = MagicMock() mock_path_ptr.value = None mocker.patch("ctypes.wintypes.LPWSTR", return_value=mock_path_ptr) try: importlib.reload(windows) from platformdirs.windows import get_win_folder_via_ctypes as fresh_fn # noqa: PLC0415 with pytest.raises(ValueError, match="SHGetKnownFolderPath returned NULL"): fresh_fn("CSIDL_LOCAL_APPDATA") finally: _cleanup_ctypes_mocks() def test_known_folder_guids_has_all_csidl_names() -> None: expected = { "CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA", "CSIDL_PERSONAL", "CSIDL_MYPICTURES", "CSIDL_MYVIDEO", "CSIDL_MYMUSIC", "CSIDL_DOWNLOADS", "CSIDL_DESKTOPDIRECTORY", "CSIDL_PROGRAMS", "CSIDL_COMMON_PROGRAMS", } assert set(_KNOWN_FOLDER_GUIDS.keys()) == expected def test_pick_get_win_folder_ctypes(mocker: MockerFixture) -> None: if sys.platform != "win32": _setup_ctypes_mocks(mocker, win_dll=MagicMock()) try: importlib.reload(windows) assert windows._pick_get_win_folder() is windows.get_win_folder_via_ctypes # noqa: SLF001 finally: if sys.platform != "win32": _cleanup_ctypes_mocks() @pytest.mark.parametrize( ("csidl_name", "env_suffix"), [ pytest.param("CSIDL_APPDATA", "APPDATA", id="appdata"), pytest.param("CSIDL_LOCAL_APPDATA", "LOCAL_APPDATA", id="local_appdata"), pytest.param("CSIDL_COMMON_APPDATA", "COMMON_APPDATA", id="common_appdata"), pytest.param("CSIDL_PERSONAL", "PERSONAL", id="personal"), pytest.param("CSIDL_DOWNLOADS", "DOWNLOADS", id="downloads"), pytest.param("CSIDL_MYPICTURES", "MYPICTURES", id="mypictures"), pytest.param("CSIDL_MYVIDEO", "MYVIDEO", id="myvideo"), pytest.param("CSIDL_MYMUSIC", "MYMUSIC", id="mymusic"), pytest.param("CSIDL_DESKTOPDIRECTORY", "DESKTOPDIRECTORY", id="desktop"), pytest.param("CSIDL_PROGRAMS", "PROGRAMS", id="programs"), ], ) 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"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("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("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("WIN_PD_OVERRIDE_LOCAL_APPDATA", " X:\\custom ") assert get_win_folder("CSIDL_LOCAL_APPDATA") == r"X:\custom" platformdirs-4.9.4/.gitignore0000644000000000000000000000011613615410400013144 0ustar00*.pyc *.egg-info /dist /.tox /src/platformdirs/version.py /report /docs/build platformdirs-4.9.4/LICENSE0000644000000000000000000000210113615410400012155 0ustar00MIT License Copyright (c) 2010-202x The platformdirs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. platformdirs-4.9.4/README.md0000644000000000000000000000577613615410400012454 0ustar00# platformdirs [![PyPI version](https://badge.fury.io/py/platformdirs.svg)](https://badge.fury.io/py/platformdirs) [![Python versions](https://img.shields.io/pypi/pyversions/platformdirs.svg)](https://pypi.python.org/pypi/platformdirs/) [![CI](https://github.com/tox-dev/platformdirs/actions/workflows/check.yaml/badge.svg)](https://github.com/platformdirs/platformdirs/actions) [![Downloads](https://static.pepy.tech/badge/platformdirs/month)](https://pepy.tech/project/platformdirs) A Python package for determining platform-specific directories (e.g. user data, config, cache, logs). Handles the differences between macOS, Windows, Linux/Unix, and Android so you don't have to. ## Quick start ```python from platformdirs import PlatformDirs dirs = PlatformDirs("MyApp", "MyCompany") dirs.user_data_dir # ~/.local/share/MyApp (Linux) dirs.user_config_dir # ~/.config/MyApp (Linux) dirs.user_cache_dir # ~/.cache/MyApp (Linux) dirs.user_state_dir # ~/.local/state/MyApp (Linux) dirs.user_log_dir # ~/.local/state/MyApp/log (Linux) dirs.user_documents_dir # ~/Documents dirs.user_downloads_dir # ~/Downloads dirs.user_runtime_dir # /run/user//MyApp (Linux) ``` For Path objects instead of strings: ```python from platformdirs import PlatformDirs dirs = PlatformDirs("MyApp", "MyCompany") dirs.user_data_path # pathlib.Path('~/.local/share/MyApp') dirs.user_config_path # pathlib.Path('~/.config/MyApp') ``` Convenience functions for quick access: ```python from platformdirs import user_data_dir, user_config_path user_data_dir("MyApp", "MyCompany") # returns str user_config_path("MyApp", "MyCompany") # returns pathlib.Path ``` ## Directory types - **Data**: Persistent application data (`user_data_dir`, `site_data_dir`) - **Config**: Configuration files and settings (`user_config_dir`, `site_config_dir`) - **Cache**: Cached data that can be regenerated (`user_cache_dir`, `site_cache_dir`) - **State**: Non-essential runtime state like window positions (`user_state_dir`, `site_state_dir`) - **Logs**: Log files (`user_log_dir`, `site_log_dir`) - **Runtime**: Runtime files like sockets and PIDs (`user_runtime_dir`, `site_runtime_dir`) Each type has both `user_*` (per-user, writable) and `site_*` (system-wide, read-only for users) variants. ## Documentation Full documentation is available at [platformdirs.readthedocs.io](https://platformdirs.readthedocs.io): - **[Getting started tutorial](https://platformdirs.readthedocs.io/en/latest/tutorial.html)** -- learn core concepts through real-world examples - **[How-to guides](https://platformdirs.readthedocs.io/en/latest/howto.html)** -- recipes for common tasks and platform-specific tips - **[API reference](https://platformdirs.readthedocs.io/en/latest/api.html)** -- complete list of functions and classes - **[Platform details](https://platformdirs.readthedocs.io/en/latest/platforms.html)** -- default paths for each operating system Contributions are welcome! See [CONTRIBUTING.md](https://github.com/tox-dev/platformdirs/blob/main/CONTRIBUTING.md) for details. platformdirs-4.9.4/pyproject.toml0000644000000000000000000001174513615410400014102 0ustar00[build-system] build-backend = "hatchling.build" requires = [ "hatch-vcs>=0.5", "hatchling>=1.29", ] [project] name = "platformdirs" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." readme = "README.md" keywords = [ "appdirs", "application", "cache", "directory", "log", "user", ] license = "MIT" maintainers = [ { name = "Bernát Gábor", email = "gaborjbernat@gmail.com" }, { name = "Julian Berman", email = "Julian@GrayVines.com" }, { name = "Ofek Lev", email = "oss@ofek.dev" }, { name = "Ronny Pfannschmidt", email = "opensource@ronnypfannschmidt.de" }, ] requires-python = ">=3.10" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", ] dynamic = [ "version", ] urls.Changelog = "https://platformdirs.readthedocs.io/en/latest/changelog.html" urls.Documentation = "https://platformdirs.readthedocs.io" urls.Homepage = "https://github.com/tox-dev/platformdirs" urls.Source = "https://github.com/tox-dev/platformdirs" urls.Tracker = "https://github.com/tox-dev/platformdirs/issues" [dependency-groups] dev = [ { include-group = "coverage" }, { include-group = "docs" }, { include-group = "fix" }, { include-group = "pkg-meta" }, { include-group = "test" }, { include-group = "type" }, ] test = [ "appdirs==1.4.4", "covdefaults>=2.3", "diff-cover>=10.2", "pytest>=9.0.2", "pytest-cov>=7", "pytest-mock>=3.15.1", ] type = [ "ty>=0.0.19", { include-group = "release" }, { include-group = "test" }, ] docs = [ "furo>=2025.12.19", "proselint>=0.16", "sphinx>=9.1", "sphinx-autodoc-typehints>=3.9.2", "sphinx-copybutton>=0.5.2", "sphinx-design>=0.7", "sphinx-sitemap>=2.9", "sphinxcontrib-mermaid>=2", "sphinxext-opengraph>=0.13", ] coverage = [ "covdefaults>=2.3", "coverage[toml]>=7.13.4", "diff-cover>=10.2", ] fix = [ "pre-commit-uv>=4.2.1", ] pkg-meta = [ "check-wheel-contents>=0.6.3", "twine>=6.2", "uv>=0.10.7", ] release = [ "gitpython>=3.1.46", "pygithub>=2.8.1", "pyyaml>=6.0.3", ] [tool.hatch] build.hooks.vcs.version-file = "src/platformdirs/version.py" build.targets.sdist.include = [ "/src", "/tests", "/tox.toml", ] version.source = "vcs" [tool.ruff] line-length = 120 format.preview = true lint.select = [ "ALL", ] lint.ignore = [ "COM812", # conflict "CPY", # no copyright notices "D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible "D205", # 1 blank line required between summary line and description "D213", # `multi-line-summary-second-line` (D213) conflicts with docstrfmt which uses D212 style "D301", # Use `r"""` if any backslashes in a docstring "D401", # The first line of docstring should be in imperative mood "DOC", # no support for restructuredtext "E501", # line length handled by ruff format (code) and docstrfmt (docstrings) "RUF067", # `__init__` module should only contain docstrings and re-exports "S104", # Possible binding to all interfaces ] lint.per-file-ignores."tasks/**/*.py" = [ "D", # don't care about documentation in tasks scripts "INP001", # no implicit namespace "S603", # `subprocess` call: check for execution of untrusted input "T201", # print allowed in scripts ] lint.per-file-ignores."tests/**/*.py" = [ "D", # don't care about documentation in tests "FBT", # don't care about booleans as positional arguments in tests "INP001", # no implicit namespace "PLC2701", # Private name import "PLR0917", # Too many positional arguments "PLR2004", # Magic value used in comparison, consider replacing with a constant variable "S101", # asserts allowed in tests "S603", # `subprocess` call: check for execution of untrusted input ] lint.isort = { known-first-party = [ "platformdirs", "tests", ], required-imports = [ "from __future__ import annotations", ] } lint.preview = true [tool.codespell] builtin = "clear,usage,en-GB_to_en-US" count = true quiet-level = 3 [tool.pyproject-fmt] max_supported_python = "3.14" [tool.coverage] run.parallel = true run.plugins = [ "covdefaults", ] paths.other = [ ".", "*/platformdirs", "*\\platformdirs", ] paths.source = [ "src", ".tox/*/lib/*/site-packages", ".tox\\*\\Lib\\site-packages", "**/src", "**\\src", ] report.fail_under = 76 html.show_contexts = true html.skip_covered = false [tool.ty] environment.python-version = "3.14" platformdirs-4.9.4/PKG-INFO0000644000000000000000000001111313615410400012250 0ustar00Metadata-Version: 2.4 Name: platformdirs Version: 4.9.4 Summary: A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`. Project-URL: Changelog, https://platformdirs.readthedocs.io/en/latest/changelog.html Project-URL: Documentation, https://platformdirs.readthedocs.io Project-URL: Homepage, https://github.com/tox-dev/platformdirs Project-URL: Source, https://github.com/tox-dev/platformdirs Project-URL: Tracker, https://github.com/tox-dev/platformdirs/issues Maintainer-email: Bernát Gábor , Julian Berman , Ofek Lev , Ronny Pfannschmidt License-Expression: MIT License-File: LICENSE Keywords: appdirs,application,cache,directory,log,user Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.10 Description-Content-Type: text/markdown # platformdirs [![PyPI version](https://badge.fury.io/py/platformdirs.svg)](https://badge.fury.io/py/platformdirs) [![Python versions](https://img.shields.io/pypi/pyversions/platformdirs.svg)](https://pypi.python.org/pypi/platformdirs/) [![CI](https://github.com/tox-dev/platformdirs/actions/workflows/check.yaml/badge.svg)](https://github.com/platformdirs/platformdirs/actions) [![Downloads](https://static.pepy.tech/badge/platformdirs/month)](https://pepy.tech/project/platformdirs) A Python package for determining platform-specific directories (e.g. user data, config, cache, logs). Handles the differences between macOS, Windows, Linux/Unix, and Android so you don't have to. ## Quick start ```python from platformdirs import PlatformDirs dirs = PlatformDirs("MyApp", "MyCompany") dirs.user_data_dir # ~/.local/share/MyApp (Linux) dirs.user_config_dir # ~/.config/MyApp (Linux) dirs.user_cache_dir # ~/.cache/MyApp (Linux) dirs.user_state_dir # ~/.local/state/MyApp (Linux) dirs.user_log_dir # ~/.local/state/MyApp/log (Linux) dirs.user_documents_dir # ~/Documents dirs.user_downloads_dir # ~/Downloads dirs.user_runtime_dir # /run/user//MyApp (Linux) ``` For Path objects instead of strings: ```python from platformdirs import PlatformDirs dirs = PlatformDirs("MyApp", "MyCompany") dirs.user_data_path # pathlib.Path('~/.local/share/MyApp') dirs.user_config_path # pathlib.Path('~/.config/MyApp') ``` Convenience functions for quick access: ```python from platformdirs import user_data_dir, user_config_path user_data_dir("MyApp", "MyCompany") # returns str user_config_path("MyApp", "MyCompany") # returns pathlib.Path ``` ## Directory types - **Data**: Persistent application data (`user_data_dir`, `site_data_dir`) - **Config**: Configuration files and settings (`user_config_dir`, `site_config_dir`) - **Cache**: Cached data that can be regenerated (`user_cache_dir`, `site_cache_dir`) - **State**: Non-essential runtime state like window positions (`user_state_dir`, `site_state_dir`) - **Logs**: Log files (`user_log_dir`, `site_log_dir`) - **Runtime**: Runtime files like sockets and PIDs (`user_runtime_dir`, `site_runtime_dir`) Each type has both `user_*` (per-user, writable) and `site_*` (system-wide, read-only for users) variants. ## Documentation Full documentation is available at [platformdirs.readthedocs.io](https://platformdirs.readthedocs.io): - **[Getting started tutorial](https://platformdirs.readthedocs.io/en/latest/tutorial.html)** -- learn core concepts through real-world examples - **[How-to guides](https://platformdirs.readthedocs.io/en/latest/howto.html)** -- recipes for common tasks and platform-specific tips - **[API reference](https://platformdirs.readthedocs.io/en/latest/api.html)** -- complete list of functions and classes - **[Platform details](https://platformdirs.readthedocs.io/en/latest/platforms.html)** -- default paths for each operating system Contributions are welcome! See [CONTRIBUTING.md](https://github.com/tox-dev/platformdirs/blob/main/CONTRIBUTING.md) for details.