# -*- coding: utf-8 -*-
"""
This module provides an API for determining application specific directories for data, config, logs, etc.
public module members:
.. autosummary::
user_data_dir
user_config_dir
user_cache_dir
user_state_dir
user_logs_dir
site_data_dir
site_config_dir
site_data_dir_list
site_config_dir_list
This code is inspired by and builds on top of code from http://github.com/ActiveState/appdirs
"""
# Dev Notes:
# - MSDN on where to store app data files:
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
import sys
import os
from enum import Enum
class _FolderTypes(Enum):
data = 1
config = 2
cache = 3
state = 4
logs = 5
# only calculate this once, at import.
if sys.platform == 'win32':
platform = Platform.WINDOWS
elif sys.platform == 'darwin':
platform = Platform.MACOS
else:
platform = Platform.POSIX
[docs]def user_data_dir(app_name, app_author, version=None, roaming=False, use_virtualenv=True, create=True):
"""
Return the full path to the user data dir for this application, using a virtualenv location as a base, if it is
exists, and falling back to the host OS's convention if it doesn't.
If using a virtualenv, the path returned is :bash:`/path/to/virtualenv/data/app_name`
Typical user data directories are:
* Mac OS X: :bash:`~/Library/Application Support/<app_name>`
* Unix: :bash:`~/.local/share/<app_name> # or $XDG_DATA_HOME/<app_name>, if defined.`
* Win XP (not roaming): :bash:`C:\\Documents and Settings\\<username>\\Application Data\\<app_author>\\<app_name>`
* Win XP (roaming): :bash:`C:\\Documents and Settings\\<username>\\Local Settings\\Application Data\\<app_author>\\<app_name>`
* Win 7 (not roaming): :bash:`C:\\Users\\<username>\\AppData\\Local\\<app_author>\\<app_name>`
* Win 7 (roaming): :bash:`C:\\Users\\<username>\\AppData\\Roaming\\<app_author>\\<app_name>`
For Unix, we follow the XDG spec and support :bash:`$XDG_DATA_HOME`.
That means, by default :bash:`~/.local/share/<AppName>`.
Args:
str app_name: Name of the application. Will be appended to the base user data path.
str app_author: Only used in Windows when not in a virtualenv, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool roaming: roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be synchronized on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> for a discussion of issues.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the user data dir for this application.
"""
return _get_folder(True, _FolderTypes.data, app_name, app_author, version, roaming, use_virtualenv, create)[0]
[docs]def user_config_dir(app_name, app_author, version=None, roaming=False, use_virtualenv=True, create=True):
"""
Return the full path to the user config dir for this application, using a virtualenv location as a base, if it is
exists, and falling back to the host OS's convention if it doesn't.
If using a virtualenv, the path returned is :bash:`/path/to/virtualenv/config/app_name`
Typical user config directories are:
* Mac OS X: same as user_data_dir
* Unix: :bash:`~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined`
* Win \\*: same as user_data_dir
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
That means, by default :bash:`~/.config/<AppName>`.
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool roaming: roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be synchronized on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> for a discussion of issues.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the user config dir for this application.
"""
return _get_folder(True, _FolderTypes.config, app_name, app_author, version, roaming, use_virtualenv, create)[0]
[docs]def user_cache_dir(app_name, app_author, version=None, use_virtualenv=True, create=True):
"""
Return the full path to the user cache dir for this application, using a virtualenv location as a base, if it is
exists, and falling back to the host OS's convention if it doesn't.
If using a virtualenv, the path returned is :bash:`/path/to/virtualenv/cache/app_name`
Typical user cache directories are:
* Mac OS X: :bash:`~/Library/Caches/<AppName>`
* Unix: :bash:`~/.cache/<AppName> (XDG default)`
* Win XP: :bash:`C:\\Documents and Settings\\<username>\\Local Settings\\Application Data\\<AppAuthor>\\<AppName>\\Cache`
* Vista: :bash:`C:\\Users\\<username>\\AppData\\Local\\<AppAuthor>\\<AppName>\\Cache`
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the user cache dir for this application.
"""
return _get_folder(True, _FolderTypes.cache, app_name, app_author, version, False, use_virtualenv, create)[0]
[docs]def user_state_dir(app_name, app_author, version=None, roaming=False, use_virtualenv=True, create=True):
"""
Return the full path to the user state dir for this application, using a virtualenv location as a base, if it is
exists, and falling back to the host OS's convention if it doesn't.
If using a virtualenv, the path returned is :bash:`/path/to/virtualenv/state/app_name`
Typical user state directories are:
* Mac OS X: same as user_data_dir
* Unix: :bash:`~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined`
* Win \\*: same as user_data_dir
For Unix, we follow this Debian proposal https://wiki.debian.org/XDGBaseDirectorySpecification#state
to extend the XDG spec and support $XDG_STATE_HOME. That means, by default :bash:`~/.local/state/<AppName>`.
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool roaming: roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be synchronized on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> for a discussion of issues.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the user state dir for this application.
"""
return _get_folder(True, _FolderTypes.state, app_name, app_author, version, roaming, use_virtualenv, create)[0]
[docs]def user_logs_dir(app_name, app_author, version=None, use_virtualenv=True, create=True):
"""
Return the full path to the user log dir for this application, using a virtualenv location as a base, if it is
exists, and falling back to the host OS's convention if it doesn't.
If using a virtualenv, the path returned is :bash:`:bash:`/path/to/virtualenv/log/app_name``
Typical user log directories are:
* Mac OS X: :bash:`~/Library/Logs/<AppName>`
* Unix: :bash:`~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined`
* Win XP: :bash:`C:\\Documents and Settings\\<username>\\Local Settings\\Application Data\\<AppAuthor>\\<AppName>\\Logs`
* Vista: :bash:`C:\\Users\\<username>\\AppData\\Local\\<AppAuthor>\\<AppName>\\Logs`
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the user log dir for this application.
"""
return _get_folder(True, _FolderTypes.logs, app_name, app_author, version, False, use_virtualenv, create)[0]
[docs]def site_data_dir(app_name, app_author, version=None, use_virtualenv=True, create=False):
"""
Return the full path to the OS wide data dir for this application.
Typical site data directories are:
* Mac OS X: :bash:`/Library/Application Support/<AppName>`
* Unix: :bash:`/usr/local/share/<AppName> or /usr/share/<AppName>`
* Win XP: :bash:`C:\\Documents and Settings\\All Users\\Application Data\\<AppAuthor>\\<AppName>`
* Vista: :bash:`(Fail! "C:\\ProgramData" is a hidden *system* directory on Vista.)`
* Win 7: :bash:`C:\\ProgramData\\<AppAuthor>\\<AppName> # Hidden, but writeable on Win 7.`
For \*nix, this is using the :bash:`$XDG_DATA_DIRS` default.
.. Note::
On linux, the $XDG_DATA_DIRS environment variable may contain a list. `site_data_dir` returns the first
element of this list. If you want access to the whole list, use :func:`site_data_dir_list`
.. WARNING::
Do not use this on Windows Vista. See the Vista-Fail note above for why.
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the site data dir for this application.
"""
return _get_folder(False, _FolderTypes.data, app_name, app_author, version, False, use_virtualenv, create)[0]
[docs]def site_data_dir_list(app_name, app_author, version=None, use_virtualenv=True, create=False):
"""
Return the list of full path to the OS wide data directories for this application.
Typical site data directories are:
* Mac OS X: :bash:`/Library/Application Support/<AppName>`
* Unix: :bash:`/usr/local/share/<AppName> or /usr/share/<AppName>`
* Win XP: :bash:`C:\\Documents and Settings\\All Users\\Application Data\\<AppAuthor>\\<AppName>`
* Vista: :bash:`(Fail! "C:\\ProgramData" is a hidden *system* directory on Vista.)`
* Win 7: :bash:`C:\\ProgramData\\<AppAuthor>\\<AppName> # Hidden, but writeable on Win 7.`
For \*nix, this is using the :bash:`$XDG_DATA_DIRS` default.
.. WARNING::
Do not use this on Windows Vista. See the Vista-Fail note above for why.
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
list: A list to the full paths for site data directories for this application.
"""
return _get_folder(False, _FolderTypes.data, app_name, app_author, version, False, use_virtualenv, create)
[docs]def site_config_dir(app_name, app_author, version=None, use_virtualenv=True, create=False):
"""
Return the full path to the OS wide config dir for this application.
Typical site data directories are:
* Mac OS X: :bash:`/Library/Application Support/<AppName>`
* Unix: :bash:`/usr/local/share/<AppName> or /usr/share/<AppName>`
* Win XP: :bash:`C:\\Documents and Settings\\All Users\\Application Data\\<AppAuthor>\\<AppName>`
* Vista: :bash:`(Fail! "C:\\ProgramData" is a hidden *system* directory on Vista.)`
* Win 7: :bash:`C:\\ProgramData\\<AppAuthor>\\<AppName> # Hidden, but writeable on Win 7.`
For \*nix, this is using the :bash:`$XDG_DATA_DIRS` default.
.. Note::
On linux, the $XDG_CONFIG_DIRS environment variable may contain a list. `site_config_dir` returns the first
element of this list. If you want access to the whole list, use :func:`site_config_dir_list`
.. WARNING::
Do not use this on Windows Vista. See the Vista-Fail note above for why.
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
str: the full path to the site config dir for this application.
"""
return _get_folder(False, _FolderTypes.config, app_name, app_author, version, False, use_virtualenv, create)[0]
[docs]def site_config_dir_list(app_name, app_author, version=None, use_virtualenv=True, create=False):
"""
Return the list of full path to the OS wide data directories for this application.
Typical site data directories are:
* Mac OS X: :bash:`/Library/Application Support/<AppName>`
* Unix: :bash:`/usr/local/share/<AppName> or /usr/share/<AppName>`
* Win XP: :bash:`C:\\Documents and Settings\\All Users\\Application Data\\<AppAuthor>\\<AppName>`
* Vista: :bash:`(Fail! "C:\\ProgramData" is a hidden *system* directory on Vista.)`
* Win 7: :bash:`C:\\ProgramData\\<AppAuthor>\\<AppName> # Hidden, but writeable on Win 7.`
For \*nix, this is using the :bash:`$XDG_DATA_DIRS` default.
.. WARNING::
Do not use this on Windows Vista. See the Vista-Fail note above for why.
Args:
str app_name: Name of the application. Will be appended to the base user config path.
str app_author: Only used in Windows, name of the application author.
str version: If given, the application version identifier will be appended to the app_name.
bool use_virtualenv: If True and we're running inside of a virtualenv, return a path relative to that
environment.
bool create: If True, the folder is created if it does not exist before the path is returned.
Returns:
list: A list to the full paths for site data directories for this application.
"""
return _get_folder(False, _FolderTypes.config, app_name, app_author, version, False, use_virtualenv, create)
def _get_folder(user, folder_type, app_name, app_author, version, roaming, use_virtualenv, create):
"""
Get the directory corresponding to the appropriate folder type and operating system.
The folder is returned, with the app_name, and in the case of windows app_author appended to it.
If version is not None, it is appended to app_name to allow for multiple versions to run in the same place.
Since some operating systems have more than one appropriate folder of a given time (e.g. site_data on linux),
A list is returned. It is up to calling functions to handle the contents of this list.
Args:
_FolderTypes folder_type: Folder type value.
str app_name: Name of the app, the returned dir has os.path.basename == app_name
str app_author: Name of app author, only used in Windows.
str version: App version, appended to app_name
bool roaming: Whether or not the Windows user is roaming.
bool use_virtualenv: If True and a virtualenv is activated, use the virtualenv path instead of the OS
convention. Note: This is ignored for site directories, for obvious reasons.
bool create: If True, create the directory if it doesn't exist. In the case of lists of directories, all folders
are created.
Returns:
list: A list of paths.
"""
if user and use_virtualenv and _in_virtualenv():
sub_folder = folder_type.name # data | config | state | log | cache
paths = [os.path.join(sys.prefix, sub_folder)]
elif platform == Platform.WINDOWS:
if user:
if folder_type in [_FolderTypes.data, _FolderTypes.config, _FolderTypes.state]:
paths = [os.path.normpath(_get_win_folder(site=False, roaming=roaming, app_author=app_author))]
elif folder_type == _FolderTypes.cache:
# we'll follow the MSDN recommendation on local data, but since they're mum on caches,
# we'll put them in LOCAL_APPDATA/app_author/Caches.
path = os.path.normpath(_get_win_folder(site=False, roaming=False, app_author=app_author))
paths = [os.path.join(path, 'Caches')]
else: # folder_type == _FolderTypes.logs:
# Similar issue as with user caches. MSDN is no help.
path = os.path.normpath(_get_win_folder(site=False, roaming=False, app_author=app_author))
paths = [os.path.join(path, 'Logs')]
elif folder_type in [_FolderTypes.data, _FolderTypes.config]:
paths = [os.path.normpath(_get_win_folder(site=True, roaming=roaming, app_author=app_author))]
else:
raise RuntimeError('Unknown folder type: {}, user: {}'.format(folder_type.name, user))
elif platform == Platform.MACOS:
if user:
if folder_type in [_FolderTypes.data, _FolderTypes.config, _FolderTypes.state]:
paths = [os.path.expanduser('~/Library/Application Support')]
elif folder_type == _FolderTypes.cache:
paths = [os.path.expanduser('~/Library/Caches')]
else: # folder_type == _FolderTypes.logs:
paths = [os.path.expanduser('~/Library/Logs')]
elif folder_type in [_FolderTypes.data, _FolderTypes.config]:
paths = [os.path.expanduser('/Library/Application Support')]
else:
raise RuntimeError('Unknown folder type: {}, user: {}'.format(folder_type.name, user))
elif platform == Platform.POSIX:
if user:
if folder_type == _FolderTypes.data:
paths = [os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))]
elif folder_type == _FolderTypes.config:
paths = [os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))]
elif folder_type == _FolderTypes.state:
paths = [os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))]
elif folder_type == _FolderTypes.cache:
paths = [os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))]
else: # folder_type == _FolderTypes.logs:
paths = [os.path.expanduser('~/.log')]
elif folder_type == _FolderTypes.data:
path = os.getenv('XDG_DATA_DIRS', os.pathsep.join(['/usr/local/share', '/usr/share']))
paths = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
elif folder_type == _FolderTypes.config:
path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
paths = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
else:
raise RuntimeError('Unknown folder type: {}, user: {}'.format(folder_type.name, user))
else:
raise RuntimeError('Unsupported operating system: {}'.format(sys.platform))
final_paths = []
for path in paths:
final_path = os.path.join(path, app_name)
if version is not None:
final_path = '{}_{}'.format(final_path, version)
if create and not os.path.exists(final_path):
os.makedirs(final_path)
final_paths.append(final_path)
return final_paths
def _in_virtualenv():
"""
Determine if we're in a virtual env.
If sys.real_prefix exists, we are in a virtualenv, and sys.prefix is the virtualenv path, while
sys.real_prefix is the 'system' python.
if sys.real_prefix does not exist, it could be because we're in python 3 and the user is using
the built in venv module instead of virtualenv. In this case a sys.base_prefix attribute always exists, and is
is different from sys.prefix.
In either case, the path we want in case we are in a virtualenv is sys.prefix.
Returns:
bool: True if we are running in a virtual environment, and False otherwise.
"""
return hasattr(sys, 'real_prefix') or hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix
def _get_win_folder(site, roaming, app_author):
import ctypes
if not isinstance(app_author, str) or app_author == '':
raise RuntimeError('On Windows, app_author must be a non-empty string.')
# As of Windows Vista, these values have been replaced by KNOWNFOLDERID values.
# The CSIDL system is supported under Windows Vista, 8 and 10 for compatibility reasons,
# And the function SHGetFolderPath maps these values to SHGetKnownFolderID. A future version of this
# library will need to updated if support for CSIDL is ever dropped.
csidl_consts = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
}
if site:
csidl_const = csidl_consts['CSIDL_COMMON_APPDATA']
elif roaming:
csidl_const = csidl_consts['CSIDL_APPDATA']
else:
csidl_const = csidl_consts['CSIDL_LOCAL_APPDATA']
buf = ctypes.create_unicode_buffer(1024)
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
# Downgrade to short path name if have highbit chars. See
# http://bugs.activestate.com/show_bug.cgi?id=85099.
# Oren: This bug is not publicly available!
has_high_char = False
for c in buf:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf2 = ctypes.create_unicode_buffer(1024)
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2
return os.path.join(buf.value, app_author)