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

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

144 statements  

1"""Unix.""" 

2 

3from __future__ import annotations 

4 

5import os 

6import sys 

7from configparser import ConfigParser 

8from pathlib import Path 

9from typing import TYPE_CHECKING, NoReturn 

10 

11from .api import PlatformDirsABC 

12 

13if TYPE_CHECKING: 

14 from collections.abc import Iterator 

15 

16if sys.platform == "win32": 

17 

18 def getuid() -> NoReturn: 

19 msg = "should only be used on Unix" 

20 raise RuntimeError(msg) 

21 

22else: 

23 from os import getuid 

24 

25 

26class Unix(PlatformDirsABC): # noqa: PLR0904 

27 """ 

28 On Unix/Linux, we follow the `XDG Basedir Spec <https://specifications.freedesktop.org/basedir-spec/basedir-spec- 

29 latest.html>`_. 

30 

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

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

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

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

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

36 

37 """ 

38 

39 @property 

40 def user_data_dir(self) -> str: 

41 """ 

42 :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or 

43 ``$XDG_DATA_HOME/$appname/$version`` 

44 """ 

45 path = os.environ.get("XDG_DATA_HOME", "") 

46 if not path.strip(): 

47 path = os.path.expanduser("~/.local/share") # noqa: PTH111 

48 return self._append_app_name_and_version(path) 

49 

50 @property 

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

52 path = os.environ.get("XDG_DATA_DIRS", "") 

53 if not path.strip(): 

54 path = f"/usr/local/share{os.pathsep}/usr/share" 

55 return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)] 

56 

57 @property 

58 def site_data_dir(self) -> str: 

59 """ 

60 :return: data directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` is 

61 enabled and ``XDG_DATA_DIRS`` is set and a multi path the response is also a multi path separated by the 

62 OS path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` 

63 """ 

64 # XDG default for $XDG_DATA_DIRS; only first, if multipath is False 

65 dirs = self._site_data_dirs 

66 if not self.multipath: 

67 return dirs[0] 

68 return os.pathsep.join(dirs) 

69 

70 @property 

71 def user_config_dir(self) -> str: 

72 """ 

73 :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or 

74 ``$XDG_CONFIG_HOME/$appname/$version`` 

75 """ 

76 path = os.environ.get("XDG_CONFIG_HOME", "") 

77 if not path.strip(): 

78 path = os.path.expanduser("~/.config") # noqa: PTH111 

79 return self._append_app_name_and_version(path) 

80 

81 @property 

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

83 path = os.environ.get("XDG_CONFIG_DIRS", "") 

84 if not path.strip(): 

85 path = "/etc/xdg" 

86 return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)] 

87 

88 @property 

89 def site_config_dir(self) -> str: 

90 """ 

91 :return: config directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` 

92 is enabled and ``XDG_CONFIG_DIRS`` is set and a multi path the response is also a multi path separated by 

93 the OS path separator), e.g. ``/etc/xdg/$appname/$version`` 

94 """ 

95 # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False 

96 dirs = self._site_config_dirs 

97 if not self.multipath: 

98 return dirs[0] 

99 return os.pathsep.join(dirs) 

100 

101 @property 

102 def user_cache_dir(self) -> str: 

103 """ 

104 :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or 

105 ``~/$XDG_CACHE_HOME/$appname/$version`` 

106 """ 

107 path = os.environ.get("XDG_CACHE_HOME", "") 

108 if not path.strip(): 

109 path = os.path.expanduser("~/.cache") # noqa: PTH111 

110 return self._append_app_name_and_version(path) 

111 

112 @property 

113 def site_cache_dir(self) -> str: 

114 """:return: cache directory shared by users, e.g. ``/var/cache/$appname/$version``""" 

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

116 

117 @property 

118 def user_state_dir(self) -> str: 

119 """ 

120 :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or 

121 ``$XDG_STATE_HOME/$appname/$version`` 

122 """ 

123 path = os.environ.get("XDG_STATE_HOME", "") 

124 if not path.strip(): 

125 path = os.path.expanduser("~/.local/state") # noqa: PTH111 

126 return self._append_app_name_and_version(path) 

127 

128 @property 

129 def user_log_dir(self) -> str: 

130 """:return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it""" 

131 path = self.user_state_dir 

132 if self.opinion: 

133 path = os.path.join(path, "log") # noqa: PTH118 

134 self._optionally_create_directory(path) 

135 return path 

136 

137 @property 

138 def user_documents_dir(self) -> str: 

139 """:return: documents directory tied to the user, e.g. ``~/Documents``""" 

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

141 

142 @property 

143 def user_downloads_dir(self) -> str: 

144 """:return: downloads directory tied to the user, e.g. ``~/Downloads``""" 

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

146 

147 @property 

148 def user_pictures_dir(self) -> str: 

149 """:return: pictures directory tied to the user, e.g. ``~/Pictures``""" 

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

151 

152 @property 

153 def user_videos_dir(self) -> str: 

154 """:return: videos directory tied to the user, e.g. ``~/Videos``""" 

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

156 

157 @property 

158 def user_music_dir(self) -> str: 

159 """:return: music directory tied to the user, e.g. ``~/Music``""" 

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

161 

162 @property 

163 def user_desktop_dir(self) -> str: 

164 """:return: desktop directory tied to the user, e.g. ``~/Desktop``""" 

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

166 

167 @property 

168 def user_runtime_dir(self) -> str: 

169 """ 

170 :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or 

171 ``$XDG_RUNTIME_DIR/$appname/$version``. 

172 

173 For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if 

174 exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR`` 

175 is not set. 

176 """ 

177 path = os.environ.get("XDG_RUNTIME_DIR", "") 

178 if not path.strip(): 

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

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

181 if not Path(path).exists(): 

182 path = f"/tmp/runtime-{getuid()}" # noqa: S108 

183 else: 

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

185 return self._append_app_name_and_version(path) 

186 

187 @property 

188 def site_runtime_dir(self) -> str: 

189 """ 

190 :return: runtime directory shared by users, e.g. ``/run/$appname/$version`` or \ 

191 ``$XDG_RUNTIME_DIR/$appname/$version``. 

192 

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

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

195 

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

197 instead. 

198 

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

200 """ 

201 path = os.environ.get("XDG_RUNTIME_DIR", "") 

202 if not path.strip(): 

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

204 path = "/var/run" 

205 else: 

206 path = "/run" 

207 return self._append_app_name_and_version(path) 

208 

209 @property 

210 def site_data_path(self) -> Path: 

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

212 return self._first_item_as_path_if_multipath(self.site_data_dir) 

213 

214 @property 

215 def site_config_path(self) -> Path: 

216 """:return: config path shared by the users, returns the first item, even if ``multipath`` is set to ``True``""" 

217 return self._first_item_as_path_if_multipath(self.site_config_dir) 

218 

219 @property 

220 def site_cache_path(self) -> Path: 

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

222 return self._first_item_as_path_if_multipath(self.site_cache_dir) 

223 

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

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

226 yield self.user_config_dir 

227 yield from self._site_config_dirs 

228 

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

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

231 yield self.user_data_dir 

232 yield from self._site_data_dirs 

233 

234 

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

236 media_dir = _get_user_dirs_folder(env_var) 

237 if media_dir is None: 

238 media_dir = os.environ.get(env_var, "").strip() 

239 if not media_dir: 

240 media_dir = os.path.expanduser(fallback_tilde_path) # noqa: PTH111 

241 

242 return media_dir 

243 

244 

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

246 """ 

247 Return directory from user-dirs.dirs config file. 

248 

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

250 

251 """ 

252 user_dirs_config_path = Path(Unix().user_config_dir) / "user-dirs.dirs" 

253 if user_dirs_config_path.exists(): 

254 parser = ConfigParser() 

255 

256 with user_dirs_config_path.open() as stream: 

257 # Add fake section header, so ConfigParser doesn't complain 

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

259 

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

261 return None 

262 

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

264 # Handle relative home paths 

265 return path.replace("$HOME", os.path.expanduser("~")) # noqa: PTH111 

266 

267 return None 

268 

269 

270__all__ = [ 

271 "Unix", 

272]