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

130 statements  

1"""XDG environment variable mixin for Unix and macOS.""" 

2 

3from __future__ import annotations 

4 

5import os 

6import posixpath 

7import sys 

8from contextlib import suppress 

9from pathlib import Path 

10from typing import Final 

11 

12from .api import PlatformDirsABC 

13 

14 

15class XDGMixin(PlatformDirsABC): 

16 """Mixin that checks XDG environment variables, falling back to platform-specific defaults via ``super()``.""" 

17 

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 

24 

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 

30 

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) 

35 

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 

42 

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 

48 

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) 

53 

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 

60 

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 

67 

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 

72 

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 

81 

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 

88 

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 

93 

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 

98 

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 

103 

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 

108 

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 

113 

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 

118 

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 

123 

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 

128 

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 

133 

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 

138 

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 

146 

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 

153 

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 

159 

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] 

165 

166 

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 

170 

171 

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)] 

174 

175 

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. 

178 

179 :raises RuntimeError: if the home directory cannot be resolved to an absolute path. 

180 

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 

187 

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 

195 

196 

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() 

200 

201 

202__all__ = [ 

203 "XDGMixin", 

204 "_expand_user", 

205 "_xdg_dir", 

206]