Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/platformdirs/_xdg.py: 48%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""XDG environment variable mixin for Unix and macOS."""
3from __future__ import annotations
5import os
6import posixpath
7import sys
8from contextlib import suppress
9from pathlib import Path
10from typing import Final
12from .api import PlatformDirsABC
15class XDGMixin(PlatformDirsABC):
16 """Mixin that checks XDG environment variables, falling back to platform-specific defaults via ``super()``."""
18 @property
19 def user_data_dir(self) -> str:
20 """Data directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default."""
21 if path := _xdg_dir("XDG_DATA_HOME"):
22 return self._append_app_name_and_version(path, private=True)
23 return super().user_data_dir
25 @property
26 def _site_data_dirs(self) -> list[str]:
27 if xdg_dirs := _xdg_dir_list("XDG_DATA_DIRS"):
28 return [self._join_app_name_and_version(p) for p in xdg_dirs]
29 return super()._site_data_dirs
31 @property
32 def site_data_dir(self) -> str:
33 """Data directories shared by users, from ``$XDG_DATA_DIRS`` if set, else platform default."""
34 return self._select_site_dirs(self._site_data_dirs)
36 @property
37 def user_config_dir(self) -> str:
38 """Config directory tied to the user, from ``$XDG_CONFIG_HOME`` if set, else platform default."""
39 if path := _xdg_dir("XDG_CONFIG_HOME"):
40 return self._append_app_name_and_version(path, private=True)
41 return super().user_config_dir
43 @property
44 def _site_config_dirs(self) -> list[str]:
45 if xdg_dirs := _xdg_dir_list("XDG_CONFIG_DIRS"):
46 return [self._join_app_name_and_version(p) for p in xdg_dirs]
47 return super()._site_config_dirs
49 @property
50 def site_config_dir(self) -> str:
51 """Config directories shared by users, from ``$XDG_CONFIG_DIRS`` if set, else platform default."""
52 return self._select_site_dirs(self._site_config_dirs)
54 @property
55 def user_cache_dir(self) -> str:
56 """Cache directory tied to the user, from ``$XDG_CACHE_HOME`` if set, else platform default."""
57 if path := _xdg_dir("XDG_CACHE_HOME"):
58 return self._append_app_name_and_version(path, private=True)
59 return super().user_cache_dir
61 @property
62 def user_state_dir(self) -> str:
63 """State directory tied to the user, from ``$XDG_STATE_HOME`` if set, else platform default."""
64 if path := _xdg_dir("XDG_STATE_HOME"):
65 return self._append_app_name_and_version(path, private=True)
66 return super().user_state_dir
68 @property
69 def user_runtime_dir(self) -> str:
70 """Runtime directory tied to the user, from ``$XDG_RUNTIME_DIR`` if set and private, else platform default."""
71 from .unix import _runtime_dir_problem, _warn_once # ruff:ignore[import-outside-top-level] # unix imports _xdg
73 if path := _xdg_dir("XDG_RUNTIME_DIR"):
74 with suppress(OSError):
75 self._optionally_create_directory(path, private=True)
76 # Another user can pre-create the path in a shared parent, and XDG requires an owned runtime dir with mode 0700.
77 if not (problem := _runtime_dir_problem(path, missing_ok=not self.ensure_exists)):
78 return self._append_app_name_and_version(path, private=True)
79 _warn_once(f"XDG_RUNTIME_DIR {problem}, falling back to the platform default")
80 return super().user_runtime_dir
82 @property
83 def site_runtime_dir(self) -> str:
84 """Runtime directory shared by users, from ``$XDG_RUNTIME_DIR`` if set, else platform default."""
85 if path := _xdg_dir("XDG_RUNTIME_DIR"):
86 return self._append_app_name_and_version(path, private=False)
87 return super().site_runtime_dir
89 @property
90 def user_documents_dir(self) -> str:
91 """Documents directory tied to the user, from ``$XDG_DOCUMENTS_DIR`` if set, else platform default."""
92 return self._xdg_media_dir("XDG_DOCUMENTS_DIR") or super().user_documents_dir
94 @property
95 def user_downloads_dir(self) -> str:
96 """Downloads directory tied to the user, from ``$XDG_DOWNLOAD_DIR`` if set, else platform default."""
97 return self._xdg_media_dir("XDG_DOWNLOAD_DIR") or super().user_downloads_dir
99 @property
100 def user_pictures_dir(self) -> str:
101 """Pictures directory tied to the user, from ``$XDG_PICTURES_DIR`` if set, else platform default."""
102 return self._xdg_media_dir("XDG_PICTURES_DIR") or super().user_pictures_dir
104 @property
105 def user_videos_dir(self) -> str:
106 """Videos directory tied to the user, from ``$XDG_VIDEOS_DIR`` if set, else platform default."""
107 return self._xdg_media_dir("XDG_VIDEOS_DIR") or super().user_videos_dir
109 @property
110 def user_music_dir(self) -> str:
111 """Music directory tied to the user, from ``$XDG_MUSIC_DIR`` if set, else platform default."""
112 return self._xdg_media_dir("XDG_MUSIC_DIR") or super().user_music_dir
114 @property
115 def user_desktop_dir(self) -> str:
116 """Desktop directory tied to the user, from ``$XDG_DESKTOP_DIR`` if set, else platform default."""
117 return self._xdg_media_dir("XDG_DESKTOP_DIR") or super().user_desktop_dir
119 @property
120 def user_projects_dir(self) -> str:
121 """Projects directory tied to the user, from ``$XDG_PROJECTS_DIR`` if set, else platform default."""
122 return self._xdg_media_dir("XDG_PROJECTS_DIR") or super().user_projects_dir
124 @property
125 def user_publicshare_dir(self) -> str:
126 """Public share directory tied to the user, from ``$XDG_PUBLICSHARE_DIR`` if set, else platform default."""
127 return self._xdg_media_dir("XDG_PUBLICSHARE_DIR") or super().user_publicshare_dir
129 @property
130 def user_templates_dir(self) -> str:
131 """Templates directory tied to the user, from ``$XDG_TEMPLATES_DIR`` if set, else platform default."""
132 return self._xdg_media_dir("XDG_TEMPLATES_DIR") or super().user_templates_dir
134 def _xdg_media_dir(self, env_var: str) -> str | None:
135 if path := _xdg_dir(env_var):
136 self._optionally_create_media_directory(path)
137 return path
139 @property
140 def user_fonts_dir(self) -> str:
141 """Fonts directory tied to the user, from ``$XDG_DATA_HOME/fonts`` if set, else platform default."""
142 if path := _xdg_dir("XDG_DATA_HOME"):
143 # posixpath keeps the XDG separator even when this mixin runs on a non-POSIX host.
144 return posixpath.join(path, "fonts")
145 return super().user_fonts_dir
147 @property
148 def user_applications_dir(self) -> str:
149 """Applications directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default."""
150 if path := _xdg_dir("XDG_DATA_HOME"):
151 return os.path.join(os.path.expanduser(path), "applications") # ruff:ignore[os-path-expanduser, os-path-join]
152 return super().user_applications_dir
154 @property
155 def _site_applications_dirs(self) -> list[str]:
156 if xdg_dirs := _xdg_dir_list("XDG_DATA_DIRS"):
157 return [os.path.join(p, "applications") for p in xdg_dirs] # ruff:ignore[os-path-join]
158 return super()._site_applications_dirs
160 @property
161 def site_applications_dir(self) -> str:
162 """Applications directories shared by users, from ``$XDG_DATA_DIRS`` if set, else platform default."""
163 dirs = self._site_applications_dirs
164 return os.pathsep.join(dirs) if self.multipath else dirs[0]
167def _xdg_dir(env_var: str) -> str | None:
168 path: Final = os.environ.get(env_var, "")
169 return path if posixpath.isabs(path) else None
172def _xdg_dir_list(env_var: str) -> list[str]:
173 return [path for path in os.environ.get(env_var, "").split(os.pathsep) if posixpath.isabs(path)]
176def _expand_user(path: str) -> str:
177 """Expand the leading ``~`` of ``path`` like :func:`os.path.expanduser`, treating a relative ``HOME`` as unset on POSIX.
179 :raises RuntimeError: if the home directory cannot be resolved to an absolute path.
181 """
182 if sys.platform != "win32" and "HOME" in os.environ and not _is_absolute(os.environ["HOME"]):
183 # expanduser uses HOME verbatim, so an empty one roots the path at / and a relative one at the working directory
184 expanded = path
185 with suppress(ImportError, KeyError):
186 import pwd # ruff:ignore[import-outside-top-level] # missing on WASI
188 expanded = pwd.getpwuid(os.getuid()).pw_dir.rstrip("/") + path[1:] or "/"
189 else:
190 expanded = os.path.expanduser(path) # ruff:ignore[os-path-expanduser] # str(Path) rewrites separators on Windows
191 if not _is_absolute(expanded):
192 msg = f"could not determine the home directory for {path!r}, set an absolute HOME or XDG variable"
193 raise RuntimeError(msg)
194 return expanded
197def _is_absolute(path: str) -> bool:
198 # is_absolute accepts a Windows drive path, posixpath.isabs the POSIX home the Unix class gets on any host
199 return posixpath.isabs(path) or Path(path).is_absolute()
202__all__ = [
203 "XDGMixin",
204 "_expand_user",
205 "_xdg_dir",
206]