Coverage for adhoc-cicd-odoo-odoo / odoo / tools / appdirs.py: 15%

194 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-09 18:05 +0000

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3# Copyright (c) 2005-2010 ActiveState Software Inc. 

4# Copyright (c) 2013 Eddy Petrișor 

5 

6"""Utilities for determining application-specific dirs. 

7 

8See <http://github.com/ActiveState/appdirs> for details and usage. 

9""" 

10from __future__ import print_function 

11# Dev Notes: 

12# - MSDN on where to store app data files: 

13# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 

14# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html 

15# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html 

16 

17__version_info__ = (1, 3, 0) 

18__version__ = '.'.join(str(v) for v in __version_info__) 

19 

20 

21import sys 

22import os 

23 

24 

25def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): 

26 r"""Return full path to the user-specific data dir for this application. 

27 

28 "appname" is the name of application. 

29 If None, just the system directory is returned. 

30 "appauthor" (only required and used on Windows) is the name of the 

31 appauthor or distributing body for this application. Typically 

32 it is the owning company name. This falls back to appname. 

33 "version" is an optional version path element to append to the 

34 path. You might want to use this if you want multiple versions 

35 of your app to be able to run independently. If used, this 

36 would typically be "<major>.<minor>". 

37 Only applied when appname is present. 

38 "roaming" (boolean, default False) can be set True to use the Windows 

39 roaming appdata directory. That means that for users on a Windows 

40 network setup for roaming profiles, this user data will be 

41 sync'd on login. See 

42 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> 

43 for a discussion of issues. 

44 

45 Typical user data directories are: 

46 Mac OS X: ~/Library/Application Support/<AppName> 

47 Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined 

48 Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName> 

49 Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName> 

50 Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName> 

51 Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName> 

52 

53 For Unix, we follow the XDG spec and support $XDG_DATA_HOME. 

54 That means, by default "~/.local/share/<AppName>". 

55 """ 

56 if sys.platform == "win32": 56 ↛ 57line 56 didn't jump to line 57 because the condition on line 56 was never true

57 if appauthor is None: 

58 appauthor = appname 

59 const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" 

60 path = os.path.normpath(_get_win_folder(const)) 

61 if appname: 

62 path = os.path.join(path, appauthor, appname) 

63 elif sys.platform == 'darwin': 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

64 path = os.path.expanduser('~/Library/Application Support/') 

65 if appname: 

66 path = os.path.join(path, appname) 

67 else: 

68 path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) 

69 if appname: 69 ↛ 71line 69 didn't jump to line 71 because the condition on line 69 was always true

70 path = os.path.join(path, appname) 

71 if appname and version: 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true

72 path = os.path.join(path, version) 

73 return path 

74 

75 

76def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): 

77 r"""Return full path to the user-shared data dir for this application. 

78 

79 "appname" is the name of application. 

80 If None, just the system directory is returned. 

81 "appauthor" (only required and used on Windows) is the name of the 

82 appauthor or distributing body for this application. Typically 

83 it is the owning company name. This falls back to appname. 

84 "version" is an optional version path element to append to the 

85 path. You might want to use this if you want multiple versions 

86 of your app to be able to run independently. If used, this 

87 would typically be "<major>.<minor>". 

88 Only applied when appname is present. 

89 "multipath" is an optional parameter only applicable to \*nix 

90 which indicates that the entire list of data dirs should be 

91 returned. By default, the first item from XDG_DATA_DIRS is 

92 returned, or :samp:`/usr/local/share/{AppName}`, 

93 if ``XDG_DATA_DIRS`` is not set 

94 

95 Typical user data directories are: 

96 

97 Mac OS X 

98 :samp:`/Library/Application Support/{AppName}` 

99 Unix 

100 :samp:`/usr/local/share/{AppName}` or :samp:`/usr/share/{AppName}` 

101 Win XP 

102 :samp:`C:\Documents and Settings\All Users\Application Data\{AppAuthor}\{AppName}` 

103 Vista 

104 Fail! "C:\ProgramData" is a hidden *system* directory on Vista. 

105 Win 7 

106 :samp:`C:\ProgramData\{AppAuthor}\{AppName}` (hidden, but writeable on Win 7) 

107 

108 For Unix, this is using the ``$XDG_DATA_DIRS[0]`` default. 

109 

110 WARNING: Do not use this on Windows. See the Vista-Fail note above for why. 

111 """ 

112 if sys.platform == "win32": 

113 if appauthor is None: 

114 appauthor = appname 

115 path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) 

116 if appname: 

117 path = os.path.join(path, appauthor, appname) 

118 elif sys.platform == 'darwin': 

119 path = os.path.expanduser('/Library/Application Support') 

120 if appname: 

121 path = os.path.join(path, appname) 

122 else: 

123 # XDG default for $XDG_DATA_DIRS 

124 # only first, if multipath is False 

125 path = os.getenv('XDG_DATA_DIRS', 

126 os.pathsep.join(['/usr/local/share', '/usr/share'])) 

127 pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ] 

128 if appname: 

129 if version: 

130 appname = os.path.join(appname, version) 

131 pathlist = [ os.sep.join([x, appname]) for x in pathlist ] 

132 

133 if multipath: 

134 path = os.pathsep.join(pathlist) 

135 else: 

136 path = pathlist[0] 

137 return path 

138 

139 if appname and version: 

140 path = os.path.join(path, version) 

141 return path 

142 

143 

144def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): 

145 """Return full path to the user-specific config dir for this application. 

146 

147 "appname" is the name of application. 

148 If None, just the system directory is returned. 

149 "appauthor" (only required and used on Windows) is the name of the 

150 appauthor or distributing body for this application. Typically 

151 it is the owning company name. This falls back to appname. 

152 "version" is an optional version path element to append to the 

153 path. You might want to use this if you want multiple versions 

154 of your app to be able to run independently. If used, this 

155 would typically be "<major>.<minor>". 

156 Only applied when appname is present. 

157 "roaming" (boolean, default False) can be set True to use the Windows 

158 roaming appdata directory. That means that for users on a Windows 

159 network setup for roaming profiles, this user data will be 

160 sync'd on login. See `managing roaming user data 

161 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_ 

162 for a discussion of issues. 

163 

164 Typical user data directories are: 

165 

166 Mac OS X 

167 same as user_data_dir 

168 Unix 

169 :samp:`~/.config/{AppName}` or in $XDG_CONFIG_HOME, if defined 

170 Win * 

171 same as user_data_dir 

172 

173 For Unix, we follow the XDG spec and support ``$XDG_DATA_HOME``. 

174 That means, by default :samp:`~/.local/share/{AppName}`. 

175 """ 

176 if sys.platform in [ "win32", "darwin" ]: 

177 path = user_data_dir(appname, appauthor, None, roaming) 

178 else: 

179 path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) 

180 if appname: 

181 path = os.path.join(path, appname) 

182 if appname and version: 

183 path = os.path.join(path, version) 

184 return path 

185 

186 

187def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): 

188 r"""Return full path to the user-shared data dir for this application. 

189 

190 "appname" is the name of application. 

191 If None, just the system directory is returned. 

192 "appauthor" (only required and used on Windows) is the name of the 

193 appauthor or distributing body for this application. Typically 

194 it is the owning company name. This falls back to appname. 

195 "version" is an optional version path element to append to the 

196 path. You might want to use this if you want multiple versions 

197 of your app to be able to run independently. If used, this 

198 would typically be "<major>.<minor>". 

199 Only applied when appname is present. 

200 "multipath" is an optional parameter only applicable to \*nix 

201 which indicates that the entire list of config dirs should be 

202 returned. By default, the first item from ``XDG_CONFIG_DIRS`` is 

203 returned, or :samp:`/etc/xdg/{AppName}`, if ``XDG_CONFIG_DIRS`` is not set 

204 

205 Typical user data directories are: 

206 

207 Mac OS X 

208 same as site_data_dir 

209 Unix 

210 ``/etc/xdg/<AppName>`` or ``$XDG_CONFIG_DIRS[i]/<AppName>`` for each 

211 value in ``$XDG_CONFIG_DIRS`` 

212 Win * 

213 same as site_data_dir 

214 Vista 

215 Fail! "C:\ProgramData" is a hidden *system* directory on Vista. 

216 

217 For Unix, this is using the ``$XDG_CONFIG_DIRS[0]`` default, if ``multipath=False`` 

218 

219 WARNING: Do not use this on Windows. See the Vista-Fail note above for why. 

220 """ 

221 if sys.platform in [ "win32", "darwin" ]: 

222 path = site_data_dir(appname, appauthor) 

223 if appname and version: 

224 path = os.path.join(path, version) 

225 else: 

226 # XDG default for $XDG_CONFIG_DIRS 

227 # only first, if multipath is False 

228 path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') 

229 pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ] 

230 if appname: 

231 if version: 

232 appname = os.path.join(appname, version) 

233 pathlist = [ os.sep.join([x, appname]) for x in pathlist ] 

234 

235 if multipath: 

236 path = os.pathsep.join(pathlist) 

237 else: 

238 path = pathlist[0] 

239 return path 

240 

241def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): 

242 r"""Return full path to the user-specific cache dir for this application. 

243 

244 "appname" is the name of application. 

245 If None, just the system directory is returned. 

246 "appauthor" (only required and used on Windows) is the name of the 

247 appauthor or distributing body for this application. Typically 

248 it is the owning company name. This falls back to appname. 

249 "version" is an optional version path element to append to the 

250 path. You might want to use this if you want multiple versions 

251 of your app to be able to run independently. If used, this 

252 would typically be "<major>.<minor>". 

253 Only applied when appname is present. 

254 "opinion" (boolean) can be False to disable the appending of 

255 "Cache" to the base app data dir for Windows. See 

256 discussion below. 

257 

258 Typical user cache directories are: 

259 

260 Mac OS X 

261 ~/Library/Caches/<AppName> 

262 Unix 

263 ~/.cache/<AppName> (XDG default) 

264 Win XP 

265 C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache 

266 Vista 

267 C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache 

268 

269 On Windows the only suggestion in the MSDN docs is that local settings go in 

270 the ``CSIDL_LOCAL_APPDATA`` directory. This is identical to the non-roaming 

271 app data dir (the default returned by ``user_data_dir`` above). Apps typically 

272 put cache data somewhere *under* the given dir here. Some examples: 

273 

274 - ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache 

275 - ...\Acme\SuperApp\Cache\1.0 

276 

277 OPINION: This function appends "Cache" to the ``CSIDL_LOCAL_APPDATA`` value. 

278 This can be disabled with the ``opinion=False`` option. 

279 """ 

280 if sys.platform == "win32": 

281 if appauthor is None: 

282 appauthor = appname 

283 path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) 

284 if appname: 

285 path = os.path.join(path, appauthor, appname) 

286 if opinion: 

287 path = os.path.join(path, "Cache") 

288 elif sys.platform == 'darwin': 

289 path = os.path.expanduser('~/Library/Caches') 

290 if appname: 

291 path = os.path.join(path, appname) 

292 else: 

293 path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) 

294 if appname: 

295 path = os.path.join(path, appname) 

296 if appname and version: 

297 path = os.path.join(path, version) 

298 return path 

299 

300def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): 

301 r"""Return full path to the user-specific log dir for this application. 

302 

303 "appname" is the name of application. 

304 If None, just the system directory is returned. 

305 "appauthor" (only required and used on Windows) is the name of the 

306 appauthor or distributing body for this application. Typically 

307 it is the owning company name. This falls back to appname. 

308 "version" is an optional version path element to append to the 

309 path. You might want to use this if you want multiple versions 

310 of your app to be able to run independently. If used, this 

311 would typically be "<major>.<minor>". 

312 Only applied when appname is present. 

313 "opinion" (boolean) can be False to disable the appending of 

314 "Logs" to the base app data dir for Windows, and "log" to the 

315 base cache dir for Unix. See discussion below. 

316 

317 Typical user cache directories are: 

318 Mac OS X: ~/Library/Logs/<AppName> 

319 Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined 

320 Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs 

321 Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs 

322 

323 On Windows the only suggestion in the MSDN docs is that local settings 

324 go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in 

325 examples of what some windows apps use for a logs dir.) 

326 

327 OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` 

328 value for Windows and appends "log" to the user cache dir for Unix. 

329 This can be disabled with the `opinion=False` option. 

330 """ 

331 if sys.platform == "darwin": 

332 path = os.path.join( 

333 os.path.expanduser('~/Library/Logs'), 

334 appname) 

335 elif sys.platform == "win32": 

336 path = user_data_dir(appname, appauthor, version); version=False 

337 if opinion: 

338 path = os.path.join(path, "Logs") 

339 else: 

340 path = user_cache_dir(appname, appauthor, version); version=False 

341 if opinion: 

342 path = os.path.join(path, "log") 

343 if appname and version: 

344 path = os.path.join(path, version) 

345 return path 

346 

347 

348class AppDirs(object): 

349 """Convenience wrapper for getting application dirs.""" 

350 def __init__(self, appname, appauthor=None, version=None, 

351 roaming=False, multipath=False): 

352 self.appname = appname 

353 self.appauthor = appauthor 

354 self.version = version 

355 self.roaming = roaming 

356 self.multipath = multipath 

357 @property 

358 def user_data_dir(self): 

359 return user_data_dir(self.appname, self.appauthor, 

360 version=self.version, roaming=self.roaming) 

361 @property 

362 def site_data_dir(self): 

363 return site_data_dir(self.appname, self.appauthor, 

364 version=self.version, multipath=self.multipath) 

365 @property 

366 def user_config_dir(self): 

367 return user_config_dir(self.appname, self.appauthor, 

368 version=self.version, roaming=self.roaming) 

369 @property 

370 def site_config_dir(self): 

371 return site_data_dir(self.appname, self.appauthor, 

372 version=self.version, multipath=self.multipath) 

373 @property 

374 def user_cache_dir(self): 

375 return user_cache_dir(self.appname, self.appauthor, 

376 version=self.version) 

377 @property 

378 def user_log_dir(self): 

379 return user_log_dir(self.appname, self.appauthor, 

380 version=self.version) 

381 

382 

383 

384 

385#---- internal support stuff 

386 

387def _get_win_folder_from_registry(csidl_name): 

388 """This is a fallback technique at best. I'm not sure if using the 

389 registry for this guarantees us the correct answer for all CSIDL_* 

390 names. 

391 """ 

392 import winreg as _winreg 

393 

394 shell_folder_name = { 

395 "CSIDL_APPDATA": "AppData", 

396 "CSIDL_COMMON_APPDATA": "Common AppData", 

397 "CSIDL_LOCAL_APPDATA": "Local AppData", 

398 }[csidl_name] 

399 

400 key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 

401 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") 

402 dir, type = _winreg.QueryValueEx(key, shell_folder_name) 

403 return dir 

404 

405def _get_win_folder_with_pywin32(csidl_name): 

406 from win32com.shell import shellcon, shell 

407 dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) 

408 # Try to make this a unicode path because SHGetFolderPath does 

409 # not return unicode strings when there is unicode data in the 

410 # path. 

411 try: 

412 dir = str(dir) 

413 

414 # Downgrade to short path name if have highbit chars. See 

415 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 

416 has_high_char = False 

417 for c in dir: 

418 if ord(c) > 255: 

419 has_high_char = True 

420 break 

421 if has_high_char: 

422 try: 

423 import win32api 

424 dir = win32api.GetShortPathName(dir) 

425 except ImportError: 

426 pass 

427 except UnicodeError: 

428 pass 

429 return dir 

430 

431def _get_win_folder_with_ctypes(csidl_name): 

432 import ctypes 

433 

434 csidl_const = { 

435 "CSIDL_APPDATA": 26, 

436 "CSIDL_COMMON_APPDATA": 35, 

437 "CSIDL_LOCAL_APPDATA": 28, 

438 }[csidl_name] 

439 

440 buf = ctypes.create_unicode_buffer(1024) 

441 ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) 

442 

443 # Downgrade to short path name if have highbit chars. See 

444 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 

445 has_high_char = False 

446 for c in buf: 

447 if ord(c) > 255: 

448 has_high_char = True 

449 break 

450 if has_high_char: 

451 buf2 = ctypes.create_unicode_buffer(1024) 

452 if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): 

453 buf = buf2 

454 

455 return buf.value 

456 

457if sys.platform == "win32": 457 ↛ 458line 457 didn't jump to line 458 because the condition on line 457 was never true

458 try: 

459 import win32com.shell 

460 _get_win_folder = _get_win_folder_with_pywin32 

461 except ImportError: 

462 try: 

463 import ctypes 

464 _get_win_folder = _get_win_folder_with_ctypes 

465 except ImportError: 

466 _get_win_folder = _get_win_folder_from_registry 

467 

468 

469 

470#---- self test code 

471 

472if __name__ == "__main__": 472 ↛ 473line 472 didn't jump to line 473 because the condition on line 472 was never true

473 appname = "MyApp" 

474 appauthor = "MyCompany" 

475 

476 props = ("user_data_dir", "site_data_dir", 

477 "user_config_dir", "site_config_dir", 

478 "user_cache_dir", "user_log_dir") 

479 

480 print("-- app dirs (with optional 'version')") 

481 dirs = AppDirs(appname, appauthor, version="1.0") 

482 for prop in props: 

483 print("%s: %s" % (prop, getattr(dirs, prop))) 

484 

485 print("\n-- app dirs (without optional 'version')") 

486 dirs = AppDirs(appname, appauthor) 

487 for prop in props: 

488 print("%s: %s" % (prop, getattr(dirs, prop))) 

489 

490 print("\n-- app dirs (without optional 'appauthor')") 

491 dirs = AppDirs(appname) 

492 for prop in props: 

493 print("%s: %s" % (prop, getattr(dirs, prop)))