platformdirs-4.2.2/src/platformdirs/__init__.py0000644000000000000000000005332113615410400016557 0ustar00""" Utilities for determining application-specific dirs. 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 def _set_platform_dir_class() -> type[PlatformDirsABC]: if sys.platform == "win32": from platformdirs.windows import Windows as Result # noqa: PLC0415 elif sys.platform == "darwin": from platformdirs.macos import MacOS as Result # noqa: PLC0415 else: from platformdirs.unix import Unix as Result # noqa: PLC0415 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 PlatformDirs = _set_platform_dir_class() #: Currently active platform AppDirs = PlatformDirs #: Backwards compatibility with appdirs def user_data_dir( appname: str | None = None, appauthor: str | None | Literal[False] = None, version: str | None = None, roaming: 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 roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :returns: data directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, ).user_data_dir def site_data_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :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( appname: str | None = None, appauthor: str | None | Literal[False] = None, version: str | None = None, roaming: 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 roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :returns: config directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, ).user_config_dir def site_config_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :param ensure_exists: See `ensure_exists `. :returns: config directory shared by the users """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, multipath=multipath, ensure_exists=ensure_exists, ).site_config_dir def user_cache_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :param ensure_exists: See `ensure_exists `. :returns: cache directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).user_cache_dir def site_cache_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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 tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_cache_dir def user_state_dir( appname: str | None = None, appauthor: str | None | Literal[False] = None, version: str | None = None, roaming: 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 roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :returns: state directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, ).user_state_dir def user_log_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :param ensure_exists: See `ensure_exists `. :returns: log directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).user_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_runtime_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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 tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).user_runtime_dir def site_runtime_dir( appname: str | None = None, appauthor: str | None | Literal[False] = 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( appname: str | None = None, appauthor: str | None | Literal[False] = None, version: str | None = None, roaming: 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 roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :returns: data path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, ).user_data_path def site_data_path( appname: str | None = None, appauthor: str | None | Literal[False] = 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( appname: str | None = None, appauthor: str | None | Literal[False] = None, version: str | None = None, roaming: 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 roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :returns: config path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, ).user_config_path def site_config_path( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :param ensure_exists: See `ensure_exists `. :returns: config path shared by the 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 | None | Literal[False] = 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 directory tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).site_cache_path def user_cache_path( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :param ensure_exists: See `ensure_exists `. :returns: cache path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).user_cache_path def user_state_path( appname: str | None = None, appauthor: str | None | Literal[False] = None, version: str | None = None, roaming: 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 roaming: See `roaming `. :param ensure_exists: See `ensure_exists `. :returns: state path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, roaming=roaming, ensure_exists=ensure_exists, ).user_state_path def user_log_path( appname: str | None = None, appauthor: str | None | Literal[False] = 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 `roaming `. :param ensure_exists: See `ensure_exists `. :returns: log path tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).user_log_path def user_documents_path() -> Path: """:returns: documents a 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_runtime_path( appname: str | None = None, appauthor: str | None | Literal[False] = 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 tied to the user """ return PlatformDirs( appname=appname, appauthor=appauthor, version=version, opinion=opinion, ensure_exists=ensure_exists, ).user_runtime_path def site_runtime_path( appname: str | None = None, appauthor: str | None | Literal[False] = 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_cache_dir", "site_cache_path", "site_config_dir", "site_config_path", "site_data_dir", "site_data_path", "site_runtime_dir", "site_runtime_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.2.2/src/platformdirs/__main__.py0000644000000000000000000000272513615410400016542 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_runtime_dir", "site_data_dir", "site_config_dir", "site_cache_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.2.2/src/platformdirs/android.py0000644000000000000000000002147013615410400016440 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): """ Follows the guidance `from here `_. Makes use of the `appname `, `version `, `ensure_exists `. """ @property def user_data_dir(self) -> str: """:return: 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: """:return: data directory shared by users, same as `user_data_dir`""" return self.user_data_dir @property def user_config_dir(self) -> str: """ :return: 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: """:return: config directory shared by the users, same as `user_config_dir`""" return self.user_config_dir @property def user_cache_dir(self) -> str: """:return: 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: """:return: cache directory shared by users, same as `user_cache_dir`""" return self.user_cache_dir @property def user_state_dir(self) -> str: """:return: state directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def user_log_dir(self) -> str: """ :return: 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 return path @property def user_documents_dir(self) -> str: """:return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``""" return _android_documents_folder() @property def user_downloads_dir(self) -> str: """:return: downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``""" return _android_downloads_folder() @property def user_pictures_dir(self) -> str: """:return: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``""" return _android_pictures_folder() @property def user_videos_dir(self) -> str: """:return: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``""" return _android_videos_folder() @property def user_music_dir(self) -> str: """:return: music directory tied to the user e.g. ``/storage/emulated/0/Music``""" return _android_music_folder() @property def user_desktop_dir(self) -> str: """:return: desktop directory tied to the user e.g. ``/storage/emulated/0/Desktop``""" return "/storage/emulated/0/Desktop" @property def user_runtime_dir(self) -> str: """ :return: 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 return path @property def site_runtime_dir(self) -> str: """:return: 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, PLR0912 """:return: 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 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: """:return: documents folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 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: """:return: downloads folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 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: """:return: pictures folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 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: """:return: videos folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 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: """:return: music folder for the Android OS""" # Get directories with pyjnius try: from jnius import autoclass # noqa: PLC0415 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.2.2/src/platformdirs/api.py0000644000000000000000000002144413615410400015572 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 typing import Iterator, Literal class PlatformDirsABC(ABC): # noqa: PLR0904 """Abstract base class for platform directories.""" def __init__( # noqa: PLR0913, PLR0917 self, appname: str | None = None, appauthor: str | None | Literal[False] = 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 ) -> 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`. """ self.appname = appname #: The name of 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. """ self.opinion = opinion #: A flag to indicating to use opinionated values. 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. """ 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) @property @abstractmethod def user_data_dir(self) -> str: """:return: data directory tied to the user""" @property @abstractmethod def site_data_dir(self) -> str: """:return: data directory shared by users""" @property @abstractmethod def user_config_dir(self) -> str: """:return: config directory tied to the user""" @property @abstractmethod def site_config_dir(self) -> str: """:return: config directory shared by the users""" @property @abstractmethod def user_cache_dir(self) -> str: """:return: cache directory tied to the user""" @property @abstractmethod def site_cache_dir(self) -> str: """:return: cache directory shared by users""" @property @abstractmethod def user_state_dir(self) -> str: """:return: state directory tied to the user""" @property @abstractmethod def user_log_dir(self) -> str: """:return: log directory tied to the user""" @property @abstractmethod def user_documents_dir(self) -> str: """:return: documents directory tied to the user""" @property @abstractmethod def user_downloads_dir(self) -> str: """:return: downloads directory tied to the user""" @property @abstractmethod def user_pictures_dir(self) -> str: """:return: pictures directory tied to the user""" @property @abstractmethod def user_videos_dir(self) -> str: """:return: videos directory tied to the user""" @property @abstractmethod def user_music_dir(self) -> str: """:return: music directory tied to the user""" @property @abstractmethod def user_desktop_dir(self) -> str: """:return: desktop directory tied to the user""" @property @abstractmethod def user_runtime_dir(self) -> str: """:return: runtime directory tied to the user""" @property @abstractmethod def site_runtime_dir(self) -> str: """:return: runtime directory shared by users""" @property def user_data_path(self) -> Path: """:return: data path tied to the user""" return Path(self.user_data_dir) @property def site_data_path(self) -> Path: """:return: data path shared by users""" return Path(self.site_data_dir) @property def user_config_path(self) -> Path: """:return: config path tied to the user""" return Path(self.user_config_dir) @property def site_config_path(self) -> Path: """:return: config path shared by the users""" return Path(self.site_config_dir) @property def user_cache_path(self) -> Path: """:return: cache path tied to the user""" return Path(self.user_cache_dir) @property def site_cache_path(self) -> Path: """:return: cache path shared by users""" return Path(self.site_cache_dir) @property def user_state_path(self) -> Path: """:return: state path tied to the user""" return Path(self.user_state_dir) @property def user_log_path(self) -> Path: """:return: log path tied to the user""" return Path(self.user_log_dir) @property def user_documents_path(self) -> Path: """:return: documents a path tied to the user""" return Path(self.user_documents_dir) @property def user_downloads_path(self) -> Path: """:return: downloads path tied to the user""" return Path(self.user_downloads_dir) @property def user_pictures_path(self) -> Path: """:return: pictures path tied to the user""" return Path(self.user_pictures_dir) @property def user_videos_path(self) -> Path: """:return: videos path tied to the user""" return Path(self.user_videos_dir) @property def user_music_path(self) -> Path: """:return: music path tied to the user""" return Path(self.user_music_dir) @property def user_desktop_path(self) -> Path: """:return: desktop path tied to the user""" return Path(self.user_desktop_dir) @property def user_runtime_path(self) -> Path: """:return: runtime path tied to the user""" return Path(self.user_runtime_dir) @property def site_runtime_path(self) -> Path: """:return: 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_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_runtime_paths(self) -> Iterator[Path]: """:yield: all user and site runtime paths.""" for path in self.iter_runtime_dirs(): yield Path(path) platformdirs-4.2.2/src/platformdirs/macos.py0000644000000000000000000001271413615410400016123 0ustar00"""macOS.""" from __future__ import annotations import os.path import sys from .api import PlatformDirsABC class MacOS(PlatformDirsABC): """ Platform directories for the macOS operating system. Follows the guidance from `Apple documentation `_. Makes use of the `appname `, `version `, `ensure_exists `. """ @property def user_data_dir(self) -> str: """:return: 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_dir(self) -> str: """ :return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``. If we're using a Python binary managed by `Homebrew `_, the directory will be under the Homebrew prefix, e.g. ``/opt/homebrew/share/$appname/$version``. If `multipath ` is enabled, and we're in Homebrew, the response is a multi-path string separated by ":", e.g. ``/opt/homebrew/share/$appname/$version:/Library/Application Support/$appname/$version`` """ is_homebrew = sys.prefix.startswith("/opt/homebrew") path_list = [self._append_app_name_and_version("/opt/homebrew/share")] if is_homebrew else [] path_list.append(self._append_app_name_and_version("/Library/Application Support")) if self.multipath: return os.pathsep.join(path_list) return path_list[0] @property def user_config_dir(self) -> str: """:return: config directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_config_dir(self) -> str: """:return: config directory shared by the users, same as `site_data_dir`""" return self.site_data_dir @property def user_cache_dir(self) -> str: """:return: 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: """ :return: 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. ``/opt/homebrew/var/cache/$appname/$version``. If `multipath ` is enabled, and we're in Homebrew, the response is a multi-path string separated by ":", e.g. ``/opt/homebrew/var/cache/$appname/$version:/Library/Caches/$appname/$version`` """ is_homebrew = sys.prefix.startswith("/opt/homebrew") path_list = [self._append_app_name_and_version("/opt/homebrew/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 user_state_dir(self) -> str: """:return: state directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def user_log_dir(self) -> str: """:return: 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 user_documents_dir(self) -> str: """:return: documents directory tied to the user, e.g. ``~/Documents``""" return os.path.expanduser("~/Documents") # noqa: PTH111 @property def user_downloads_dir(self) -> str: """:return: downloads directory tied to the user, e.g. ``~/Downloads``""" return os.path.expanduser("~/Downloads") # noqa: PTH111 @property def user_pictures_dir(self) -> str: """:return: pictures directory tied to the user, e.g. ``~/Pictures``""" return os.path.expanduser("~/Pictures") # noqa: PTH111 @property def user_videos_dir(self) -> str: """:return: videos directory tied to the user, e.g. ``~/Movies``""" return os.path.expanduser("~/Movies") # noqa: PTH111 @property def user_music_dir(self) -> str: """:return: music directory tied to the user, e.g. ``~/Music``""" return os.path.expanduser("~/Music") # noqa: PTH111 @property def user_desktop_dir(self) -> str: """:return: desktop directory tied to the user, e.g. ``~/Desktop``""" return os.path.expanduser("~/Desktop") # noqa: PTH111 @property def user_runtime_dir(self) -> str: """:return: 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: """:return: runtime directory shared by users, same as `user_runtime_dir`""" return self.user_runtime_dir __all__ = [ "MacOS", ] platformdirs-4.2.2/src/platformdirs/py.typed0000644000000000000000000000000013615410400016127 0ustar00platformdirs-4.2.2/src/platformdirs/unix.py0000644000000000000000000002462313615410400016006 0ustar00"""Unix.""" from __future__ import annotations import os import sys from configparser import ConfigParser from pathlib import Path from typing import Iterator, NoReturn from .api import PlatformDirsABC if sys.platform == "win32": def getuid() -> NoReturn: msg = "should only be used on Unix" raise RuntimeError(msg) else: from os import getuid class Unix(PlatformDirsABC): # noqa: PLR0904 """ 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: """ :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or ``$XDG_DATA_HOME/$appname/$version`` """ path = os.environ.get("XDG_DATA_HOME", "") if not path.strip(): path = os.path.expanduser("~/.local/share") # noqa: PTH111 return self._append_app_name_and_version(path) @property def _site_data_dirs(self) -> list[str]: path = os.environ.get("XDG_DATA_DIRS", "") if not path.strip(): path = f"/usr/local/share{os.pathsep}/usr/share" return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)] @property def site_data_dir(self) -> str: """ :return: data directories shared by users (if `multipath ` is enabled and ``XDG_DATA_DIRS`` is set and a multi path the response is also a multi path separated by the OS path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` """ # XDG default for $XDG_DATA_DIRS; only first, if multipath is False dirs = self._site_data_dirs if not self.multipath: return dirs[0] return os.pathsep.join(dirs) @property def user_config_dir(self) -> str: """ :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or ``$XDG_CONFIG_HOME/$appname/$version`` """ path = os.environ.get("XDG_CONFIG_HOME", "") if not path.strip(): path = os.path.expanduser("~/.config") # noqa: PTH111 return self._append_app_name_and_version(path) @property def _site_config_dirs(self) -> list[str]: path = os.environ.get("XDG_CONFIG_DIRS", "") if not path.strip(): path = "/etc/xdg" return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)] @property def site_config_dir(self) -> str: """ :return: config directories shared by users (if `multipath ` is enabled and ``XDG_CONFIG_DIRS`` is set and a multi path the response is also a multi path separated by the OS path separator), e.g. ``/etc/xdg/$appname/$version`` """ # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False dirs = self._site_config_dirs if not self.multipath: return dirs[0] return os.pathsep.join(dirs) @property def user_cache_dir(self) -> str: """ :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or ``~/$XDG_CACHE_HOME/$appname/$version`` """ path = os.environ.get("XDG_CACHE_HOME", "") if not path.strip(): path = os.path.expanduser("~/.cache") # noqa: PTH111 return self._append_app_name_and_version(path) @property def site_cache_dir(self) -> str: """:return: 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: """ :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or ``$XDG_STATE_HOME/$appname/$version`` """ path = os.environ.get("XDG_STATE_HOME", "") if not path.strip(): path = os.path.expanduser("~/.local/state") # noqa: PTH111 return self._append_app_name_and_version(path) @property def user_log_dir(self) -> str: """:return: 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 user_documents_dir(self) -> str: """:return: 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: """:return: 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: """:return: 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: """:return: 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: """:return: 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: """:return: desktop directory tied to the user, e.g. ``~/Desktop``""" return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop") @property def user_runtime_dir(self) -> str: """ :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or ``$XDG_RUNTIME_DIR/$appname/$version``. For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR`` is not set. """ path = os.environ.get("XDG_RUNTIME_DIR", "") if not path.strip(): if sys.platform.startswith(("freebsd", "openbsd", "netbsd")): path = f"/var/run/user/{getuid()}" if not Path(path).exists(): path = f"/tmp/runtime-{getuid()}" # noqa: S108 else: path = f"/run/user/{getuid()}" return self._append_app_name_and_version(path) @property def site_runtime_dir(self) -> str: """ :return: 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. """ path = os.environ.get("XDG_RUNTIME_DIR", "") if not path.strip(): 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: """:return: 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: """:return: config path shared by the 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: """:return: 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 _first_item_as_path_if_multipath(self, directory: str) -> Path: if self.multipath: # If multipath is True, the first path is returned. directory = directory.split(os.pathsep)[0] return Path(directory) 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 def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str: media_dir = _get_user_dirs_folder(env_var) if media_dir is None: media_dir = os.environ.get(env_var, "").strip() if not media_dir: media_dir = os.path.expanduser(fallback_tilde_path) # noqa: PTH111 return media_dir 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/. """ user_dirs_config_path = Path(Unix().user_config_dir) / "user-dirs.dirs" if user_dirs_config_path.exists(): parser = ConfigParser() with user_dirs_config_path.open() as stream: # Add fake section header, so ConfigParser doesn't complain parser.read_string(f"[top]\n{stream.read()}") if key not in parser["top"]: return None path = parser["top"][key].strip('"') # Handle relative home paths return path.replace("$HOME", os.path.expanduser("~")) # noqa: PTH111 return None __all__ = [ "Unix", ] platformdirs-4.2.2/src/platformdirs/version.py0000644000000000000000000000063313615410400016503 0ustar00# file generated by setuptools_scm # don't change, don't track in version control TYPE_CHECKING = False if TYPE_CHECKING: from typing import Tuple, Union VERSION_TUPLE = Tuple[Union[int, str], ...] else: VERSION_TUPLE = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE __version__ = version = '4.2.2' __version_tuple__ = version_tuple = (4, 2, 2) platformdirs-4.2.2/src/platformdirs/windows.py0000644000000000000000000002361513615410400016515 0ustar00"""Windows.""" from __future__ import annotations import os import sys from functools import lru_cache from typing import TYPE_CHECKING from .api import PlatformDirsABC if TYPE_CHECKING: from collections.abc import Callable class Windows(PlatformDirsABC): """ `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: """ :return: 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: """:return: 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: """:return: config directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def site_config_dir(self) -> str: """:return: config directory shared by the users, same as `site_data_dir`""" return self.site_data_dir @property def user_cache_dir(self) -> str: """ :return: 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: """:return: 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: """:return: state directory tied to the user, same as `user_data_dir`""" return self.user_data_dir @property def user_log_dir(self) -> str: """:return: 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 user_documents_dir(self) -> str: """:return: 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: """:return: 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: """:return: 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: """:return: 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: """:return: 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: """:return: desktop directory tied to the user, e.g. ``%USERPROFILE%\\Desktop``""" return os.path.normpath(get_win_folder("CSIDL_DESKTOPDIRECTORY")) @property def user_runtime_dir(self) -> str: """ :return: 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: """:return: 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: """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 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. """ 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", }.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 key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") directory, _ = winreg.QueryValueEx(key, shell_folder_name) return str(directory) def get_win_folder_via_ctypes(csidl_name: str) -> str: """Get folder with ctypes.""" # There is no 'CSIDL_DOWNLOADS'. # Use 'CSIDL_PROFILE' (40) and append the default folder 'Downloads' instead. # https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid import ctypes # noqa: PLC0415 csidl_const = { "CSIDL_APPDATA": 26, "CSIDL_COMMON_APPDATA": 35, "CSIDL_LOCAL_APPDATA": 28, "CSIDL_PERSONAL": 5, "CSIDL_MYPICTURES": 39, "CSIDL_MYVIDEO": 14, "CSIDL_MYMUSIC": 13, "CSIDL_DOWNLOADS": 40, "CSIDL_DESKTOPDIRECTORY": 16, }.get(csidl_name) if csidl_const is None: msg = f"Unknown CSIDL name: {csidl_name}" raise ValueError(msg) buf = ctypes.create_unicode_buffer(1024) windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) # Downgrade to short path name if it has high-bit chars. if any(ord(c) > 255 for c in buf): # noqa: PLR2004 buf2 = ctypes.create_unicode_buffer(1024) if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): buf = buf2 if csidl_name == "CSIDL_DOWNLOADS": return os.path.join(buf.value, "Downloads") # noqa: PTH118 return buf.value def _pick_get_win_folder() -> Callable[[str], str]: try: import ctypes # noqa: PLC0415 except ImportError: pass else: if hasattr(ctypes, "windll"): return get_win_folder_via_ctypes try: import winreg # noqa: PLC0415, F401 except ImportError: return get_win_folder_from_env_vars else: return get_win_folder_from_registry get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder()) __all__ = [ "Windows", ] platformdirs-4.2.2/tests/conftest.py0000644000000000000000000000150513615410400014507 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_runtime_dir", "site_data_dir", "site_config_dir", "site_cache_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.2.2/tests/test_android.py0000644000000000000000000001400613615410400015341 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 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}", "user_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_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 platformdirs-4.2.2/tests/test_api.py0000644000000000000000000000761613615410400014503 0ustar00from __future__ import annotations import builtins import functools import inspect import sys from pathlib import Path from typing import TYPE_CHECKING, Any, Callable import pytest import platformdirs from platformdirs.android import Android builtin_import = builtins.__import__ if TYPE_CHECKING: from types import ModuleType def test_package_metadata() -> None: assert hasattr(platformdirs, "__version__") 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, *args: Any, **kwargs: Any) -> ModuleType: # noqa: ANN401 if name == "ctypes": msg = f"No module named {name}" raise ModuleNotFoundError(msg) return builtin_import(name, *args, **kwargs) def mock_import(func: Callable[[], None]) -> Callable[[], None]: @functools.wraps(func) def wrap() -> None: platformdirs_module_items = [item for item in sys.modules.items() if item[0].startswith("platformdirs")] try: builtins.__import__ = _fake_import for name, _ in platformdirs_module_items: del sys.modules[name] return func() finally: # restore original modules builtins.__import__ = builtin_import for name, module in platformdirs_module_items: sys.modules[name] = module return wrap @mock_import def test_no_ctypes() -> None: import platformdirs # noqa: PLC0415 assert platformdirs platformdirs-4.2.2/tests/test_comp_with_appdirs.py0000644000000000000000000000451513615410400017440 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.2.2/tests/test_macos.py0000644000000000000000000000775713615410400015042 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 @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.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(mocker: MockerFixture, params: dict[str, Any], func: str) -> None: # Make sure we are not in Homebrew 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}", "user_log_dir": f"{home}/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_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", ], ) @pytest.mark.parametrize("multipath", [pytest.param(True, id="multipath"), pytest.param(False, id="singlepath")]) def test_macos_homebrew(mocker: MockerFixture, params: dict[str, Any], multipath: bool, site_func: str) -> None: mocker.patch("sys.prefix", "/opt/homebrew/opt/python") 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_map = { "site_data_dir": f"/opt/homebrew/share{suffix}", "site_config_dir": f"/opt/homebrew/share{suffix}", "site_cache_dir": f"/opt/homebrew/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_map[site_func] assert result == expected platformdirs-4.2.2/tests/test_main.py0000644000000000000000000000075013615410400014646 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.2.2/tests/test_unix.py0000644000000000000000000001547613615410400014720 0ustar00from __future__ import annotations import importlib import os import sys import typing 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.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", "/run/user/1234"), "site_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", "/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.usefixtures("_getuid") @pytest.mark.parametrize("platform", ["freebsd", "openbsd", "netbsd"]) def test_platform_on_bsd(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, platform: str) -> None: monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) mocker.patch("sys.platform", platform) assert Unix().site_runtime_dir == "/var/run" mocker.patch("pathlib.Path.exists", return_value=True) assert Unix().user_runtime_dir == "/var/run/user/1234" mocker.patch("pathlib.Path.exists", return_value=False) 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 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() platformdirs-4.2.2/.gitignore0000644000000000000000000000011613615410400013133 0ustar00*.pyc *.egg-info /dist /.tox /src/platformdirs/version.py /report /docs/build platformdirs-4.2.2/LICENSE0000644000000000000000000000210113615410400012144 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.2.2/README.rst0000644000000000000000000002217413615410400012642 0ustar00The problem =========== .. image:: https://github.com/platformdirs/platformdirs/actions/workflows/check.yml/badge.svg :target: https://github.com/platformdirs/platformdirs/actions When writing desktop application, finding the right location to store user data and configuration varies per platform. Even for single-platform apps, there may by plenty of nuances in figuring out the right location. For example, if running on macOS, you should use:: ~/Library/Application Support/ If on Windows (at least English Win) that should be:: C:\Documents and Settings\\Application Data\Local Settings\\ or possibly:: C:\Documents and Settings\\Application Data\\ for `roaming profiles `_ but that is another story. On Linux (and other Unices), according to the `XDG Basedir Spec`_, it should be:: ~/.local/share/ .. _XDG Basedir Spec: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html ``platformdirs`` to the rescue ============================== This kind of thing is what the ``platformdirs`` package is for. ``platformdirs`` will help you choose an appropriate: - user data dir (``user_data_dir``) - user config dir (``user_config_dir``) - user cache dir (``user_cache_dir``) - site data dir (``site_data_dir``) - site config dir (``site_config_dir``) - user log dir (``user_log_dir``) - user documents dir (``user_documents_dir``) - user downloads dir (``user_downloads_dir``) - user pictures dir (``user_pictures_dir``) - user videos dir (``user_videos_dir``) - user music dir (``user_music_dir``) - user desktop dir (``user_desktop_dir``) - user runtime dir (``user_runtime_dir``) And also: - Is slightly opinionated on the directory names used. Look for "OPINION" in documentation and code for when an opinion is being applied. Example output ============== On macOS: .. code-block:: pycon >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) '/Users/trentm/Library/Application Support/SuperApp' >>> site_data_dir(appname, appauthor) '/Library/Application Support/SuperApp' >>> user_cache_dir(appname, appauthor) '/Users/trentm/Library/Caches/SuperApp' >>> user_log_dir(appname, appauthor) '/Users/trentm/Library/Logs/SuperApp' >>> user_documents_dir() '/Users/trentm/Documents' >>> user_downloads_dir() '/Users/trentm/Downloads' >>> user_pictures_dir() '/Users/trentm/Pictures' >>> user_videos_dir() '/Users/trentm/Movies' >>> user_music_dir() '/Users/trentm/Music' >>> user_desktop_dir() '/Users/trentm/Desktop' >>> user_runtime_dir(appname, appauthor) '/Users/trentm/Library/Caches/TemporaryItems/SuperApp' On Windows: .. code-block:: pycon >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' >>> user_data_dir(appname, appauthor, roaming=True) 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp' >>> user_cache_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache' >>> user_log_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Logs' >>> user_documents_dir() 'C:\\Users\\trentm\\Documents' >>> user_downloads_dir() 'C:\\Users\\trentm\\Downloads' >>> user_pictures_dir() 'C:\\Users\\trentm\\Pictures' >>> user_videos_dir() 'C:\\Users\\trentm\\Videos' >>> user_music_dir() 'C:\\Users\\trentm\\Music' >>> user_desktop_dir() 'C:\\Users\\trentm\\Desktop' >>> user_runtime_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Temp\\Acme\\SuperApp' On Linux: .. code-block:: pycon >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) '/home/trentm/.local/share/SuperApp' >>> site_data_dir(appname, appauthor) '/usr/local/share/SuperApp' >>> site_data_dir(appname, appauthor, multipath=True) '/usr/local/share/SuperApp:/usr/share/SuperApp' >>> user_cache_dir(appname, appauthor) '/home/trentm/.cache/SuperApp' >>> user_log_dir(appname, appauthor) '/home/trentm/.local/state/SuperApp/log' >>> user_config_dir(appname) '/home/trentm/.config/SuperApp' >>> user_documents_dir() '/home/trentm/Documents' >>> user_downloads_dir() '/home/trentm/Downloads' >>> user_pictures_dir() '/home/trentm/Pictures' >>> user_videos_dir() '/home/trentm/Videos' >>> user_music_dir() '/home/trentm/Music' >>> user_desktop_dir() '/home/trentm/Desktop' >>> user_runtime_dir(appname, appauthor) '/run/user/{os.getuid()}/SuperApp' >>> site_config_dir(appname) '/etc/xdg/SuperApp' >>> os.environ["XDG_CONFIG_DIRS"] = "/etc:/usr/local/etc" >>> site_config_dir(appname, multipath=True) '/etc/SuperApp:/usr/local/etc/SuperApp' On Android:: >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) '/data/data/com.myApp/files/SuperApp' >>> user_cache_dir(appname, appauthor) '/data/data/com.myApp/cache/SuperApp' >>> user_log_dir(appname, appauthor) '/data/data/com.myApp/cache/SuperApp/log' >>> user_config_dir(appname) '/data/data/com.myApp/shared_prefs/SuperApp' >>> 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_runtime_dir(appname, appauthor) '/data/data/com.myApp/cache/SuperApp/tmp' Note: Some android apps like Termux and Pydroid are used as shells. These apps are used by the end user to emulate Linux environment. Presence of ``SHELL`` environment variable is used by Platformdirs to differentiate between general android apps and android apps used as shells. Shell android apps also support ``XDG_*`` environment variables. ``PlatformDirs`` for convenience ================================ .. code-block:: pycon >>> from platformdirs import PlatformDirs >>> dirs = PlatformDirs("SuperApp", "Acme") >>> dirs.user_data_dir '/Users/trentm/Library/Application Support/SuperApp' >>> dirs.site_data_dir '/Library/Application Support/SuperApp' >>> dirs.user_cache_dir '/Users/trentm/Library/Caches/SuperApp' >>> dirs.user_log_dir '/Users/trentm/Library/Logs/SuperApp' >>> dirs.user_documents_dir '/Users/trentm/Documents' >>> dirs.user_downloads_dir '/Users/trentm/Downloads' >>> dirs.user_pictures_dir '/Users/trentm/Pictures' >>> dirs.user_videos_dir '/Users/trentm/Movies' >>> dirs.user_music_dir '/Users/trentm/Music' >>> dirs.user_desktop_dir '/Users/trentm/Desktop' >>> dirs.user_runtime_dir '/Users/trentm/Library/Caches/TemporaryItems/SuperApp' Per-version isolation ===================== If you have multiple versions of your app in use that you want to be able to run side-by-side, then you may want version-isolation for these dirs:: >>> from platformdirs import PlatformDirs >>> dirs = PlatformDirs("SuperApp", "Acme", version="1.0") >>> dirs.user_data_dir '/Users/trentm/Library/Application Support/SuperApp/1.0' >>> dirs.site_data_dir '/Library/Application Support/SuperApp/1.0' >>> dirs.user_cache_dir '/Users/trentm/Library/Caches/SuperApp/1.0' >>> dirs.user_log_dir '/Users/trentm/Library/Logs/SuperApp/1.0' >>> dirs.user_documents_dir '/Users/trentm/Documents' >>> dirs.user_downloads_dir '/Users/trentm/Downloads' >>> dirs.user_pictures_dir '/Users/trentm/Pictures' >>> dirs.user_videos_dir '/Users/trentm/Movies' >>> dirs.user_music_dir '/Users/trentm/Music' >>> dirs.user_desktop_dir '/Users/trentm/Desktop' >>> dirs.user_runtime_dir '/Users/trentm/Library/Caches/TemporaryItems/SuperApp/1.0' Be wary of using this for configuration files though; you'll need to handle migrating configuration files manually. Why this Fork? ============== This repository is a friendly fork of the wonderful work started by `ActiveState `_ who created ``appdirs``, this package's ancestor. Maintaining an open source project is no easy task, particularly from within an organization, and the Python community is indebted to ``appdirs`` (and to Trent Mick and Jeff Rouse in particular) for creating an incredibly useful simple module, as evidenced by the wide number of users it has attracted over the years. Nonetheless, given the number of long-standing open issues and pull requests, and no clear path towards `ensuring that maintenance of the package would continue or grow `_, this fork was created. Contributions are most welcome. platformdirs-4.2.2/pyproject.toml0000644000000000000000000001564613615410400014075 0ustar00[build-system] build-backend = "hatchling.build" requires = [ "hatch-vcs>=0.4", "hatchling>=1.18", ] [project] name = "platformdirs" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." readme = "README.rst" 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.8" 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.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", ] dynamic = [ "version", ] optional-dependencies.docs = [ "furo>=2023.9.10", "proselint>=0.13", "sphinx>=7.2.6", "sphinx-autodoc-typehints>=1.25.2", ] optional-dependencies.test = [ "appdirs==1.4.4", "covdefaults>=2.3", "pytest>=7.4.3", "pytest-cov>=4.1", "pytest-mock>=3.12", ] optional-dependencies.type = [ "mypy>=1.8", ] urls.Documentation = "https://platformdirs.readthedocs.io" urls.Homepage = "https://github.com/platformdirs/platformdirs" urls.Source = "https://github.com/platformdirs/platformdirs" urls.Tracker = "https://github.com/platformdirs/platformdirs/issues" [tool.hatch] build.hooks.vcs.version-file = "src/platformdirs/version.py" build.targets.sdist.include = [ "/src", "/tests", "/tox.ini", ] version.source = "vcs" [tool.hatch.envs.default] description = "Development environment" features = [ "test", "docs", "type", ] scripts = { show = [ "python -m pip list --format=columns", 'python -c "import sys; print(sys.executable)"', ] } [tool.hatch.envs.test] template = "test" # dev-mode = false # cannot enable this until https://github.com/pypa/hatch/issues/1237 description = "Run the test suite" matrix = [ { python = [ "3.12", "3.11", "3.10", "3.9", "3.8", "pypy3.9", ] }, ] features = [ "test", ] env-vars = { COVERAGE_FILE = "report/.coverage.{matrix:python}", COVERAGE_PROCESS_START = "pyproject.toml", _COVERAGE_SRC = "src/platformdirs" } [tool.hatch.envs.test.scripts] run = [ """ pytest --junitxml report/junit.{matrix:python}.xml --cov src/platformdirs --cov tests \ --cov-config=pyproject.toml --no-cov-on-fail --cov-report term-missing:skip-covered --cov-context=test \ --cov-report html:report/html{matrix:python} --cov-report xml:report/coverage{matrix:python}.xml \ tests """, ] [tool.hatch.envs.coverage] template = "coverage" description = "combine coverage files and generate diff" dependencies = [ "covdefaults>=2.3", "coverage[toml]>=7.3.2", "diff-cover>=8.0.1", ] env-vars = { COVERAGE_FILE = "report/.coverage" } [tool.hatch.envs.coverage.scripts] run = [ "coverage combine report", "coverage report --skip-covered --show-missing", "coverage xml -o report/coverage.xml", "coverage html -d report/html", "diff-cover --compare-branch {env:DIFF_AGAINST:origin/main} report/coverage.xml", ] [tool.hatch.envs.type] template = "type" description = "Run the type checker" python = "3.12" dev-mode = false features = [ "type", "test", ] scripts = { run = [ "mypy --strict src", "mypy --strict tests", ] } [tool.hatch.envs.fix] template = "fix" description = "Run the pre-commit tool to lint and autofix issues" python = "3.12" detached = true dependencies = [ "pre-commit>=3.6", ] scripts = { "run" = [ "pre-commit run --all-files --show-diff-on-failure", ] } [tool.hatch.envs.docs] template = "docs" description = "Build documentation using Sphinx" python = "3.12" dev-mode = false features = [ "docs", ] [tool.hatch.envs.docs.scripts] build = [ """python -c "import glob; import subprocess; subprocess.call(['proselint'] + glob.glob('docs/*.rst'))" """, """python -c "from shutil import rmtree; rmtree('docs/build', ignore_errors=True)" """, "sphinx-build -d docs/build/tree docs docs/build --color -b html", """python -c "from pathlib import Path; p=(Path('docs')/'build'/'index.html').absolute().as_uri(); print('Documentation built under '+p)" """, ] [tool.hatch.envs.readme] template = "readme" description = "check that the long description is valid" python = "3.12" dependencies = [ "build[virtualenv]>=1.0.3", "twine>=4.0.2", "check-wheel-contents>=0.6.0", ] scripts = { "run" = [ "python -m build -o dist", "twine check dist/*.whl dist/*.tar.gz", "check-wheel-contents dist", ] } [tool.ruff] select = [ "ALL", ] line-length = 120 target-version = "py38" isort = { known-first-party = [ "platformdirs", "tests", ], required-imports = [ "from __future__ import annotations", ] } ignore = [ "ANN101", # Missing type annotation for `self` in method "D301", # Use `r"""` if any backslashes in a docstring "D205", # 1 blank line required between summary line and description "D401", # The first line of docstring should be in imperative mood "D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible "D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible "S104", # Possible binding to all interfaces "CPY", # no copyright notices ] format.preview = true lint.preview = true [tool.ruff.per-file-ignores] "tests/**/*.py" = [ "S101", # asserts allowed in tests... "FBT", # don"t care about booleans as positional arguments in tests "INP001", # no implicit namespace "D", # don't care about documentation in tests "S603", # `subprocess` call: check for execution of untrusted input "PLR2004", # Magic value used in comparison, consider replacing with a constant variable "PLC2701", # Private name import "PLR0917", # Too many positional arguments ] [tool.codespell] builtin = "clear,usage,en-GB_to_en-US" count = true quiet-level = 3 [tool.docformatter] blank = true recursive = true pre-summary-newline = true wrap-descriptions = 120 wrap-summaries = 120 [tool.coverage] html.show_contexts = true html.skip_covered = false run.relative_files = true paths.source = [ "src", "**/site-packages", ] report.fail_under = 76 run.parallel = true run.plugins = [ "covdefaults", ] [tool.mypy] python_version = "3.11" show_error_codes = true strict = true overrides = [ { module = [ "appdirs.*", "jnius.*", ], ignore_missing_imports = true }, ] platformdirs-4.2.2/PKG-INFO0000644000000000000000000002624513615410400012253 0ustar00Metadata-Version: 2.3 Name: platformdirs Version: 4.2.2 Summary: A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`. Project-URL: Documentation, https://platformdirs.readthedocs.io Project-URL: Homepage, https://github.com/platformdirs/platformdirs Project-URL: Source, https://github.com/platformdirs/platformdirs Project-URL: Tracker, https://github.com/platformdirs/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.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.8 Provides-Extra: docs Requires-Dist: furo>=2023.9.10; extra == 'docs' Requires-Dist: proselint>=0.13; extra == 'docs' Requires-Dist: sphinx-autodoc-typehints>=1.25.2; extra == 'docs' Requires-Dist: sphinx>=7.2.6; extra == 'docs' Provides-Extra: test Requires-Dist: appdirs==1.4.4; extra == 'test' Requires-Dist: covdefaults>=2.3; extra == 'test' Requires-Dist: pytest-cov>=4.1; extra == 'test' Requires-Dist: pytest-mock>=3.12; extra == 'test' Requires-Dist: pytest>=7.4.3; extra == 'test' Provides-Extra: type Requires-Dist: mypy>=1.8; extra == 'type' Description-Content-Type: text/x-rst The problem =========== .. image:: https://github.com/platformdirs/platformdirs/actions/workflows/check.yml/badge.svg :target: https://github.com/platformdirs/platformdirs/actions When writing desktop application, finding the right location to store user data and configuration varies per platform. Even for single-platform apps, there may by plenty of nuances in figuring out the right location. For example, if running on macOS, you should use:: ~/Library/Application Support/ If on Windows (at least English Win) that should be:: C:\Documents and Settings\\Application Data\Local Settings\\ or possibly:: C:\Documents and Settings\\Application Data\\ for `roaming profiles `_ but that is another story. On Linux (and other Unices), according to the `XDG Basedir Spec`_, it should be:: ~/.local/share/ .. _XDG Basedir Spec: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html ``platformdirs`` to the rescue ============================== This kind of thing is what the ``platformdirs`` package is for. ``platformdirs`` will help you choose an appropriate: - user data dir (``user_data_dir``) - user config dir (``user_config_dir``) - user cache dir (``user_cache_dir``) - site data dir (``site_data_dir``) - site config dir (``site_config_dir``) - user log dir (``user_log_dir``) - user documents dir (``user_documents_dir``) - user downloads dir (``user_downloads_dir``) - user pictures dir (``user_pictures_dir``) - user videos dir (``user_videos_dir``) - user music dir (``user_music_dir``) - user desktop dir (``user_desktop_dir``) - user runtime dir (``user_runtime_dir``) And also: - Is slightly opinionated on the directory names used. Look for "OPINION" in documentation and code for when an opinion is being applied. Example output ============== On macOS: .. code-block:: pycon >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) '/Users/trentm/Library/Application Support/SuperApp' >>> site_data_dir(appname, appauthor) '/Library/Application Support/SuperApp' >>> user_cache_dir(appname, appauthor) '/Users/trentm/Library/Caches/SuperApp' >>> user_log_dir(appname, appauthor) '/Users/trentm/Library/Logs/SuperApp' >>> user_documents_dir() '/Users/trentm/Documents' >>> user_downloads_dir() '/Users/trentm/Downloads' >>> user_pictures_dir() '/Users/trentm/Pictures' >>> user_videos_dir() '/Users/trentm/Movies' >>> user_music_dir() '/Users/trentm/Music' >>> user_desktop_dir() '/Users/trentm/Desktop' >>> user_runtime_dir(appname, appauthor) '/Users/trentm/Library/Caches/TemporaryItems/SuperApp' On Windows: .. code-block:: pycon >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' >>> user_data_dir(appname, appauthor, roaming=True) 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp' >>> user_cache_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache' >>> user_log_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Logs' >>> user_documents_dir() 'C:\\Users\\trentm\\Documents' >>> user_downloads_dir() 'C:\\Users\\trentm\\Downloads' >>> user_pictures_dir() 'C:\\Users\\trentm\\Pictures' >>> user_videos_dir() 'C:\\Users\\trentm\\Videos' >>> user_music_dir() 'C:\\Users\\trentm\\Music' >>> user_desktop_dir() 'C:\\Users\\trentm\\Desktop' >>> user_runtime_dir(appname, appauthor) 'C:\\Users\\trentm\\AppData\\Local\\Temp\\Acme\\SuperApp' On Linux: .. code-block:: pycon >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) '/home/trentm/.local/share/SuperApp' >>> site_data_dir(appname, appauthor) '/usr/local/share/SuperApp' >>> site_data_dir(appname, appauthor, multipath=True) '/usr/local/share/SuperApp:/usr/share/SuperApp' >>> user_cache_dir(appname, appauthor) '/home/trentm/.cache/SuperApp' >>> user_log_dir(appname, appauthor) '/home/trentm/.local/state/SuperApp/log' >>> user_config_dir(appname) '/home/trentm/.config/SuperApp' >>> user_documents_dir() '/home/trentm/Documents' >>> user_downloads_dir() '/home/trentm/Downloads' >>> user_pictures_dir() '/home/trentm/Pictures' >>> user_videos_dir() '/home/trentm/Videos' >>> user_music_dir() '/home/trentm/Music' >>> user_desktop_dir() '/home/trentm/Desktop' >>> user_runtime_dir(appname, appauthor) '/run/user/{os.getuid()}/SuperApp' >>> site_config_dir(appname) '/etc/xdg/SuperApp' >>> os.environ["XDG_CONFIG_DIRS"] = "/etc:/usr/local/etc" >>> site_config_dir(appname, multipath=True) '/etc/SuperApp:/usr/local/etc/SuperApp' On Android:: >>> from platformdirs import * >>> appname = "SuperApp" >>> appauthor = "Acme" >>> user_data_dir(appname, appauthor) '/data/data/com.myApp/files/SuperApp' >>> user_cache_dir(appname, appauthor) '/data/data/com.myApp/cache/SuperApp' >>> user_log_dir(appname, appauthor) '/data/data/com.myApp/cache/SuperApp/log' >>> user_config_dir(appname) '/data/data/com.myApp/shared_prefs/SuperApp' >>> 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_runtime_dir(appname, appauthor) '/data/data/com.myApp/cache/SuperApp/tmp' Note: Some android apps like Termux and Pydroid are used as shells. These apps are used by the end user to emulate Linux environment. Presence of ``SHELL`` environment variable is used by Platformdirs to differentiate between general android apps and android apps used as shells. Shell android apps also support ``XDG_*`` environment variables. ``PlatformDirs`` for convenience ================================ .. code-block:: pycon >>> from platformdirs import PlatformDirs >>> dirs = PlatformDirs("SuperApp", "Acme") >>> dirs.user_data_dir '/Users/trentm/Library/Application Support/SuperApp' >>> dirs.site_data_dir '/Library/Application Support/SuperApp' >>> dirs.user_cache_dir '/Users/trentm/Library/Caches/SuperApp' >>> dirs.user_log_dir '/Users/trentm/Library/Logs/SuperApp' >>> dirs.user_documents_dir '/Users/trentm/Documents' >>> dirs.user_downloads_dir '/Users/trentm/Downloads' >>> dirs.user_pictures_dir '/Users/trentm/Pictures' >>> dirs.user_videos_dir '/Users/trentm/Movies' >>> dirs.user_music_dir '/Users/trentm/Music' >>> dirs.user_desktop_dir '/Users/trentm/Desktop' >>> dirs.user_runtime_dir '/Users/trentm/Library/Caches/TemporaryItems/SuperApp' Per-version isolation ===================== If you have multiple versions of your app in use that you want to be able to run side-by-side, then you may want version-isolation for these dirs:: >>> from platformdirs import PlatformDirs >>> dirs = PlatformDirs("SuperApp", "Acme", version="1.0") >>> dirs.user_data_dir '/Users/trentm/Library/Application Support/SuperApp/1.0' >>> dirs.site_data_dir '/Library/Application Support/SuperApp/1.0' >>> dirs.user_cache_dir '/Users/trentm/Library/Caches/SuperApp/1.0' >>> dirs.user_log_dir '/Users/trentm/Library/Logs/SuperApp/1.0' >>> dirs.user_documents_dir '/Users/trentm/Documents' >>> dirs.user_downloads_dir '/Users/trentm/Downloads' >>> dirs.user_pictures_dir '/Users/trentm/Pictures' >>> dirs.user_videos_dir '/Users/trentm/Movies' >>> dirs.user_music_dir '/Users/trentm/Music' >>> dirs.user_desktop_dir '/Users/trentm/Desktop' >>> dirs.user_runtime_dir '/Users/trentm/Library/Caches/TemporaryItems/SuperApp/1.0' Be wary of using this for configuration files though; you'll need to handle migrating configuration files manually. Why this Fork? ============== This repository is a friendly fork of the wonderful work started by `ActiveState `_ who created ``appdirs``, this package's ancestor. Maintaining an open source project is no easy task, particularly from within an organization, and the Python community is indebted to ``appdirs`` (and to Trent Mick and Jeff Rouse in particular) for creating an incredibly useful simple module, as evidenced by the wide number of users it has attracted over the years. Nonetheless, given the number of long-standing open issues and pull requests, and no clear path towards `ensuring that maintenance of the package would continue or grow `_, this fork was created. Contributions are most welcome.