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:15 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:15 +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
6"""Utilities for determining application-specific dirs.
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
17__version_info__ = (1, 3, 0)
18__version__ = '.'.join(str(v) for v in __version_info__)
21import sys
22import os
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.
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.
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>
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
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.
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
95 Typical user data directories are:
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)
108 For Unix, this is using the ``$XDG_DATA_DIRS[0]`` default.
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 ]
133 if multipath:
134 path = os.pathsep.join(pathlist)
135 else:
136 path = pathlist[0]
137 return path
139 if appname and version:
140 path = os.path.join(path, version)
141 return path
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.
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.
164 Typical user data directories are:
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
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
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.
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
205 Typical user data directories are:
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.
217 For Unix, this is using the ``$XDG_CONFIG_DIRS[0]`` default, if ``multipath=False``
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 ]
235 if multipath:
236 path = os.pathsep.join(pathlist)
237 else:
238 path = pathlist[0]
239 return path
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.
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.
258 Typical user cache directories are:
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
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:
274 - ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
275 - ...\Acme\SuperApp\Cache\1.0
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
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.
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.
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
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.)
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
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)
385#---- internal support stuff
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
394 shell_folder_name = {
395 "CSIDL_APPDATA": "AppData",
396 "CSIDL_COMMON_APPDATA": "Common AppData",
397 "CSIDL_LOCAL_APPDATA": "Local AppData",
398 }[csidl_name]
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
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)
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
431def _get_win_folder_with_ctypes(csidl_name):
432 import ctypes
434 csidl_const = {
435 "CSIDL_APPDATA": 26,
436 "CSIDL_COMMON_APPDATA": 35,
437 "CSIDL_LOCAL_APPDATA": 28,
438 }[csidl_name]
440 buf = ctypes.create_unicode_buffer(1024)
441 ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
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
455 return buf.value
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
470#---- self test code
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"
476 props = ("user_data_dir", "site_data_dir",
477 "user_config_dir", "site_config_dir",
478 "user_cache_dir", "user_log_dir")
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)))
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)))
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)))