Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/platformdirs/unix.py: 2%

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

196 statements  

1"""Unix.""" 

2 

3from __future__ import annotations 

4 

5import os 

6import sys 

7from configparser import ConfigParser 

8from functools import cached_property 

9from pathlib import Path 

10from tempfile import gettempdir 

11from typing import TYPE_CHECKING, NoReturn 

12 

13from ._xdg import XDGMixin 

14from .api import PlatformDirsABC 

15 

16if TYPE_CHECKING: 

17 from collections.abc import Iterator 

18 

19if sys.platform == "win32": 

20 

21 def getuid() -> NoReturn: 

22 msg = "should only be used on Unix" 

23 raise RuntimeError(msg) 

24 

25else: 

26 from os import getuid 

27 

28 

29class _UnixDefaults(PlatformDirsABC): # ruff:ignore[too-many-public-methods] 

30 """Default directories for Unix/Linux without XDG environment variable overrides. 

31 

32 The XDG env var handling is in :class:`~platformdirs._xdg.XDGMixin`. 

33 

34 """ 

35 

36 @cached_property 

37 def _use_site(self) -> bool: 

38 return self.use_site_for_root and getuid() == 0 

39 

40 @property 

41 def user_data_dir(self) -> str: 

42 """Data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or ``$XDG_DATA_HOME/$appname/$version``.""" 

43 return self._append_app_name_and_version(os.path.expanduser("~/.local/share")) # ruff:ignore[os-path-expanduser] 

44 

45 @property 

46 def _site_data_dirs(self) -> list[str]: 

47 return [self._append_app_name_and_version("/usr/local/share"), self._append_app_name_and_version("/usr/share")] 

48 

49 @property 

50 def user_config_dir(self) -> str: 

51 """Config directory tied to the user, e.g. ``~/.config/$appname/$version`` or ``$XDG_CONFIG_HOME/$appname/$version``.""" 

52 return self._append_app_name_and_version(os.path.expanduser("~/.config")) # ruff:ignore[os-path-expanduser] 

53 

54 @property 

55 def _site_config_dirs(self) -> list[str]: 

56 return [self._append_app_name_and_version("/etc/xdg")] 

57 

58 @property 

59 def user_cache_dir(self) -> str: 

60 """Cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or ``$XDG_CACHE_HOME/$appname/$version``.""" 

61 return self._append_app_name_and_version(os.path.expanduser("~/.cache")) # ruff:ignore[os-path-expanduser] 

62 

63 @property 

64 def site_cache_dir(self) -> str: 

65 """Cache directory shared by users, e.g. ``/var/cache/$appname/$version``.""" 

66 return self._append_app_name_and_version("/var/cache") 

67 

68 @property 

69 def user_state_dir(self) -> str: 

70 """State directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or ``$XDG_STATE_HOME/$appname/$version``.""" 

71 return self._append_app_name_and_version(os.path.expanduser("~/.local/state")) # ruff:ignore[os-path-expanduser] 

72 

73 @property 

74 def site_state_dir(self) -> str: 

75 """State directory shared by users, e.g. ``/var/lib/$appname/$version``.""" 

76 return self._append_app_name_and_version("/var/lib") 

77 

78 @property 

79 def user_log_dir(self) -> str: 

80 """Log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it.""" 

81 path = self.user_state_dir 

82 if self.opinion: 

83 path = os.path.join(path, "log") # ruff:ignore[os-path-join] 

84 self._optionally_create_directory(path) 

85 return path 

86 

87 @property 

88 def site_log_dir(self) -> str: 

89 """Log directory shared by users, e.g. ``/var/log/$appname/$version``. 

90 

91 Unlike `user_log_dir`, ``opinion`` has no effect since ``/var/log`` is inherently a log directory. 

92 

93 """ 

94 return self._append_app_name_and_version("/var/log") 

95 

96 @property 

97 def user_documents_dir(self) -> str: 

98 """Documents directory tied to the user, e.g. ``~/Documents``.""" 

99 return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents") 

100 

101 @property 

102 def user_downloads_dir(self) -> str: 

103 """Downloads directory tied to the user, e.g. ``~/Downloads``.""" 

104 return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads") 

105 

106 @property 

107 def user_pictures_dir(self) -> str: 

108 """Pictures directory tied to the user, e.g. ``~/Pictures``.""" 

109 return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures") 

110 

111 @property 

112 def user_videos_dir(self) -> str: 

113 """Videos directory tied to the user, e.g. ``~/Videos``.""" 

114 return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos") 

115 

116 @property 

117 def user_music_dir(self) -> str: 

118 """Music directory tied to the user, e.g. ``~/Music``.""" 

119 return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music") 

120 

121 @property 

122 def user_desktop_dir(self) -> str: 

123 """Desktop directory tied to the user, e.g. ``~/Desktop``.""" 

124 return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop") 

125 

126 @property 

127 def user_projects_dir(self) -> str: 

128 """Projects directory tied to the user, e.g. ``~/Projects``.""" 

129 return _get_user_media_dir("XDG_PROJECTS_DIR", "~/Projects") 

130 

131 @property 

132 def user_publicshare_dir(self) -> str: 

133 """Public share directory tied to the user, e.g. ``~/Public``.""" 

134 return _get_user_media_dir("XDG_PUBLICSHARE_DIR", "~/Public") 

135 

136 @property 

137 def user_templates_dir(self) -> str: 

138 """Templates directory tied to the user, e.g. ``~/Templates``.""" 

139 return _get_user_media_dir("XDG_TEMPLATES_DIR", "~/Templates") 

140 

141 @property 

142 def user_fonts_dir(self) -> str: 

143 """Fonts directory tied to the user, e.g. ``~/.local/share/fonts``.""" 

144 return f"{os.path.expanduser('~/.local/share')}/fonts" # ruff:ignore[os-path-expanduser] # API returns str, not Path 

145 

146 @property 

147 def user_preference_dir(self) -> str: 

148 """Preference directory tied to the user, same as ``user_config_dir``.""" 

149 return self.user_config_dir 

150 

151 @property 

152 def user_bin_dir(self) -> str: 

153 """Bin directory tied to the user, e.g. ``~/.local/bin``.""" 

154 return os.path.expanduser("~/.local/bin") # ruff:ignore[os-path-expanduser] 

155 

156 @property 

157 def site_bin_dir(self) -> str: 

158 """Bin directory shared by users, e.g. ``/usr/local/bin``.""" 

159 return "/usr/local/bin" 

160 

161 @property 

162 def user_applications_dir(self) -> str: 

163 """Applications directory tied to the user, e.g. ``~/.local/share/applications``.""" 

164 return os.path.join(os.path.expanduser("~/.local/share"), "applications") # ruff:ignore[os-path-expanduser, os-path-join] 

165 

166 @property 

167 def _site_applications_dirs(self) -> list[str]: 

168 return [os.path.join(p, "applications") for p in ["/usr/local/share", "/usr/share"]] # ruff:ignore[os-path-join] 

169 

170 @property 

171 def site_applications_dir(self) -> str: 

172 """Applications directory shared by users, e.g. ``/usr/share/applications``.""" 

173 dirs = self._site_applications_dirs 

174 return os.pathsep.join(dirs) if self.multipath else dirs[0] 

175 

176 @property 

177 def user_runtime_dir(self) -> str: 

178 """Runtime directory tied to the user, e.g. ``$XDG_RUNTIME_DIR/$appname/$version``. 

179 

180 If ``$XDG_RUNTIME_DIR`` is unset, tries the platform default (``/tmp/run/user/$(id -u)`` on OpenBSD, 

181 ``/var/run/user/$(id -u)`` on FreeBSD/NetBSD, ``/run/user/$(id -u)`` otherwise). If the default is not writable, 

182 falls back to a temporary directory. 

183 

184 """ 

185 if sys.platform.startswith("openbsd"): 

186 path = f"/tmp/run/user/{getuid()}" # ruff:ignore[hardcoded-temp-file] 

187 elif sys.platform.startswith(("freebsd", "netbsd")): 

188 path = f"/var/run/user/{getuid()}" 

189 else: 

190 path = f"/run/user/{getuid()}" 

191 if not os.access(path, os.W_OK): 

192 path = f"{gettempdir()}/runtime-{getuid()}" 

193 return self._append_app_name_and_version(path) 

194 

195 @property 

196 def site_runtime_dir(self) -> str: 

197 """Runtime directory shared by users, e.g. ``/run/$appname/$version`` or ``$XDG_RUNTIME_DIR/$appname/$version``. 

198 

199 Note that this behaves almost exactly like `user_runtime_dir` if ``$XDG_RUNTIME_DIR`` is set, but will fall back 

200 to paths associated to the root user instead of a regular logged-in user if it's not set. 

201 

202 If you wish to ensure that a logged-in root user path is returned e.g. ``/run/user/0``, use `user_runtime_dir` 

203 instead. 

204 

205 For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/$appname/$version`` if ``$XDG_RUNTIME_DIR`` is not set. 

206 

207 """ 

208 if sys.platform.startswith(("freebsd", "openbsd", "netbsd")): 

209 path = "/var/run" 

210 else: 

211 path = "/run" 

212 return self._append_app_name_and_version(path) 

213 

214 @property 

215 def site_data_path(self) -> Path: 

216 """Data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``.""" 

217 return self._first_item_as_path_if_multipath(self.site_data_dir) 

218 

219 @property 

220 def site_config_path(self) -> Path: 

221 """Config path shared by users, returns the first item, even if ``multipath`` is set to ``True``.""" 

222 return self._first_item_as_path_if_multipath(self.site_config_dir) 

223 

224 @property 

225 def site_cache_path(self) -> Path: 

226 """Cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``.""" 

227 return self._first_item_as_path_if_multipath(self.site_cache_dir) 

228 

229 def iter_config_dirs(self) -> Iterator[str]: 

230 """:yield: all user and site configuration directories.""" 

231 if not self._use_site: 

232 yield self.user_config_dir 

233 yield from self._site_config_dirs 

234 

235 def iter_data_dirs(self) -> Iterator[str]: 

236 """:yield: all user and site data directories.""" 

237 if not self._use_site: 

238 yield self.user_data_dir 

239 yield from self._site_data_dirs 

240 

241 def iter_cache_dirs(self) -> Iterator[str]: 

242 """:yield: all user and site cache directories.""" 

243 if not self._use_site: 

244 yield self.user_cache_dir 

245 yield self.site_cache_dir 

246 

247 def iter_state_dirs(self) -> Iterator[str]: 

248 """:yield: all user and site state directories.""" 

249 if not self._use_site: 

250 yield self.user_state_dir 

251 yield self.site_state_dir 

252 

253 def iter_log_dirs(self) -> Iterator[str]: 

254 """:yield: all user and site log directories.""" 

255 if not self._use_site: 

256 yield self.user_log_dir 

257 yield self.site_log_dir 

258 

259 def iter_runtime_dirs(self) -> Iterator[str]: 

260 """:yield: all user and site runtime directories.""" 

261 if not self._use_site: 

262 yield self.user_runtime_dir 

263 yield self.site_runtime_dir 

264 

265 

266class Unix(XDGMixin, _UnixDefaults): 

267 """On Unix/Linux, we follow the `XDG Basedir Spec <https://specifications.freedesktop.org/basedir/latest/>`_. 

268 

269 The spec allows overriding directories with environment variables. The examples shown are the default values, 

270 alongside the name of the environment variable that overrides them. Makes use of the `appname 

271 <platformdirs.api.PlatformDirsABC.appname>`, `version <platformdirs.api.PlatformDirsABC.version>`, `multipath 

272 <platformdirs.api.PlatformDirsABC.multipath>`, `opinion <platformdirs.api.PlatformDirsABC.opinion>`, `ensure_exists 

273 <platformdirs.api.PlatformDirsABC.ensure_exists>`. 

274 

275 """ 

276 

277 @property 

278 def user_data_dir(self) -> str: 

279 """Data directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

280 return self.site_data_dir if self._use_site else super().user_data_dir 

281 

282 @property 

283 def user_config_dir(self) -> str: 

284 """Config directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

285 return self.site_config_dir if self._use_site else super().user_config_dir 

286 

287 @property 

288 def user_cache_dir(self) -> str: 

289 """Cache directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

290 return self.site_cache_dir if self._use_site else super().user_cache_dir 

291 

292 @property 

293 def user_state_dir(self) -> str: 

294 """State directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

295 return self.site_state_dir if self._use_site else super().user_state_dir 

296 

297 @property 

298 def user_log_dir(self) -> str: 

299 """Log directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

300 return self.site_log_dir if self._use_site else super().user_log_dir 

301 

302 @property 

303 def user_applications_dir(self) -> str: 

304 """Applications directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

305 return self.site_applications_dir if self._use_site else super().user_applications_dir 

306 

307 @property 

308 def user_runtime_dir(self) -> str: 

309 """Runtime directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

310 return self.site_runtime_dir if self._use_site else super().user_runtime_dir 

311 

312 @property 

313 def user_bin_dir(self) -> str: 

314 """Bin directory tied to the user, or site equivalent when root with ``use_site_for_root``.""" 

315 return self.site_bin_dir if self._use_site else super().user_bin_dir 

316 

317 

318def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str: 

319 if media_dir := _get_user_dirs_folder(env_var): 

320 return media_dir 

321 return os.path.expanduser(fallback_tilde_path) # ruff:ignore[os-path-expanduser] 

322 

323 

324def _get_user_dirs_folder(key: str) -> str | None: 

325 """Return directory from user-dirs.dirs config file. 

326 

327 See https://freedesktop.org/wiki/Software/xdg-user-dirs/. 

328 

329 """ 

330 config_home = os.environ.get("XDG_CONFIG_HOME", "").strip() or os.path.expanduser("~/.config") # ruff:ignore[os-path-expanduser] 

331 user_dirs_config_path = Path(config_home) / "user-dirs.dirs" 

332 if user_dirs_config_path.exists(): 

333 parser = ConfigParser() 

334 

335 with user_dirs_config_path.open() as stream: 

336 parser.read_string(f"[top]\n{stream.read()}") 

337 

338 if key not in parser["top"]: 

339 return None 

340 

341 path = parser["top"][key].strip('"') 

342 return path.replace("$HOME", os.path.expanduser("~")) # ruff:ignore[os-path-expanduser] 

343 

344 return None 

345 

346 

347__all__ = [ 

348 "Unix", 

349]