diff --git a/news/050cda98-2240-11ea-9951-00e04c3600d8.trivial b/news/050cda98-2240-11ea-9951-00e04c3600d8.trivial new file mode 100644 index 000000000..e69de29bb diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index 06cd8314a..cce1e293c 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -1,19 +1,17 @@ """ -This code was taken from https://github.com/ActiveState/appdirs and modified -to suit our purposes. -""" +This code wraps the vendored appdirs module to so the return values are +compatible for the current pip code base. -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False +The intention is to rewrite current usages guradually, keeping the tests pass, +and eventually drop this after all usages are changed. +""" from __future__ import absolute_import import os -import sys -from pip._vendor.six import PY2, text_type +from pip._vendor import appdirs as _appdirs -from pip._internal.utils.compat import WINDOWS, expanduser from pip._internal.utils.typing import MYPY_CHECK_RUNNING if MYPY_CHECK_RUNNING: @@ -22,255 +20,22 @@ if MYPY_CHECK_RUNNING: def user_cache_dir(appname): # type: (str) -> str - r""" - Return full path to the user-specific cache dir for this application. - - "appname" is the name of application. - - Typical user cache directories are: - macOS: ~/Library/Caches/ - Unix: ~/.cache/ (XDG default) - Windows: C:\Users\\AppData\Local\\Cache - - On Windows the only suggestion in the MSDN docs is that local settings go - in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the - non-roaming app data dir (the default returned by `user_data_dir`). Apps - typically put cache data somewhere *under* the given dir here. Some - examples: - ...\Mozilla\Firefox\Profiles\\Cache - ...\Acme\SuperApp\Cache\1.0 - - OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. - """ - if WINDOWS: - # Get the base path - path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) - - # When using Python 2, return paths as bytes on Windows like we do on - # other operating systems. See helper function docs for more details. - if PY2 and isinstance(path, text_type): - path = _win_path_to_bytes(path) - - # Add our app name and Cache directory to it - path = os.path.join(path, appname, "Cache") - elif sys.platform == "darwin": - # Get the base path - path = expanduser("~/Library/Caches") - - # Add our app name to it - path = os.path.join(path, appname) - else: - # Get the base path - path = os.getenv("XDG_CACHE_HOME", expanduser("~/.cache")) - - # Add our app name to it - path = os.path.join(path, appname) - - return path - - -def user_data_dir(appname, roaming=False): - # type: (str, bool) -> str - r""" - Return full path to the user-specific data dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. - - Typical user data directories are: - macOS: ~/Library/Application Support/ - if it exists, else ~/.config/ - Unix: ~/.local/share/ # or in - $XDG_DATA_HOME, if defined - Win XP (not roaming): C:\Documents and Settings\\ ... - ...Application Data\ - Win XP (roaming): C:\Documents and Settings\\Local ... - ...Settings\Application Data\ - Win 7 (not roaming): C:\\Users\\AppData\Local\ - Win 7 (roaming): C:\\Users\\AppData\Roaming\ - - For Unix, we follow the XDG spec and support $XDG_DATA_HOME. - That means, by default "~/.local/share/". - """ - if WINDOWS: - const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" - path = os.path.join(os.path.normpath(_get_win_folder(const)), appname) - elif sys.platform == "darwin": - path = os.path.join( - expanduser('~/Library/Application Support/'), - appname, - ) if os.path.isdir(os.path.join( - expanduser('~/Library/Application Support/'), - appname, - ) - ) else os.path.join( - expanduser('~/.config/'), - appname, - ) - else: - path = os.path.join( - os.getenv('XDG_DATA_HOME', expanduser("~/.local/share")), - appname, - ) - - return path + return _appdirs.user_cache_dir(appname, appauthor=False) def user_config_dir(appname, roaming=True): # type: (str, bool) -> str - """Return full path to the user-specific config dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "roaming" (boolean, default True) can be set False to not use the - Windows roaming appdata directory. That means that for users on a - Windows network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. - - Typical user data directories are: - macOS: same as user_data_dir - Unix: ~/.config/ - Win *: same as user_data_dir - - For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. - That means, by default "~/.config/". - """ - if WINDOWS: - path = user_data_dir(appname, roaming=roaming) - elif sys.platform == "darwin": - path = user_data_dir(appname) - else: - path = os.getenv('XDG_CONFIG_HOME', expanduser("~/.config")) - path = os.path.join(path, appname) - - return path + return _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming) + + +def user_data_dir(appname, roaming=False): + # type: (str, bool) -> str + return _appdirs.user_data_dir(appname, appauthor=False, roaming=roaming) -# for the discussion regarding site_config_dirs locations -# see def site_config_dirs(appname): # type: (str) -> List[str] - r"""Return a list of potential user-shared config dirs for this application. - - "appname" is the name of application. - - Typical user config directories are: - macOS: /Library/Application Support// - Unix: /etc or $XDG_CONFIG_DIRS[i]// for each value in - $XDG_CONFIG_DIRS - Win XP: C:\Documents and Settings\All Users\Application ... - ...Data\\ - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory - on Vista.) - Win 7: Hidden, but writeable on Win 7: - C:\ProgramData\\ - """ - if WINDOWS: - path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) - pathlist = [os.path.join(path, appname)] - elif sys.platform == 'darwin': - pathlist = [os.path.join('/Library/Application Support', appname)] - else: - # try looking in $XDG_CONFIG_DIRS - xdg_config_dirs = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') - if xdg_config_dirs: - pathlist = [ - os.path.join(expanduser(x), appname) - for x in xdg_config_dirs.split(os.pathsep) - ] - else: - pathlist = [] - - # always look in /etc directly as well - pathlist.append('/etc') - - return pathlist - - -# -- Windows support functions -- - -def _get_win_folder_from_registry(csidl_name): - # type: (str) -> str - """ - This is a fallback technique at best. I'm not sure if using the - registry for this guarantees us the correct answer for all CSIDL_* - names. - """ - import _winreg - - shell_folder_name = { - "CSIDL_APPDATA": "AppData", - "CSIDL_COMMON_APPDATA": "Common AppData", - "CSIDL_LOCAL_APPDATA": "Local AppData", - }[csidl_name] - - key = _winreg.OpenKey( - _winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" - ) - directory, _type = _winreg.QueryValueEx(key, shell_folder_name) - return directory - - -def _get_win_folder_with_ctypes(csidl_name): - # type: (str) -> str - # On Python 2, ctypes.create_unicode_buffer().value returns "unicode", - # which isn't the same as str in the annotation above. - csidl_const = { - "CSIDL_APPDATA": 26, - "CSIDL_COMMON_APPDATA": 35, - "CSIDL_LOCAL_APPDATA": 28, - }[csidl_name] - - buf = ctypes.create_unicode_buffer(1024) - windll = ctypes.windll # type: ignore - windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) - - # Downgrade to short path name if have highbit chars. See - # . - 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 windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): - buf = buf2 - - # The type: ignore is explained under the type annotation for this function - return buf.value # type: ignore - - -if WINDOWS: - try: - import ctypes - _get_win_folder = _get_win_folder_with_ctypes - except ImportError: - _get_win_folder = _get_win_folder_from_registry - - -def _win_path_to_bytes(path): - """Encode Windows paths to bytes. Only used on Python 2. - - Motivation is to be consistent with other operating systems where paths - are also returned as bytes. This avoids problems mixing bytes and Unicode - elsewhere in the codebase. For more details and discussion see - . - - If encoding using ASCII and MBCS fails, return the original Unicode path. - """ - for encoding in ('ASCII', 'MBCS'): - try: - return path.encode(encoding) - except (UnicodeEncodeError, LookupError): - pass - return path + dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True) + if _appdirs.system == "linux2": + return dirval.split(os.pathsep) + return [dirval] diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/appdirs.py index 2bd391102..92b80251e 100644 --- a/src/pip/_vendor/appdirs.py +++ b/src/pip/_vendor/appdirs.py @@ -64,7 +64,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): for a discussion of issues. Typical user data directories are: - Mac OS X: ~/Library/Application Support/ + Mac OS X: ~/Library/Application Support/ # or ~/.config/, if the other does not exist Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined Win XP (not roaming): C:\Documents and Settings\\Application Data\\ Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ @@ -88,6 +88,10 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): path = os.path.expanduser('~/Library/Application Support/') if appname: path = os.path.join(path, appname) + if not os.path.isdir(path): + path = os.path.expanduser('~/.config/') + if appname: + path = os.path.join(path, appname) else: path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) if appname: @@ -150,7 +154,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): if appname: if version: appname = os.path.join(appname, version) - pathlist = [os.sep.join([x, appname]) for x in pathlist] + pathlist = [os.path.join(x, appname) for x in pathlist] if multipath: path = os.pathsep.join(pathlist) @@ -203,6 +207,8 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): return path +# for the discussion regarding site_config_dir locations +# see def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): r"""Return full path to the user-shared data dir for this application. @@ -245,7 +251,9 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) if appname: if version: appname = os.path.join(appname, version) - pathlist = [os.sep.join([x, appname]) for x in pathlist] + pathlist = [os.path.join(x, appname) for x in pathlist] + # always look in /etc directly as well + pathlist.append('/etc') if multipath: path = os.pathsep.join(pathlist) @@ -291,6 +299,10 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): if appauthor is None: appauthor = appname path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + # When using Python 2, return paths as bytes on Windows like we do on + # other operating systems. See helper function docs for more details. + if not PY3 and isinstance(path, unicode): + path = _win_path_to_bytes(path) if appname: if appauthor is not False: path = os.path.join(path, appauthor, appname) @@ -567,6 +579,24 @@ if system == "win32": _get_win_folder = _get_win_folder_from_registry +def _win_path_to_bytes(path): + """Encode Windows paths to bytes. Only used on Python 2. + + Motivation is to be consistent with other operating systems where paths + are also returned as bytes. This avoids problems mixing bytes and Unicode + elsewhere in the codebase. For more details and discussion see + . + + If encoding using ASCII and MBCS fails, return the original Unicode path. + """ + for encoding in ('ASCII', 'MBCS'): + try: + return path.encode(encoding) + except (UnicodeEncodeError, LookupError): + pass + return path + + #---- self test code if __name__ == "__main__": diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index 1ee68ef2f..1a0146417 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -4,6 +4,7 @@ import posixpath import sys import pretend +from pip._vendor import appdirs as _appdirs from pip._internal.utils import appdirs @@ -16,12 +17,12 @@ class TestUserCacheDir: return "C:\\Users\\test\\AppData\\Local" monkeypatch.setattr( - appdirs, + _appdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(appdirs, "WINDOWS", True) + monkeypatch.setattr(_appdirs, "system", "win32") monkeypatch.setattr(os, "path", ntpath) assert (appdirs.user_cache_dir("pip") == @@ -29,7 +30,7 @@ class TestUserCacheDir: assert _get_win_folder.calls == [pretend.call("CSIDL_LOCAL_APPDATA")] def test_user_cache_dir_osx(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") monkeypatch.setattr(sys, "platform", "darwin") @@ -37,7 +38,7 @@ class TestUserCacheDir: assert appdirs.user_cache_dir("pip") == "/home/test/Library/Caches/pip" def test_user_cache_dir_linux(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CACHE_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") @@ -46,7 +47,7 @@ class TestUserCacheDir: assert appdirs.user_cache_dir("pip") == "/home/test/.cache/pip" def test_user_cache_dir_linux_override(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_CACHE_HOME", "/home/test/.other-cache") monkeypatch.setenv("HOME", "/home/test") @@ -55,7 +56,7 @@ class TestUserCacheDir: assert appdirs.user_cache_dir("pip") == "/home/test/.other-cache/pip" def test_user_cache_dir_linux_home_slash(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) # Verify that we are not affected by https://bugs.python.org/issue14768 monkeypatch.delenv("XDG_CACHE_HOME", raising=False) @@ -71,7 +72,7 @@ class TestUserCacheDir: def my_get_win_folder(csidl_name): return u"\u00DF\u00E4\u03B1\u20AC" - monkeypatch.setattr(appdirs, "_get_win_folder", my_get_win_folder) + monkeypatch.setattr(_appdirs, "_get_win_folder", my_get_win_folder) # Do not use the isinstance expression directly in the # assert statement, as the Unicode characters in the result @@ -92,19 +93,19 @@ class TestSiteConfigDirs: return "C:\\ProgramData" monkeypatch.setattr( - appdirs, + _appdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(appdirs, "WINDOWS", True) + monkeypatch.setattr(_appdirs, "system", "win32") monkeypatch.setattr(os, "path", ntpath) assert appdirs.site_config_dirs("pip") == ["C:\\ProgramData\\pip"] assert _get_win_folder.calls == [pretend.call("CSIDL_COMMON_APPDATA")] def test_site_config_dirs_osx(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") monkeypatch.setattr(sys, "platform", "darwin") @@ -113,7 +114,7 @@ class TestSiteConfigDirs: ["/Library/Application Support/pip"] def test_site_config_dirs_linux(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) monkeypatch.setattr(sys, "platform", "linux2") @@ -124,7 +125,7 @@ class TestSiteConfigDirs: ] def test_site_config_dirs_linux_override(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setattr(os, "pathsep", ':') monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg") @@ -146,12 +147,12 @@ class TestUserDataDir: return "C:\\Users\\test\\AppData\\Local" monkeypatch.setattr( - appdirs, + _appdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(appdirs, "WINDOWS", True) + monkeypatch.setattr(_appdirs, "system", "win32") monkeypatch.setattr(os, "path", ntpath) assert (appdirs.user_data_dir("pip") == @@ -164,12 +165,12 @@ class TestUserDataDir: return "C:\\Users\\test\\AppData\\Roaming" monkeypatch.setattr( - appdirs, + _appdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(appdirs, "WINDOWS", True) + monkeypatch.setattr(_appdirs, "system", "win32") monkeypatch.setattr(os, "path", ntpath) assert ( @@ -179,7 +180,7 @@ class TestUserDataDir: assert _get_win_folder.calls == [pretend.call("CSIDL_APPDATA")] def test_user_data_dir_osx(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") monkeypatch.setattr(sys, "platform", "darwin") @@ -192,7 +193,7 @@ class TestUserDataDir: "/home/test/.config/pip") def test_user_data_dir_linux(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_DATA_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") @@ -201,7 +202,7 @@ class TestUserDataDir: assert appdirs.user_data_dir("pip") == "/home/test/.local/share/pip" def test_user_data_dir_linux_override(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_DATA_HOME", "/home/test/.other-share") monkeypatch.setenv("HOME", "/home/test") @@ -210,7 +211,7 @@ class TestUserDataDir: assert appdirs.user_data_dir("pip") == "/home/test/.other-share/pip" def test_user_data_dir_linux_home_slash(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) # Verify that we are not affected by https://bugs.python.org/issue14768 monkeypatch.delenv("XDG_DATA_HOME", raising=False) @@ -228,12 +229,12 @@ class TestUserConfigDir: return "C:\\Users\\test\\AppData\\Local" monkeypatch.setattr( - appdirs, + _appdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(appdirs, "WINDOWS", True) + monkeypatch.setattr(_appdirs, "system", "win32") monkeypatch.setattr(os, "path", ntpath) assert ( @@ -248,12 +249,12 @@ class TestUserConfigDir: return "C:\\Users\\test\\AppData\\Roaming" monkeypatch.setattr( - appdirs, + _appdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(appdirs, "WINDOWS", True) + monkeypatch.setattr(_appdirs, "system", "win32") monkeypatch.setattr(os, "path", ntpath) assert (appdirs.user_config_dir("pip") == @@ -261,7 +262,7 @@ class TestUserConfigDir: assert _get_win_folder.calls == [pretend.call("CSIDL_APPDATA")] def test_user_config_dir_osx(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") monkeypatch.setattr(sys, "platform", "darwin") @@ -274,7 +275,7 @@ class TestUserConfigDir: "/home/test/.config/pip") def test_user_config_dir_linux(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") @@ -283,7 +284,7 @@ class TestUserConfigDir: assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" def test_user_config_dir_linux_override(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config") monkeypatch.setenv("HOME", "/home/test") @@ -292,7 +293,7 @@ class TestUserConfigDir: assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip" def test_user_config_dir_linux_home_slash(self, monkeypatch): - monkeypatch.setattr(appdirs, "WINDOWS", False) + monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) # Verify that we are not affected by https://bugs.python.org/issue14768 monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) diff --git a/tools/automation/vendoring/patches/appdirs.patch b/tools/automation/vendoring/patches/appdirs.patch index 73f9f2b74..136f34b48 100644 --- a/tools/automation/vendoring/patches/appdirs.patch +++ b/tools/automation/vendoring/patches/appdirs.patch @@ -1,9 +1,69 @@ diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/appdirs.py -index ae67001a..2bd39110 100644 +index ae67001a..92b80251 100644 --- a/src/pip/_vendor/appdirs.py +++ b/src/pip/_vendor/appdirs.py -@@ -557,18 +557,14 @@ def _get_win_folder_with_jna(csidl_name): - +@@ -64,7 +64,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + for a discussion of issues. + + Typical user data directories are: +- Mac OS X: ~/Library/Application Support/ ++ Mac OS X: ~/Library/Application Support/ # or ~/.config/, if the other does not exist + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ +@@ -88,6 +88,10 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) ++ if not os.path.isdir(path): ++ path = os.path.expanduser('~/.config/') ++ if appname: ++ path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: +@@ -150,7 +154,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + if appname: + if version: + appname = os.path.join(appname, version) +- pathlist = [os.sep.join([x, appname]) for x in pathlist] ++ pathlist = [os.path.join(x, appname) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) +@@ -203,6 +207,8 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + return path + + ++# for the discussion regarding site_config_dir locations ++# see + def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + +@@ -245,7 +251,9 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) + if appname: + if version: + appname = os.path.join(appname, version) +- pathlist = [os.sep.join([x, appname]) for x in pathlist] ++ pathlist = [os.path.join(x, appname) for x in pathlist] ++ # always look in /etc directly as well ++ pathlist.append('/etc') + + if multipath: + path = os.pathsep.join(pathlist) +@@ -291,6 +299,10 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) ++ # When using Python 2, return paths as bytes on Windows like we do on ++ # other operating systems. See helper function docs for more details. ++ if not PY3 and isinstance(path, unicode): ++ path = _win_path_to_bytes(path) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) +@@ -557,18 +569,32 @@ def _get_win_folder_with_jna(csidl_name): + if system == "win32": try: - import win32com.shell @@ -23,6 +83,24 @@ index ae67001a..2bd39110 100644 - except ImportError: - _get_win_folder = _get_win_folder_from_registry + _get_win_folder = _get_win_folder_from_registry - - ++ ++ ++def _win_path_to_bytes(path): ++ """Encode Windows paths to bytes. Only used on Python 2. ++ ++ Motivation is to be consistent with other operating systems where paths ++ are also returned as bytes. This avoids problems mixing bytes and Unicode ++ elsewhere in the codebase. For more details and discussion see ++ . ++ ++ If encoding using ASCII and MBCS fails, return the original Unicode path. ++ """ ++ for encoding in ('ASCII', 'MBCS'): ++ try: ++ return path.encode(encoding) ++ except (UnicodeEncodeError, LookupError): ++ pass ++ return path + + #---- self test code