From ae6cf66c79734924544c4d5312dc0ca6072f324b Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Sat, 24 Jul 2021 23:18:35 +0100 Subject: [PATCH 01/11] Switch from appdirs to platformdirs --- pyproject.toml | 2 +- src/pip/_internal/utils/appdirs.py | 10 +- src/pip/_vendor/__init__.py | 2 +- src/pip/_vendor/pkg_resources/__init__.py | 4 +- ...s.LICENSE.txt => platformdirs.LICENSE.txt} | 0 .../_vendor/{appdirs.py => platformdirs.py} | 651 ++++++++++-------- src/pip/_vendor/vendor.txt | 2 +- tests/unit/test_appdirs.py | 143 ++-- tools/vendoring/patches/appdirs.patch | 115 ---- tools/vendoring/patches/pkg_resources.patch | 22 + tools/vendoring/patches/platformdirs.patch | 20 + 11 files changed, 490 insertions(+), 481 deletions(-) rename src/pip/_vendor/{appdirs.LICENSE.txt => platformdirs.LICENSE.txt} (100%) rename src/pip/_vendor/{appdirs.py => platformdirs.py} (61%) delete mode 100644 tools/vendoring/patches/appdirs.patch create mode 100644 tools/vendoring/patches/pkg_resources.patch create mode 100644 tools/vendoring/patches/platformdirs.patch diff --git a/pyproject.toml b/pyproject.toml index a479b968d6d..4ec4d1421ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,8 +48,8 @@ drop = [ [tool.vendoring.typing-stubs] six = ["six.__init__", "six.moves.__init__", "six.moves.configparser"] -appdirs = [] distro = [] +platformdirs = [] [tool.vendoring.license.directories] setuptools = "pkg_resources" diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index a8403b7dee4..eb77104496b 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -9,7 +9,7 @@ import os from typing import List -from pip._vendor import appdirs as _appdirs +from pip._vendor import platformdirs as _appdirs def user_cache_dir(appname: str) -> str: @@ -29,7 +29,11 @@ def user_config_dir(appname: str, roaming: bool = True) -> str: # see def site_config_dirs(appname: str) -> List[str]: dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True) - if _appdirs.system not in ["win32", "darwin"]: + if _appdirs.system == "darwin": + # always look in /Library/Application Support/pip as well + return dirval.split(os.pathsep) + ["/Library/Application Support/pip"] + elif _appdirs.system == "win32": + return [dirval] + else: # always look in /etc directly as well return dirval.split(os.pathsep) + ["/etc"] - return [dirval] diff --git a/src/pip/_vendor/__init__.py b/src/pip/_vendor/__init__.py index 57e32dab105..3843cb09955 100644 --- a/src/pip/_vendor/__init__.py +++ b/src/pip/_vendor/__init__.py @@ -58,7 +58,6 @@ def vendored(modulename): sys.path[:] = glob.glob(os.path.join(WHEEL_DIR, "*.whl")) + sys.path # Actually alias all of our vendored dependencies. - vendored("appdirs") vendored("cachecontrol") vendored("certifi") vendored("colorama") @@ -74,6 +73,7 @@ def vendored(modulename): vendored("packaging.specifiers") vendored("pep517") vendored("pkg_resources") + vendored("platformdirs") vendored("progress") vendored("requests") vendored("requests.exceptions") diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py index a457ff27ef0..4cd562cf94c 100644 --- a/src/pip/_vendor/pkg_resources/__init__.py +++ b/src/pip/_vendor/pkg_resources/__init__.py @@ -77,7 +77,7 @@ importlib_machinery = None from . import py31compat -from pip._vendor import appdirs +from pip._vendor import platformdirs from pip._vendor import packaging __import__('pip._vendor.packaging.version') __import__('pip._vendor.packaging.specifiers') @@ -1310,7 +1310,7 @@ def get_default_cache(): """ return ( os.environ.get('PYTHON_EGG_CACHE') - or appdirs.user_cache_dir(appname='Python-Eggs') + or platformdirs.user_cache_dir(appname='Python-Eggs') ) diff --git a/src/pip/_vendor/appdirs.LICENSE.txt b/src/pip/_vendor/platformdirs.LICENSE.txt similarity index 100% rename from src/pip/_vendor/appdirs.LICENSE.txt rename to src/pip/_vendor/platformdirs.LICENSE.txt diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/platformdirs.py similarity index 61% rename from src/pip/_vendor/appdirs.py rename to src/pip/_vendor/platformdirs.py index 33a3b77410c..3b8ef8ad8cc 100644 --- a/src/pip/_vendor/appdirs.py +++ b/src/pip/_vendor/platformdirs.py @@ -1,28 +1,27 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c) 2005-2010 ActiveState Software Inc. # Copyright (c) 2013 Eddy Petrișor """Utilities for determining application-specific dirs. -See for details and usage. +See for details and usage. """ # 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 +# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -__version__ = "1.4.4" -__version_info__ = tuple(int(segment) for segment in __version__.split(".")) +__version__ = "2.0.2" +__version_info__ = 2, 0, 2 import sys import os -PY3 = sys.version_info[0] == 3 +PY2 = sys.version_info[0] == 2 -if PY3: +if not PY2: unicode = str if sys.platform.startswith('java'): @@ -37,14 +36,348 @@ # are actually checked for and the rest of the module expects # *sys.platform* style strings. system = 'linux2' -elif sys.platform == 'cli' and os.name == 'nt': - # Detect Windows in IronPython to match pip._internal.utils.compat.WINDOWS - # Discussion: - system = 'win32' else: system = sys.platform +# https://docs.python.org/dev/library/sys.html#sys.platform +if system == 'win32': + try: + from ctypes import windll + except ImportError: + try: + import com.sun.jna + except ImportError: + try: + if PY2: + import _winreg as winreg + else: + import winreg + except ImportError: + def _get_win_folder(csidl_name): + """Get folder from environment variables.""" + if csidl_name == 'CSIDL_APPDATA': + env_var_name = 'APPDATA' + elif csidl_name == 'CSIDL_COMMON_APPDATA': + env_var_name = 'ALLUSERSPROFILE' + elif csidl_name == 'CSIDL_LOCAL_APPDATA': + env_var_name = 'LOCALAPPDATA' + else: + raise ValueError('Unknown CSIDL name: {}'.format(csidl_name)) + + if env_var_name in os.environ: + return os.environ[env_var_name] + else: + raise ValueError('Unset environment variable: {}'.format(env_var_name)) + else: + def _get_win_folder(csidl_name): + """Get folder from the registry. + + 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. + """ + if csidl_name == 'CSIDL_APPDATA': + shell_folder_name = 'AppData' + elif csidl_name == 'CSIDL_COMMON_APPDATA': + shell_folder_name = 'Common AppData' + elif csidl_name == 'CSIDL_LOCAL_APPDATA': + shell_folder_name = 'Local AppData' + else: + raise ValueError('Unknown CSIDL name: {}'.format(csidl_name)) + + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' + ) + directory, _ = winreg.QueryValueEx(key, shell_folder_name) + return directory + else: + def _get_win_folder_with_jna(csidl_name): + """Get folder with JNA.""" + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath( + None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf + ) + directory = jna.Native.toString(buf.tostring()).rstrip('\0') + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in directory: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(directory, buf, buf_size): + directory = jna.Native.toString(buf.tostring()).rstrip('\0') + + return directory + else: + def _get_win_folder(csidl_name): + """Get folder with ctypes.""" + import ctypes + + if csidl_name == 'CSIDL_APPDATA': + csidl_const = 26 + elif csidl_name == 'CSIDL_COMMON_APPDATA': + csidl_const = 35 + elif csidl_name == 'CSIDL_LOCAL_APPDATA': + csidl_const = 28 + else: + raise ValueError('Unknown CSIDL name: {}'.format(csidl_name)) + + 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 + # . + 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 buf.value + + def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + if appauthor is None: + appauthor = appname + + const = 'CSIDL_APPDATA' if roaming else 'CSIDL_LOCAL_APPDATA' + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + + if version: + path = os.path.join(path, version) + + return path + + def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False): + if appauthor is None: + appauthor = appname + + path = os.path.normpath(_get_win_folder('CSIDL_COMMON_APPDATA')) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + + if version: + path = os.path.join(path, version) + + return path + + def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) + + def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): + return _site_data_dir_impl(appname=appname, appauthor=appauthor, version=version) + + def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True): + if appauthor is None: + appauthor = appname + + path = os.path.normpath(_get_win_folder('CSIDL_LOCAL_APPDATA')) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + + if opinion: + path = os.path.join(path, 'Cache') + + if version: + path = os.path.join(path, version) + + return path + + def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) + + def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True): + path = _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version) + if opinion: + path = os.path.join(path, 'Logs') + + return path + +elif system == 'darwin': + + def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False): + path = '/Library/Application Support' + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + path = os.path.expanduser('~/Library/Preferences/') + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): + path = '/Library/Preferences' + if appname: + path = os.path.join(path, appname) + + return path + + def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True): + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) + + def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True): + path = os.path.expanduser('~/Library/Logs') + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + +else: + + def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + if 'XDG_DATA_HOME' in os.environ: + path = os.environ['XDG_DATA_HOME'] + else: + path = os.path.expanduser('~/.local/share') + + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False): + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + if 'XDG_DATA_DIRS' in os.environ: + path = os.environ['XDG_DATA_DIRS'] + else: + path = '/usr/local/share{}/usr/share'.format(os.pathsep) + + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.path.join(x, appname) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + + return path + + def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + if 'XDG_CONFIG_HOME' in os.environ: + path = os.environ['XDG_CONFIG_HOME'] + else: + path = os.path.expanduser('~/.config') + + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): + # XDG default for $XDG_CONFIG_DIRSS (missing or empty) + # see + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' + + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.path.join(x, appname) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + + return path + + def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True): + if 'XDG_CACHE_HOME' in os.environ: + path = os.environ['XDG_CACHE_HOME'] + else: + path = os.path.expanduser('~/.cache') + + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False): + if 'XDG_STATE_HOME' in os.environ: + path = os.environ['XDG_STATE_HOME'] + else: + path = os.path.expanduser('~/.local/state') + + if appname: + path = os.path.join(path, appname) + if version: + path = os.path.join(path, version) + + return path + + def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True): + path = _user_cache_dir_impl(appname=appname, appauthor=appauthor, version=version) + if opinion: + path = os.path.join(path, 'log') + + return path + def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): r"""Return full path to the user-specific data dir for this application. @@ -68,7 +401,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/ # or ~/.config/, if the other does not exist + Mac OS X: ~/Library/Application Support/ 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\\ @@ -78,27 +411,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): For Unix, we follow the XDG spec and support $XDG_DATA_HOME. That means, by default "~/.local/share/". """ - if system == "win32": - if appauthor is None: - appauthor = appname - const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" - path = os.path.normpath(_get_win_folder(const)) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - elif system == 'darwin': - path = os.path.expanduser('~/Library/Application Support/') - if appname: - path = os.path.join(path, appname) - else: - path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path + return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): @@ -132,39 +445,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ - if system == "win32": - if appauthor is None: - appauthor = appname - path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - elif system == 'darwin': - path = os.path.expanduser('/Library/Application Support') - if appname: - path = os.path.join(path, appname) - else: - # XDG default for $XDG_DATA_DIRS - # only first, if multipath is False - path = os.getenv('XDG_DATA_DIRS', - os.pathsep.join(['/usr/local/share', '/usr/share'])) - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] - if appname: - if version: - appname = os.path.join(appname, version) - pathlist = [os.path.join(x, appname) for x in pathlist] - - if multipath: - path = os.pathsep.join(pathlist) - else: - path = pathlist[0] - return path - - if appname and version: - path = os.path.join(path, version) - return path + return _site_data_dir_impl(appname=appname, appauthor=appauthor, version=version, multipath=multipath) def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): @@ -189,26 +470,16 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): for a discussion of issues. Typical user config directories are: - Mac OS X: same as user_data_dir + Mac OS X: ~/Library/Preferences/ Unix: ~/.config/ # 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 "~/.config/". """ - if system in ["win32", "darwin"]: - path = user_data_dir(appname, appauthor, None, roaming) - else: - path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path + return _user_config_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) -# 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. @@ -239,26 +510,7 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ - if system in ["win32", "darwin"]: - path = site_data_dir(appname, appauthor) - if appname and version: - path = os.path.join(path, version) - else: - # XDG default for $XDG_CONFIG_DIRS (missing or empty) - # see - # only first, if multipath is False - path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) if x] - if appname: - if version: - appname = os.path.join(appname, version) - pathlist = [os.path.join(x, appname) for x in pathlist] - - if multipath: - path = os.pathsep.join(pathlist) - else: - path = pathlist[0] - return path + return _site_config_dir_impl(appname=appname, appauthor=appauthor, version=version, multipath=multipath) def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): @@ -294,32 +546,7 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. This can be disabled with the `opinion=False` option. """ - if system == "win32": - 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) - else: - path = os.path.join(path, appname) - if opinion: - path = os.path.join(path, "Cache") - elif system == 'darwin': - path = os.path.expanduser('~/Library/Caches') - if appname: - path = os.path.join(path, appname) - else: - path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path + return _user_cache_dir_impl(appname=appname, appauthor=appauthor, version=version, opinion=opinion) def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): @@ -353,15 +580,7 @@ def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): That means, by default "~/.local/state/". """ - if system in ["win32", "darwin"]: - path = user_data_dir(appname, appauthor, None, roaming) - else: - path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path + return _user_state_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): @@ -396,26 +615,10 @@ def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): value for Windows and appends "log" to the user cache dir for Unix. This can be disabled with the `opinion=False` option. """ - if system == "darwin": - path = os.path.join( - os.path.expanduser('~/Library/Logs'), - appname) - elif system == "win32": - path = user_data_dir(appname, appauthor, version) - version = False - if opinion: - path = os.path.join(path, "Logs") - else: - path = user_cache_dir(appname, appauthor, version) - version = False - if opinion: - path = os.path.join(path, "log") - if appname and version: - path = os.path.join(path, version) - return path + return _user_log_dir_impl(appname=appname, appauthor=appauthor, version=version, opinion=opinion) -class AppDirs(object): +class PlatformDirs(object): """Convenience wrapper for getting application dirs.""" def __init__(self, appname=None, appauthor=None, version=None, roaming=False, multipath=False): @@ -461,144 +664,12 @@ def user_log_dir(self): version=self.version) -#---- internal support stuff - -def _get_win_folder_from_registry(csidl_name): - """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. - """ - if PY3: - import winreg as _winreg - else: - 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" - ) - dir, type = _winreg.QueryValueEx(key, shell_folder_name) - return dir - - -def _get_win_folder_with_pywin32(csidl_name): - from win32com.shell import shellcon, shell - dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) - # Try to make this a unicode path because SHGetFolderPath does - # not return unicode strings when there is unicode data in the - # path. - try: - dir = unicode(dir) - - # Downgrade to short path name if have highbit chars. See - # . - has_high_char = False - for c in dir: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - try: - import win32api - dir = win32api.GetShortPathName(dir) - except ImportError: - pass - except UnicodeError: - pass - return dir - - -def _get_win_folder_with_ctypes(csidl_name): - import ctypes - - csidl_const = { - "CSIDL_APPDATA": 26, - "CSIDL_COMMON_APPDATA": 35, - "CSIDL_LOCAL_APPDATA": 28, - }[csidl_name] - - 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 - # . - 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 buf.value - -def _get_win_folder_with_jna(csidl_name): - import array - from com.sun import jna - from com.sun.jna.platform import win32 - - buf_size = win32.WinDef.MAX_PATH * 2 - buf = array.zeros('c', buf_size) - shell = win32.Shell32.INSTANCE - shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) - dir = jna.Native.toString(buf.tostring()).rstrip("\0") - - # Downgrade to short path name if have highbit chars. See - # . - has_high_char = False - for c in dir: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - buf = array.zeros('c', buf_size) - kernel = win32.Kernel32.INSTANCE - if kernel.GetShortPathName(dir, buf, buf_size): - dir = jna.Native.toString(buf.tostring()).rstrip("\0") - - return dir - -if system == "win32": - try: - from ctypes import windll - _get_win_folder = _get_win_folder_with_ctypes - except ImportError: - try: - import com.sun.jna - _get_win_folder = _get_win_folder_with_jna - 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 - +# Backwards compatibility with appdirs +AppDirs = PlatformDirs -#---- self test code if __name__ == "__main__": + # ---- self test code appname = "MyApp" appauthor = "MyCompany" @@ -613,21 +684,21 @@ def _win_path_to_bytes(path): print("-- app dirs %s --" % __version__) print("-- app dirs (with optional 'version')") - dirs = AppDirs(appname, appauthor, version="1.0") + dirs = PlatformDirs(appname, appauthor, version="1.0") for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) print("\n-- app dirs (without optional 'version')") - dirs = AppDirs(appname, appauthor) + dirs = PlatformDirs(appname, appauthor) for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) print("\n-- app dirs (without optional 'appauthor')") - dirs = AppDirs(appname) + dirs = PlatformDirs(appname) for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) print("\n-- app dirs (with disabled 'appauthor')") - dirs = AppDirs(appname, appauthor=False) + dirs = PlatformDirs(appname, appauthor=False) for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index 991c3af8c49..a0402fab83e 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -1,4 +1,3 @@ -appdirs==1.4.4 CacheControl==0.12.6 colorama==0.4.4 distlib==0.3.3 @@ -7,6 +6,7 @@ html5lib==1.1 msgpack==1.0.2 packaging==21.0 pep517==0.11.0 +platformdirs==2.0.2 progress==1.5 pyparsing==2.4.7 requests==2.26.0 diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index 70453c2755c..0cee99dd5de 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -1,3 +1,4 @@ +import importlib import ntpath import os import posixpath @@ -5,23 +6,66 @@ from unittest import mock import pytest -from pip._vendor import appdirs as _appdirs +from pip._vendor import platformdirs from pip._internal.utils import appdirs +@pytest.fixture() +def platformdirs_win32(monkeypatch): + # Monkeypatch platformdirs to pretend we're running on Windows + + with monkeypatch.context() as m: + m.setattr(sys, "platform", "win32") + m.setattr(os, "path", ntpath) + importlib.reload(platformdirs) + importlib.reload(appdirs) + yield + + importlib.reload(platformdirs) + importlib.reload(appdirs) + + +@pytest.fixture() +def platformdirs_darwin(monkeypatch): + # Monkeypatch platformdirs to pretend we're running on macOS + + with monkeypatch.context() as m: + m.setattr(sys, "platform", "darwin") + m.setattr(os, "path", posixpath) + importlib.reload(platformdirs) + importlib.reload(appdirs) + yield + + importlib.reload(platformdirs) + importlib.reload(appdirs) + + +@pytest.fixture() +def platformdirs_linux(monkeypatch): + # Monkeypatch platformdirs to pretend we're running on Linux + + with monkeypatch.context() as m: + m.setattr(sys, "platform", "linux") + m.setattr(os, "path", posixpath) + importlib.reload(platformdirs) + importlib.reload(appdirs) + yield + + importlib.reload(platformdirs) + importlib.reload(appdirs) + + class TestUserCacheDir: - def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - _appdirs, + platformdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(_appdirs, "system", "win32") - monkeypatch.setattr(os, "path", ntpath) assert ( appdirs.user_cache_dir("pip") @@ -29,43 +73,31 @@ def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch) -> None: ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] - def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) + def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "darwin") assert appdirs.user_cache_dir("pip") == "/home/test/Library/Caches/pip" - def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None: monkeypatch.delenv("XDG_CACHE_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.user_cache_dir("pip") == "/home/test/.cache/pip" def test_user_cache_dir_linux_override( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_CACHE_HOME", "/home/test/.other-cache") monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.user_cache_dir("pip") == "/home/test/.other-cache/pip" def test_user_cache_dir_linux_home_slash( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - 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) monkeypatch.setenv("HOME", "/") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.user_cache_dir("pip") == "/.cache/pip" @@ -76,7 +108,7 @@ def test_user_cache_dir_unicode(self, monkeypatch: pytest.MonkeyPatch) -> None: def my_get_win_folder(csidl_name): return "\u00DF\u00E4\u03B1\u20AC" - monkeypatch.setattr(_appdirs, "_get_win_folder", my_get_win_folder) + monkeypatch.setattr(platformdirs, "_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 @@ -91,45 +123,38 @@ def my_get_win_folder(csidl_name): class TestSiteConfigDirs: - def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32) -> None: _get_win_folder = mock.Mock(return_value="C:\\ProgramData") monkeypatch.setattr( - _appdirs, + platformdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(_appdirs, "system", "win32") - monkeypatch.setattr(os, "path", ntpath) assert appdirs.site_config_dirs("pip") == ["C:\\ProgramData\\pip"] assert _get_win_folder.call_args_list == [mock.call("CSIDL_COMMON_APPDATA")] - def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) + def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "darwin") - assert appdirs.site_config_dirs("pip") == ["/Library/Application Support/pip"] + assert appdirs.site_config_dirs("pip") == [ + "/Library/Preferences/pip", + "/Library/Application Support/pip" + ] - def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None: monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] def test_site_config_dirs_linux_override( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) - monkeypatch.setattr(os, "pathsep", ":") + monkeypatch.setattr(os, "pathsep", ':') monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.site_config_dirs("pip") == [ "/spam/pip", @@ -139,30 +164,26 @@ def test_site_config_dirs_linux_override( ] def test_site_config_dirs_linux_empty( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) - monkeypatch.setattr(os, "pathsep", ":") + monkeypatch.setattr(os, "pathsep", ':') monkeypatch.setenv("XDG_CONFIG_DIRS", "") - monkeypatch.setattr(sys, "platform", "linux2") - assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] + assert appdirs.site_config_dirs("pip") == ['/etc/xdg/pip', '/etc'] class TestUserConfigDir: def test_user_config_dir_win_no_roaming( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32 ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - _appdirs, + platformdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(_appdirs, "system", "win32") - monkeypatch.setattr(os, "path", ntpath) assert ( appdirs.user_config_dir("pip", roaming=False) @@ -171,29 +192,24 @@ def test_user_config_dir_win_no_roaming( assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] def test_user_config_dir_win_yes_roaming( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32 ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Roaming") monkeypatch.setattr( - _appdirs, + platformdirs, "_get_win_folder", _get_win_folder, raising=False, ) - monkeypatch.setattr(_appdirs, "system", "win32") - monkeypatch.setattr(os, "path", ntpath) assert ( appdirs.user_config_dir("pip") == "C:\\Users\\test\\AppData\\Roaming\\pip" ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_APPDATA")] - def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) + def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "darwin") if os.path.isdir("/home/test/Library/Application Support/"): assert ( @@ -203,34 +219,25 @@ def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: else: assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" - def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" def test_user_config_dir_linux_override( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config") monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip" def test_user_config_dir_linux_home_slash( - self, monkeypatch: pytest.MonkeyPatch + self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - 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) monkeypatch.setenv("HOME", "/") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.user_config_dir("pip") == "/.config/pip" diff --git a/tools/vendoring/patches/appdirs.patch b/tools/vendoring/patches/appdirs.patch deleted file mode 100644 index 69afd3e8681..00000000000 --- a/tools/vendoring/patches/appdirs.patch +++ /dev/null @@ -1,115 +0,0 @@ -diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/appdirs.py -index ae67001a..3a52b758 100644 ---- a/src/pip/_vendor/appdirs.py -+++ b/src/pip/_vendor/appdirs.py -@@ -37,6 +37,10 @@ if sys.platform.startswith('java'): - # are actually checked for and the rest of the module expects - # *sys.platform* style strings. - system = 'linux2' -+elif sys.platform == 'cli' and os.name == 'nt': -+ # Detect Windows in IronPython to match pip._internal.utils.compat.WINDOWS -+ # Discussion: -+ system = 'win32' - else: - system = sys.platform - -@@ -64,7 +68,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\\ -@@ -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 +203,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. - -@@ -238,14 +244,15 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) - if appname and version: - path = os.path.join(path, version) - else: -- # XDG default for $XDG_CONFIG_DIRS -+ # XDG default for $XDG_CONFIG_DIRS (missing or empty) -+ # see - # only first, if multipath is False -- path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') -- pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] -+ path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' -+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) if x] - 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) -@@ -291,6 +300,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 +570,32 @@ def _get_win_folder_with_jna(csidl_name): - - if system == "win32": - try: -- import win32com.shell -- _get_win_folder = _get_win_folder_with_pywin32 -+ from ctypes import windll -+ _get_win_folder = _get_win_folder_with_ctypes - except ImportError: - try: -- from ctypes import windll -- _get_win_folder = _get_win_folder_with_ctypes -+ import com.sun.jna -+ _get_win_folder = _get_win_folder_with_jna - except ImportError: -- try: -- import com.sun.jna -- _get_win_folder = _get_win_folder_with_jna -- 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 diff --git a/tools/vendoring/patches/pkg_resources.patch b/tools/vendoring/patches/pkg_resources.patch new file mode 100644 index 00000000000..eea49e475af --- /dev/null +++ b/tools/vendoring/patches/pkg_resources.patch @@ -0,0 +1,22 @@ +diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py +index a457ff27e..4cd562cf9 100644 +--- a/src/pip/_vendor/pkg_resources/__init__.py ++++ b/src/pip/_vendor/pkg_resources/__init__.py +@@ -77,7 +77,7 @@ + importlib_machinery = None + + from . import py31compat +-from pip._vendor import appdirs ++from pip._vendor import platformdirs + from pip._vendor import packaging + __import__('pip._vendor.packaging.version') + __import__('pip._vendor.packaging.specifiers') +@@ -1310,7 +1310,7 @@ + """ + return ( + os.environ.get('PYTHON_EGG_CACHE') +- or appdirs.user_cache_dir(appname='Python-Eggs') ++ or platformdirs.user_cache_dir(appname='Python-Eggs') + ) + + diff --git a/tools/vendoring/patches/platformdirs.patch b/tools/vendoring/patches/platformdirs.patch new file mode 100644 index 00000000000..00831244191 --- /dev/null +++ b/tools/vendoring/patches/platformdirs.patch @@ -0,0 +1,20 @@ +diff --git a/src/pip/_vendor/platformdirs.py b/src/pip/_vendor/platformdirs.py +index 23c6af8c7..3b8ef8ad8 100644 +--- a/src/pip/_vendor/platformdirs.py ++++ b/src/pip/_vendor/platformdirs.py +@@ -327,12 +327,10 @@ else: + return path + + def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): +- # XDG default for $XDG_CONFIG_DIRS ++ # XDG default for $XDG_CONFIG_DIRSS (missing or empty) ++ # see + # only first, if multipath is False +- if 'XDG_CONFIG_DIRS' in os.environ: +- path = os.environ['XDG_CONFIG_DIRS'] +- else: +- path = '/etc/xdg' ++ path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' + + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: From f2d29df908f9a6d1a6f82ee6944b845d9f95ae6a Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Wed, 22 Sep 2021 22:35:28 +0100 Subject: [PATCH 02/11] Update pkg_resources patch. --- tools/vendoring/patches/pkg_resources.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/vendoring/patches/pkg_resources.patch b/tools/vendoring/patches/pkg_resources.patch index eea49e475af..6556a860867 100644 --- a/tools/vendoring/patches/pkg_resources.patch +++ b/tools/vendoring/patches/pkg_resources.patch @@ -2,16 +2,16 @@ diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_res index a457ff27e..4cd562cf9 100644 --- a/src/pip/_vendor/pkg_resources/__init__.py +++ b/src/pip/_vendor/pkg_resources/__init__.py -@@ -77,7 +77,7 @@ +@@ -77,7 +77,7 @@ except ImportError: importlib_machinery = None - + from . import py31compat --from pip._vendor import appdirs -+from pip._vendor import platformdirs - from pip._vendor import packaging - __import__('pip._vendor.packaging.version') - __import__('pip._vendor.packaging.specifiers') -@@ -1310,7 +1310,7 @@ +-from pkg_resources.extern import appdirs ++from pkg_resources.extern import platformdirs + from pkg_resources.extern import packaging + __import__('pkg_resources.extern.packaging.version') + __import__('pkg_resources.extern.packaging.specifiers') +@@ -1310,7 +1310,7 @@ def get_default_cache(): """ return ( os.environ.get('PYTHON_EGG_CACHE') From f2ff6effc1a3882dae988acf81416e368bdc2e53 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Wed, 22 Sep 2021 22:35:56 +0100 Subject: [PATCH 03/11] Lint test_appdirs.py --- tests/unit/test_appdirs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index 0cee99dd5de..70887a7032b 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -141,7 +141,7 @@ def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch, platformdir assert appdirs.site_config_dirs("pip") == [ "/Library/Preferences/pip", - "/Library/Application Support/pip" + "/Library/Application Support/pip", ] def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None: @@ -153,7 +153,7 @@ def test_site_config_dirs_linux_override( self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: monkeypatch.setattr(os, "path", posixpath) - monkeypatch.setattr(os, "pathsep", ':') + monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg") assert appdirs.site_config_dirs("pip") == [ @@ -167,9 +167,9 @@ def test_site_config_dirs_linux_empty( self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: monkeypatch.setattr(os, "path", posixpath) - monkeypatch.setattr(os, "pathsep", ':') + monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "") - assert appdirs.site_config_dirs("pip") == ['/etc/xdg/pip', '/etc'] + assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] class TestUserConfigDir: From d031c640eb8b47b88638182473aca2e90798e138 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Wed, 22 Sep 2021 21:43:24 +0100 Subject: [PATCH 04/11] Bump platformdirs version. --- src/pip/_internal/utils/appdirs.py | 7 +- src/pip/_vendor/platformdirs.py | 704 ------------------ .../LICENSE.txt} | 1 - src/pip/_vendor/platformdirs/__init__.py | 313 ++++++++ src/pip/_vendor/platformdirs/__main__.py | 43 ++ src/pip/_vendor/platformdirs/android.py | 94 +++ src/pip/_vendor/platformdirs/api.py | 145 ++++ src/pip/_vendor/platformdirs/macos.py | 57 ++ src/pip/_vendor/platformdirs/py.typed | 0 src/pip/_vendor/platformdirs/unix.py | 144 ++++ src/pip/_vendor/platformdirs/version.py | 4 + src/pip/_vendor/platformdirs/windows.py | 172 +++++ src/pip/_vendor/vendor.txt | 2 +- tests/unit/test_appdirs.py | 37 +- tools/vendoring/patches/platformdirs.patch | 38 +- 15 files changed, 1012 insertions(+), 749 deletions(-) delete mode 100644 src/pip/_vendor/platformdirs.py rename src/pip/_vendor/{platformdirs.LICENSE.txt => platformdirs/LICENSE.txt} (99%) create mode 100644 src/pip/_vendor/platformdirs/__init__.py create mode 100644 src/pip/_vendor/platformdirs/__main__.py create mode 100644 src/pip/_vendor/platformdirs/android.py create mode 100644 src/pip/_vendor/platformdirs/api.py create mode 100644 src/pip/_vendor/platformdirs/macos.py create mode 100644 src/pip/_vendor/platformdirs/py.typed create mode 100644 src/pip/_vendor/platformdirs/unix.py create mode 100644 src/pip/_vendor/platformdirs/version.py create mode 100644 src/pip/_vendor/platformdirs/windows.py diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index eb77104496b..5e334126b61 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -7,6 +7,7 @@ """ import os +import sys from typing import List from pip._vendor import platformdirs as _appdirs @@ -18,7 +19,7 @@ def user_cache_dir(appname: str) -> str: def user_config_dir(appname: str, roaming: bool = True) -> str: path = _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming) - if _appdirs.system == "darwin" and not os.path.isdir(path): + if sys.platform == "darwin" and not os.path.isdir(path): path = os.path.expanduser("~/.config/") if appname: path = os.path.join(path, appname) @@ -29,10 +30,10 @@ def user_config_dir(appname: str, roaming: bool = True) -> str: # see def site_config_dirs(appname: str) -> List[str]: dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True) - if _appdirs.system == "darwin": + if sys.platform == "darwin": # always look in /Library/Application Support/pip as well return dirval.split(os.pathsep) + ["/Library/Application Support/pip"] - elif _appdirs.system == "win32": + elif sys.platform == "win32": return [dirval] else: # always look in /etc directly as well diff --git a/src/pip/_vendor/platformdirs.py b/src/pip/_vendor/platformdirs.py deleted file mode 100644 index 3b8ef8ad8cc..00000000000 --- a/src/pip/_vendor/platformdirs.py +++ /dev/null @@ -1,704 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2005-2010 ActiveState Software Inc. -# Copyright (c) 2013 Eddy Petrișor - -"""Utilities for determining application-specific dirs. - -See for details and usage. -""" -# 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: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - -__version__ = "2.0.2" -__version_info__ = 2, 0, 2 - - -import sys -import os - -PY2 = sys.version_info[0] == 2 - -if not PY2: - unicode = str - -if sys.platform.startswith('java'): - import platform - os_name = platform.java_ver()[3][0] - if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. - system = 'win32' - elif os_name.startswith('Mac'): # "Mac OS X", etc. - system = 'darwin' - else: # "Linux", "SunOS", "FreeBSD", etc. - # Setting this to "linux2" is not ideal, but only Windows or Mac - # are actually checked for and the rest of the module expects - # *sys.platform* style strings. - system = 'linux2' -else: - system = sys.platform - - -# https://docs.python.org/dev/library/sys.html#sys.platform -if system == 'win32': - try: - from ctypes import windll - except ImportError: - try: - import com.sun.jna - except ImportError: - try: - if PY2: - import _winreg as winreg - else: - import winreg - except ImportError: - def _get_win_folder(csidl_name): - """Get folder from environment variables.""" - if csidl_name == 'CSIDL_APPDATA': - env_var_name = 'APPDATA' - elif csidl_name == 'CSIDL_COMMON_APPDATA': - env_var_name = 'ALLUSERSPROFILE' - elif csidl_name == 'CSIDL_LOCAL_APPDATA': - env_var_name = 'LOCALAPPDATA' - else: - raise ValueError('Unknown CSIDL name: {}'.format(csidl_name)) - - if env_var_name in os.environ: - return os.environ[env_var_name] - else: - raise ValueError('Unset environment variable: {}'.format(env_var_name)) - else: - def _get_win_folder(csidl_name): - """Get folder from the registry. - - 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. - """ - if csidl_name == 'CSIDL_APPDATA': - shell_folder_name = 'AppData' - elif csidl_name == 'CSIDL_COMMON_APPDATA': - shell_folder_name = 'Common AppData' - elif csidl_name == 'CSIDL_LOCAL_APPDATA': - shell_folder_name = 'Local AppData' - else: - raise ValueError('Unknown CSIDL name: {}'.format(csidl_name)) - - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' - ) - directory, _ = winreg.QueryValueEx(key, shell_folder_name) - return directory - else: - def _get_win_folder_with_jna(csidl_name): - """Get folder with JNA.""" - import array - from com.sun import jna - from com.sun.jna.platform import win32 - - buf_size = win32.WinDef.MAX_PATH * 2 - buf = array.zeros('c', buf_size) - shell = win32.Shell32.INSTANCE - shell.SHGetFolderPath( - None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf - ) - directory = jna.Native.toString(buf.tostring()).rstrip('\0') - - # Downgrade to short path name if have highbit chars. See - # . - has_high_char = False - for c in directory: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - buf = array.zeros('c', buf_size) - kernel = win32.Kernel32.INSTANCE - if kernel.GetShortPathName(directory, buf, buf_size): - directory = jna.Native.toString(buf.tostring()).rstrip('\0') - - return directory - else: - def _get_win_folder(csidl_name): - """Get folder with ctypes.""" - import ctypes - - if csidl_name == 'CSIDL_APPDATA': - csidl_const = 26 - elif csidl_name == 'CSIDL_COMMON_APPDATA': - csidl_const = 35 - elif csidl_name == 'CSIDL_LOCAL_APPDATA': - csidl_const = 28 - else: - raise ValueError('Unknown CSIDL name: {}'.format(csidl_name)) - - 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 - # . - 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 buf.value - - def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - if appauthor is None: - appauthor = appname - - const = 'CSIDL_APPDATA' if roaming else 'CSIDL_LOCAL_APPDATA' - path = os.path.normpath(_get_win_folder(const)) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - - if version: - path = os.path.join(path, version) - - return path - - def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False): - if appauthor is None: - appauthor = appname - - path = os.path.normpath(_get_win_folder('CSIDL_COMMON_APPDATA')) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - - if version: - path = os.path.join(path, version) - - return path - - def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) - - def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): - return _site_data_dir_impl(appname=appname, appauthor=appauthor, version=version) - - def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True): - if appauthor is None: - appauthor = appname - - path = os.path.normpath(_get_win_folder('CSIDL_LOCAL_APPDATA')) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - - if opinion: - path = os.path.join(path, 'Cache') - - if version: - path = os.path.join(path, version) - - return path - - def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) - - def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True): - path = _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version) - if opinion: - path = os.path.join(path, 'Logs') - - return path - -elif system == 'darwin': - - def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - path = os.path.expanduser('~/Library/Application Support/') - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False): - path = '/Library/Application Support' - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - path = os.path.expanduser('~/Library/Preferences/') - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): - path = '/Library/Preferences' - if appname: - path = os.path.join(path, appname) - - return path - - def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True): - path = os.path.expanduser('~/Library/Caches') - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) - - def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True): - path = os.path.expanduser('~/Library/Logs') - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - -else: - - def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - if 'XDG_DATA_HOME' in os.environ: - path = os.environ['XDG_DATA_HOME'] - else: - path = os.path.expanduser('~/.local/share') - - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False): - # XDG default for $XDG_DATA_DIRS - # only first, if multipath is False - if 'XDG_DATA_DIRS' in os.environ: - path = os.environ['XDG_DATA_DIRS'] - else: - path = '/usr/local/share{}/usr/share'.format(os.pathsep) - - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] - if appname: - if version: - appname = os.path.join(appname, version) - pathlist = [os.path.join(x, appname) for x in pathlist] - - if multipath: - path = os.pathsep.join(pathlist) - else: - path = pathlist[0] - - return path - - def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - if 'XDG_CONFIG_HOME' in os.environ: - path = os.environ['XDG_CONFIG_HOME'] - else: - path = os.path.expanduser('~/.config') - - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): - # XDG default for $XDG_CONFIG_DIRSS (missing or empty) - # see - # only first, if multipath is False - path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' - - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] - if appname: - if version: - appname = os.path.join(appname, version) - pathlist = [os.path.join(x, appname) for x in pathlist] - - if multipath: - path = os.pathsep.join(pathlist) - else: - path = pathlist[0] - - return path - - def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True): - if 'XDG_CACHE_HOME' in os.environ: - path = os.environ['XDG_CACHE_HOME'] - else: - path = os.path.expanduser('~/.cache') - - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False): - if 'XDG_STATE_HOME' in os.environ: - path = os.environ['XDG_STATE_HOME'] - else: - path = os.path.expanduser('~/.local/state') - - if appname: - path = os.path.join(path, appname) - if version: - path = os.path.join(path, version) - - return path - - def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True): - path = _user_cache_dir_impl(appname=appname, appauthor=appauthor, version=version) - if opinion: - path = os.path.join(path, 'log') - - return path - - -def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): - 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. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "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: - Mac OS X: ~/Library/Application Support/ - 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/". - """ - return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) - - -def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): - r"""Return full path to the user-shared data dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "multipath" is an optional parameter only applicable to *nix - which indicates that the entire list of data dirs should be - returned. By default, the first item from XDG_DATA_DIRS is - returned, or '/usr/local/share/', - if XDG_DATA_DIRS is not set - - Typical site data directories are: - Mac OS X: /Library/Application Support/ - Unix: /usr/local/share/ or /usr/share/ - Win XP: C:\Documents and Settings\All Users\Application Data\\ - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) - Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. - - For Unix, this is using the $XDG_DATA_DIRS[0] default. - - WARNING: Do not use this on Windows. See the Vista-Fail note above for why. - """ - return _site_data_dir_impl(appname=appname, appauthor=appauthor, version=version, multipath=multipath) - - -def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): - r"""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. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "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 config directories are: - Mac OS X: ~/Library/Preferences/ - Unix: ~/.config/ # 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 "~/.config/". - """ - return _user_config_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) - - -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. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "multipath" is an optional parameter only applicable to *nix - which indicates that the entire list of config dirs should be - returned. By default, the first item from XDG_CONFIG_DIRS is - returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set - - Typical site config directories are: - Mac OS X: same as site_data_dir - Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in - $XDG_CONFIG_DIRS - Win *: same as site_data_dir - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) - - For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False - - WARNING: Do not use this on Windows. See the Vista-Fail note above for why. - """ - return _site_config_dir_impl(appname=appname, appauthor=appauthor, version=version, multipath=multipath) - - -def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): - r"""Return full path to the user-specific cache dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "opinion" (boolean) can be False to disable the appending of - "Cache" to the base app data dir for Windows. See - discussion below. - - Typical user cache directories are: - Mac OS X: ~/Library/Caches/ - Unix: ~/.cache/ (XDG default) - Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache - Vista: 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` above). 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. - This can be disabled with the `opinion=False` option. - """ - return _user_cache_dir_impl(appname=appname, appauthor=appauthor, version=version, opinion=opinion) - - -def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): - r"""Return full path to the user-specific state dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "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 state directories are: - Mac OS X: same as user_data_dir - Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined - Win *: same as user_data_dir - - For Unix, we follow this Debian proposal - to extend the XDG spec and support $XDG_STATE_HOME. - - That means, by default "~/.local/state/". - """ - return _user_state_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming) - - -def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): - r"""Return full path to the user-specific log dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "opinion" (boolean) can be False to disable the appending of - "Logs" to the base app data dir for Windows, and "log" to the - base cache dir for Unix. See discussion below. - - Typical user log directories are: - Mac OS X: ~/Library/Logs/ - Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined - Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs - Vista: C:\Users\\AppData\Local\\\Logs - - On Windows the only suggestion in the MSDN docs is that local settings - go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in - examples of what some windows apps use for a logs dir.) - - OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` - value for Windows and appends "log" to the user cache dir for Unix. - This can be disabled with the `opinion=False` option. - """ - return _user_log_dir_impl(appname=appname, appauthor=appauthor, version=version, opinion=opinion) - - -class PlatformDirs(object): - """Convenience wrapper for getting application dirs.""" - def __init__(self, appname=None, appauthor=None, version=None, - roaming=False, multipath=False): - self.appname = appname - self.appauthor = appauthor - self.version = version - self.roaming = roaming - self.multipath = multipath - - @property - def user_data_dir(self): - return user_data_dir(self.appname, self.appauthor, - version=self.version, roaming=self.roaming) - - @property - def site_data_dir(self): - return site_data_dir(self.appname, self.appauthor, - version=self.version, multipath=self.multipath) - - @property - def user_config_dir(self): - return user_config_dir(self.appname, self.appauthor, - version=self.version, roaming=self.roaming) - - @property - def site_config_dir(self): - return site_config_dir(self.appname, self.appauthor, - version=self.version, multipath=self.multipath) - - @property - def user_cache_dir(self): - return user_cache_dir(self.appname, self.appauthor, - version=self.version) - - @property - def user_state_dir(self): - return user_state_dir(self.appname, self.appauthor, - version=self.version) - - @property - def user_log_dir(self): - return user_log_dir(self.appname, self.appauthor, - version=self.version) - - -# Backwards compatibility with appdirs -AppDirs = PlatformDirs - - -if __name__ == "__main__": - # ---- self test code - appname = "MyApp" - appauthor = "MyCompany" - - props = ("user_data_dir", - "user_config_dir", - "user_cache_dir", - "user_state_dir", - "user_log_dir", - "site_data_dir", - "site_config_dir") - - print("-- app dirs %s --" % __version__) - - print("-- app dirs (with optional 'version')") - dirs = PlatformDirs(appname, appauthor, version="1.0") - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (without optional 'version')") - dirs = PlatformDirs(appname, appauthor) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (without optional 'appauthor')") - dirs = PlatformDirs(appname) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (with disabled 'appauthor')") - dirs = PlatformDirs(appname, appauthor=False) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/src/pip/_vendor/platformdirs.LICENSE.txt b/src/pip/_vendor/platformdirs/LICENSE.txt similarity index 99% rename from src/pip/_vendor/platformdirs.LICENSE.txt rename to src/pip/_vendor/platformdirs/LICENSE.txt index 107c61405e3..f0bbd69f0c8 100644 --- a/src/pip/_vendor/platformdirs.LICENSE.txt +++ b/src/pip/_vendor/platformdirs/LICENSE.txt @@ -20,4 +20,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/src/pip/_vendor/platformdirs/__init__.py b/src/pip/_vendor/platformdirs/__init__.py new file mode 100644 index 00000000000..07baa5b1d05 --- /dev/null +++ b/src/pip/_vendor/platformdirs/__init__.py @@ -0,0 +1,313 @@ +""" +Utilities for determining application-specific dirs. See for details and +usage. +""" +import importlib +import os +import sys +from pathlib import Path +from typing import TYPE_CHECKING, Optional, Type, Union + +if TYPE_CHECKING: + from typing_extensions import Literal # pragma: no cover + +from .api import PlatformDirsABC +from .version import __version__, __version_info__ + + +def _set_platform_dir_class() -> Type[PlatformDirsABC]: + if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system": + module, name = "pip._vendor.platformdirs.android", "Android" + elif sys.platform == "win32": + module, name = "pip._vendor.platformdirs.windows", "Windows" + elif sys.platform == "darwin": + module, name = "pip._vendor.platformdirs.macos", "MacOS" + else: + module, name = "pip._vendor.platformdirs.unix", "Unix" + result: Type[PlatformDirsABC] = getattr(importlib.import_module(module), name) + return result + + +PlatformDirs = _set_platform_dir_class() #: Currently active platform +AppDirs = PlatformDirs #: Backwards compatibility with appdirs + + +def user_data_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: data directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_dir + + +def site_data_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `roaming `. + :returns: data directory shared by users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_dir + + +def user_config_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: config directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_dir + + +def site_config_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `roaming `. + :returns: config directory shared by the users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_dir + + +def user_cache_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: cache directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_dir + + +def user_state_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: state directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_dir + + +def user_log_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: log directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_dir + + +def user_runtime_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `opinion `. + :returns: runtime directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_dir + + +def user_data_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: data path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_path + + +def site_data_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `multipath `. + :returns: data path shared by users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_path + + +def user_config_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: config path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_path + + +def site_config_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `roaming `. + :returns: config path shared by the users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_path + + +def user_cache_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: cache path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_path + + +def user_state_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: state path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_path + + +def user_log_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: log path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_path + + +def user_runtime_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `opinion `. + :returns: runtime path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_path + + +__all__ = [ + "__version__", + "__version_info__", + "PlatformDirs", + "AppDirs", + "PlatformDirsABC", + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "user_runtime_dir", + "site_data_dir", + "site_config_dir", + "user_data_path", + "user_config_path", + "user_cache_path", + "user_state_path", + "user_log_path", + "user_runtime_path", + "site_data_path", + "site_config_path", +] diff --git a/src/pip/_vendor/platformdirs/__main__.py b/src/pip/_vendor/platformdirs/__main__.py new file mode 100644 index 00000000000..48f3be39c68 --- /dev/null +++ b/src/pip/_vendor/platformdirs/__main__.py @@ -0,0 +1,43 @@ +from pip._vendor.platformdirs import PlatformDirs, __version__ + +PROPS = ( + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "user_runtime_dir", + "site_data_dir", + "site_config_dir", +) + + +def main() -> None: + app_name = "MyApp" + app_author = "MyCompany" + + print(f"-- platformdirs {__version__} --") + + print("-- app dirs (with optional 'version')") + dirs = PlatformDirs(app_name, app_author, version="1.0") + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (without optional 'version')") + dirs = PlatformDirs(app_name, app_author) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (without optional 'appauthor')") + dirs = PlatformDirs(app_name) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = PlatformDirs(app_name, appauthor=False) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + +if __name__ == "__main__": + main() diff --git a/src/pip/_vendor/platformdirs/android.py b/src/pip/_vendor/platformdirs/android.py new file mode 100644 index 00000000000..22675592ca7 --- /dev/null +++ b/src/pip/_vendor/platformdirs/android.py @@ -0,0 +1,94 @@ +import os +import re +import sys +from functools import lru_cache + +from .api import PlatformDirsABC + + +class Android(PlatformDirsABC): + """ + Follows the guidance `from here `_. Makes use of the + `appname ` and + `version `. + """ + + @property + def user_data_dir(self) -> str: + """:return: data directory tied to the user, e.g. ``/data/user///files/``""" + return self._append_app_name_and_version(_android_folder(), "files") + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_config_dir(self) -> str: + """ + :return: config directory tied to the user, e.g. ``/data/user///shared_prefs/`` + """ + return self._append_app_name_and_version(_android_folder(), "shared_prefs") + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, same as `user_config_dir`""" + return self.user_config_dir + + @property + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user, e.g. e.g. ``/data/user///cache/``""" + return self._append_app_name_and_version(_android_folder(), "cache") + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it, + e.g. ``/data/user///cache//log`` + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "log") + return path + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, + e.g. ``/data/user///cache//tmp`` + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "tmp") + return path + + +@lru_cache(maxsize=1) +def _android_folder() -> str: + """:return: base folder for the Android OS""" + try: + # First try to get path to android app via pyjnius + from jnius import autoclass # noqa: SC200 + + Context = autoclass("android.content.Context") # noqa: SC200 + result: str = Context.getFilesDir().getParentFile().getAbsolutePath() + except Exception: + # if fails find an android folder looking path on the sys.path + pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files") + for path in sys.path: + if pattern.match(path): + result = path.split("/files")[0] + break + else: + raise OSError("Cannot find path to android app folder") + return result + + +__all__ = [ + "Android", +] diff --git a/src/pip/_vendor/platformdirs/api.py b/src/pip/_vendor/platformdirs/api.py new file mode 100644 index 00000000000..43804ce9c7a --- /dev/null +++ b/src/pip/_vendor/platformdirs/api.py @@ -0,0 +1,145 @@ +import os +import sys +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional, Union + +if sys.version_info >= (3, 8): # pragma: no branch + from typing import Literal # pragma: no cover + + +class PlatformDirsABC(ABC): + """ + Abstract base class for platform directories. + """ + + def __init__( + self, + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, + multipath: bool = False, + opinion: bool = True, + ): + """ + Create a new platform directory. + + :param appname: See `appname`. + :param appauthor: See `appauthor`. + :param version: See `version`. + :param roaming: See `roaming`. + :param multipath: See `multipath`. + :param opinion: See `opinion`. + """ + self.appname = appname #: The name of application. + self.appauthor = appauthor + """ + The name of the app author or distributing body for this application. Typically, it is the owning company name. + Defaults to `appname`. You may pass ``False`` to disable it. + """ + self.version = version + """ + An optional version path element to append to the path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this would typically be ``.``. + """ + self.roaming = roaming + """ + Whether to use the roaming appdata directory on Windows. That means that for users on a Windows network setup + for roaming profiles, this user data will be synced on login (see + `here `_). + """ + self.multipath = multipath + """ + An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be + returned. By default, the first item would only be returned. + """ + self.opinion = opinion #: A flag to indicating to use opinionated values. + + def _append_app_name_and_version(self, *base: str) -> str: + params = list(base[1:]) + if self.appname: + params.append(self.appname) + if self.version: + params.append(self.version) + return os.path.join(base[0], *params) + + @property + @abstractmethod + def user_data_dir(self) -> str: + """:return: data directory tied to the user""" + + @property + @abstractmethod + def site_data_dir(self) -> str: + """:return: data directory shared by users""" + + @property + @abstractmethod + def user_config_dir(self) -> str: + """:return: config directory tied to the user""" + + @property + @abstractmethod + def site_config_dir(self) -> str: + """:return: config directory shared by the users""" + + @property + @abstractmethod + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user""" + + @property + @abstractmethod + def user_state_dir(self) -> str: + """:return: state directory tied to the user""" + + @property + @abstractmethod + def user_log_dir(self) -> str: + """:return: log directory tied to the user""" + + @property + @abstractmethod + def user_runtime_dir(self) -> str: + """:return: runtime directory tied to the user""" + + @property + def user_data_path(self) -> Path: + """:return: data path tied to the user""" + return Path(self.user_data_dir) + + @property + def site_data_path(self) -> Path: + """:return: data path shared by users""" + return Path(self.site_data_dir) + + @property + def user_config_path(self) -> Path: + """:return: config path tied to the user""" + return Path(self.user_config_dir) + + @property + def site_config_path(self) -> Path: + """:return: config path shared by the users""" + return Path(self.site_config_dir) + + @property + def user_cache_path(self) -> Path: + """:return: cache path tied to the user""" + return Path(self.user_cache_dir) + + @property + def user_state_path(self) -> Path: + """:return: state path tied to the user""" + return Path(self.user_state_dir) + + @property + def user_log_path(self) -> Path: + """:return: log path tied to the user""" + return Path(self.user_log_dir) + + @property + def user_runtime_path(self) -> Path: + """:return: runtime path tied to the user""" + return Path(self.user_runtime_dir) diff --git a/src/pip/_vendor/platformdirs/macos.py b/src/pip/_vendor/platformdirs/macos.py new file mode 100644 index 00000000000..acd562ba305 --- /dev/null +++ b/src/pip/_vendor/platformdirs/macos.py @@ -0,0 +1,57 @@ +import os + +from .api import PlatformDirsABC + + +class MacOS(PlatformDirsABC): + """ + Platform directories for the macOS operating system. Follows the guidance from `Apple documentation + `_. + Makes use of the `appname ` and + `version `. + """ + + @property + def user_data_dir(self) -> str: + """:return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support/")) + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``""" + return self._append_app_name_and_version("/Library/Application Support") + + @property + def user_config_dir(self) -> str: + """:return: config directory tied to the user, e.g. ``~/Library/Preferences/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Preferences/")) + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, e.g. ``/Library/Preferences/$appname``""" + return self._append_app_name_and_version("/Library/Preferences") + + @property + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches")) + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """:return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) + + @property + def user_runtime_dir(self) -> str: + """:return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) + + +__all__ = [ + "MacOS", +] diff --git a/src/pip/_vendor/platformdirs/py.typed b/src/pip/_vendor/platformdirs/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pip/_vendor/platformdirs/unix.py b/src/pip/_vendor/platformdirs/unix.py new file mode 100644 index 00000000000..9f3fe7bc43d --- /dev/null +++ b/src/pip/_vendor/platformdirs/unix.py @@ -0,0 +1,144 @@ +import os +import sys +from pathlib import Path + +from .api import PlatformDirsABC + +if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker + from os import getuid +else: + + def getuid() -> int: + raise RuntimeError("should only be used on Linux") + + +class Unix(PlatformDirsABC): + """ + On Unix/Linux, we follow the + `XDG Basedir Spec `_. The spec allows + overriding directories with environment variables. The examples show are the default values, alongside the name of + the environment variable that overrides them. Makes use of the + `appname `, + `version `, + `multipath `, + `opinion `. + """ + + @property + def user_data_dir(self) -> str: + """ + :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or + ``$XDG_DATA_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_DATA_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/share") + return self._append_app_name_and_version(path) + + @property + def site_data_dir(self) -> str: + """ + :return: data directories shared by users (if `multipath ` is + enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS + path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` + """ + # XDG default for $XDG_DATA_DIRS; only first, if multipath is False + path = os.environ.get("XDG_DATA_DIRS", "") + if not path.strip(): + path = f"/usr/local/share{os.pathsep}/usr/share" + return self._with_multi_path(path) + + def _with_multi_path(self, path: str) -> str: + path_list = path.split(os.pathsep) + if not self.multipath: + path_list = path_list[0:1] + path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list] + return os.pathsep.join(path_list) + + @property + def user_config_dir(self) -> str: + """ + :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or + ``$XDG_CONFIG_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_CONFIG_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.config") + return self._append_app_name_and_version(path) + + @property + def site_config_dir(self) -> str: + """ + :return: config directories shared by users (if `multipath ` + is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS + path separator), e.g. ``/etc/xdg/$appname/$version`` + """ + # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False + path = os.environ.get("XDG_CONFIG_DIRS", "") + if not path.strip(): + path = "/etc/xdg" + return self._with_multi_path(path) + + @property + def user_cache_dir(self) -> str: + """ + :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or + ``~/$XDG_CACHE_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_CACHE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.cache") + return self._append_app_name_and_version(path) + + @property + def user_state_dir(self) -> str: + """ + :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or + ``$XDG_STATE_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_STATE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/state") + return self._append_app_name_and_version(path) + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``log`` in it + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "log") + return path + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or + ``$XDG_RUNTIME_DIR/$appname/$version`` + """ + path = os.environ.get("XDG_RUNTIME_DIR", "") + if not path.strip(): + path = f"/run/user/{getuid()}" + return self._append_app_name_and_version(path) + + @property + def site_data_path(self) -> Path: + """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``""" + return self._first_item_as_path_if_multipath(self.site_data_dir) + + @property + def site_config_path(self) -> Path: + """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``""" + return self._first_item_as_path_if_multipath(self.site_config_dir) + + def _first_item_as_path_if_multipath(self, directory: str) -> Path: + if self.multipath: + # If multipath is True, the first path is returned. + directory = directory.split(os.pathsep)[0] + return Path(directory) + + +__all__ = [ + "Unix", +] diff --git a/src/pip/_vendor/platformdirs/version.py b/src/pip/_vendor/platformdirs/version.py new file mode 100644 index 00000000000..0d7379d32e8 --- /dev/null +++ b/src/pip/_vendor/platformdirs/version.py @@ -0,0 +1,4 @@ +""" Version information """ + +__version__ = "2.3.0" +__version_info__ = (2, 3, 0) diff --git a/src/pip/_vendor/platformdirs/windows.py b/src/pip/_vendor/platformdirs/windows.py new file mode 100644 index 00000000000..2944d0e9c83 --- /dev/null +++ b/src/pip/_vendor/platformdirs/windows.py @@ -0,0 +1,172 @@ +import ctypes +import os +from functools import lru_cache +from typing import Callable, Optional + +from .api import PlatformDirsABC + + +class Windows(PlatformDirsABC): + """`MSDN on where to store app data files + `_. + Makes use of the + `appname `, + `appauthor `, + `version `, + `roaming `, + `opinion `.""" + + @property + def user_data_dir(self) -> str: + """ + :return: data directory tied to the user, e.g. + ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not roaming) or + ``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming) + """ + const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(get_win_folder(const)) + return self._append_parts(path) + + def _append_parts(self, path: str, *, opinion_value: Optional[str] = None) -> str: + params = [] + if self.appname: + if self.appauthor is not False: + author = self.appauthor or self.appname + params.append(author) + params.append(self.appname) + if opinion_value is not None and self.opinion: + params.append(opinion_value) + if self.version: + params.append(self.version) + return os.path.join(path, *params) + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname``""" + path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA")) + return self._append_parts(path) + + @property + def user_config_dir(self) -> str: + """:return: config directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, same as `site_data_dir`""" + return self.site_data_dir + + @property + def user_cache_dir(self) -> str: + """ + :return: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g. + ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version`` + """ + path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA")) + return self._append_parts(path, opinion_value="Cache") + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it + """ + path = self.user_data_dir + if self.opinion: + path = os.path.join(path, "Logs") + return path + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, e.g. + ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname`` + """ + path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) + return self._append_parts(path) + + +def get_win_folder_from_env_vars(csidl_name: str) -> str: + """Get folder from environment variables.""" + env_var_name = { + "CSIDL_APPDATA": "APPDATA", + "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE", + "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA", + }.get(csidl_name) + if env_var_name is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + result = os.environ.get(env_var_name) + if result is None: + raise ValueError(f"Unset environment variable: {env_var_name}") + return result + + +def get_win_folder_from_registry(csidl_name: str) -> str: + """Get folder from the registry. + + 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. + """ + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }.get(csidl_name) + if shell_folder_name is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + + import winreg + + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") + directory, _ = winreg.QueryValueEx(key, shell_folder_name) + return str(directory) + + +def get_win_folder_via_ctypes(csidl_name: str) -> str: + """Get folder with ctypes.""" + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }.get(csidl_name) + if csidl_const is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + + buf = ctypes.create_unicode_buffer(1024) + windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker + windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + has_high_char = False # Downgrade to short path name if it has highbit chars. + 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 + + return buf.value + + +def _pick_get_win_folder() -> Callable[[str], str]: + if hasattr(ctypes, "windll"): + return get_win_folder_via_ctypes + try: + import winreg # noqa: F401 + except ImportError: + return get_win_folder_from_env_vars + else: + return get_win_folder_from_registry + + +get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder()) + +__all__ = [ + "Windows", +] diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index a0402fab83e..2b0306f443e 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -6,7 +6,7 @@ html5lib==1.1 msgpack==1.0.2 packaging==21.0 pep517==0.11.0 -platformdirs==2.0.2 +platformdirs==2.3.0 progress==1.5 pyparsing==2.4.7 requests==2.26.0 diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index 70887a7032b..fbf9a90a5e9 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -1,4 +1,3 @@ -import importlib import ntpath import os import posixpath @@ -18,12 +17,10 @@ def platformdirs_win32(monkeypatch): with monkeypatch.context() as m: m.setattr(sys, "platform", "win32") m.setattr(os, "path", ntpath) - importlib.reload(platformdirs) - importlib.reload(appdirs) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() yield - importlib.reload(platformdirs) - importlib.reload(appdirs) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() @pytest.fixture() @@ -33,12 +30,10 @@ def platformdirs_darwin(monkeypatch): with monkeypatch.context() as m: m.setattr(sys, "platform", "darwin") m.setattr(os, "path", posixpath) - importlib.reload(platformdirs) - importlib.reload(appdirs) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() yield - importlib.reload(platformdirs) - importlib.reload(appdirs) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() @pytest.fixture() @@ -48,12 +43,10 @@ def platformdirs_linux(monkeypatch): with monkeypatch.context() as m: m.setattr(sys, "platform", "linux") m.setattr(os, "path", posixpath) - importlib.reload(platformdirs) - importlib.reload(appdirs) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() yield - importlib.reload(platformdirs) - importlib.reload(appdirs) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() class TestUserCacheDir: @@ -61,8 +54,8 @@ def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_ _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - platformdirs, - "_get_win_folder", + platformdirs.windows, + "get_win_folder", _get_win_folder, raising=False, ) @@ -108,7 +101,7 @@ def test_user_cache_dir_unicode(self, monkeypatch: pytest.MonkeyPatch) -> None: def my_get_win_folder(csidl_name): return "\u00DF\u00E4\u03B1\u20AC" - monkeypatch.setattr(platformdirs, "_get_win_folder", my_get_win_folder) + monkeypatch.setattr(platformdirs.windows, "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 @@ -127,8 +120,8 @@ def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch, platformdir _get_win_folder = mock.Mock(return_value="C:\\ProgramData") monkeypatch.setattr( - platformdirs, - "_get_win_folder", + platformdirs.windows, + "get_win_folder", _get_win_folder, raising=False, ) @@ -179,8 +172,8 @@ def test_user_config_dir_win_no_roaming( _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - platformdirs, - "_get_win_folder", + platformdirs.windows, + "get_win_folder", _get_win_folder, raising=False, ) @@ -197,8 +190,8 @@ def test_user_config_dir_win_yes_roaming( _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Roaming") monkeypatch.setattr( - platformdirs, - "_get_win_folder", + platformdirs.windows, + "get_win_folder", _get_win_folder, raising=False, ) diff --git a/tools/vendoring/patches/platformdirs.patch b/tools/vendoring/patches/platformdirs.patch index 00831244191..4f27b743dde 100644 --- a/tools/vendoring/patches/platformdirs.patch +++ b/tools/vendoring/patches/platformdirs.patch @@ -1,20 +1,22 @@ -diff --git a/src/pip/_vendor/platformdirs.py b/src/pip/_vendor/platformdirs.py -index 23c6af8c7..3b8ef8ad8 100644 ---- a/src/pip/_vendor/platformdirs.py -+++ b/src/pip/_vendor/platformdirs.py -@@ -327,12 +327,10 @@ else: - return path +diff --git a/src/pip/_vendor/platformdirs/__init__.py b/src/pip/_vendor/platformdirs/__init__.py +index 693b64843..07baa5b1d 100644 +--- a/src/pip/_vendor/platformdirs/__init__.py ++++ b/src/pip/_vendor/platformdirs/__init__.py +@@ -17,13 +17,13 @@ from .version import __version__, __version_info__ - def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False): -- # XDG default for $XDG_CONFIG_DIRS -+ # XDG default for $XDG_CONFIG_DIRSS (missing or empty) -+ # see - # only first, if multipath is False -- if 'XDG_CONFIG_DIRS' in os.environ: -- path = os.environ['XDG_CONFIG_DIRS'] -- else: -- path = '/etc/xdg' -+ path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' + def _set_platform_dir_class() -> Type[PlatformDirsABC]: + if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system": +- module, name = "platformdirs.android", "Android" ++ module, name = "pip._vendor.platformdirs.android", "Android" + elif sys.platform == "win32": +- module, name = "platformdirs.windows", "Windows" ++ module, name = "pip._vendor.platformdirs.windows", "Windows" + elif sys.platform == "darwin": +- module, name = "platformdirs.macos", "MacOS" ++ module, name = "pip._vendor.platformdirs.macos", "MacOS" + else: +- module, name = "platformdirs.unix", "Unix" ++ module, name = "pip._vendor.platformdirs.unix", "Unix" + result: Type[PlatformDirsABC] = getattr(importlib.import_module(module), name) + return result - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] - if appname: From e498cee6cdf2c6d82431b86bf11a165623ab1ae2 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Thu, 23 Sep 2021 08:29:10 +0100 Subject: [PATCH 05/11] Downgrade platformdirs to 2.2.0 --- src/pip/_vendor/platformdirs/__init__.py | 34 ------------------------ src/pip/_vendor/platformdirs/__main__.py | 1 - src/pip/_vendor/platformdirs/android.py | 11 -------- src/pip/_vendor/platformdirs/api.py | 10 ------- src/pip/_vendor/platformdirs/macos.py | 5 ---- src/pip/_vendor/platformdirs/unix.py | 19 ------------- src/pip/_vendor/platformdirs/version.py | 4 +-- src/pip/_vendor/platformdirs/windows.py | 9 ------- src/pip/_vendor/vendor.txt | 2 +- 9 files changed, 3 insertions(+), 92 deletions(-) diff --git a/src/pip/_vendor/platformdirs/__init__.py b/src/pip/_vendor/platformdirs/__init__.py index 07baa5b1d05..ed51f27e6d8 100644 --- a/src/pip/_vendor/platformdirs/__init__.py +++ b/src/pip/_vendor/platformdirs/__init__.py @@ -144,22 +144,6 @@ def user_log_dir( return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_dir -def user_runtime_dir( - appname: Optional[str] = None, - appauthor: Union[str, None, "Literal[False]"] = None, - version: Optional[str] = None, - opinion: bool = True, -) -> str: - """ - :param appname: See `appname `. - :param appauthor: See `appauthor `. - :param version: See `version `. - :param opinion: See `opinion `. - :returns: runtime directory tied to the user - """ - return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_dir - - def user_data_path( appname: Optional[str] = None, appauthor: Union[str, None, "Literal[False]"] = None, @@ -272,22 +256,6 @@ def user_log_path( return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_path -def user_runtime_path( - appname: Optional[str] = None, - appauthor: Union[str, None, "Literal[False]"] = None, - version: Optional[str] = None, - opinion: bool = True, -) -> Path: - """ - :param appname: See `appname `. - :param appauthor: See `appauthor `. - :param version: See `version `. - :param opinion: See `opinion `. - :returns: runtime path tied to the user - """ - return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_path - - __all__ = [ "__version__", "__version_info__", @@ -299,7 +267,6 @@ def user_runtime_path( "user_cache_dir", "user_state_dir", "user_log_dir", - "user_runtime_dir", "site_data_dir", "site_config_dir", "user_data_path", @@ -307,7 +274,6 @@ def user_runtime_path( "user_cache_path", "user_state_path", "user_log_path", - "user_runtime_path", "site_data_path", "site_config_path", ] diff --git a/src/pip/_vendor/platformdirs/__main__.py b/src/pip/_vendor/platformdirs/__main__.py index 48f3be39c68..97f9de5dd4e 100644 --- a/src/pip/_vendor/platformdirs/__main__.py +++ b/src/pip/_vendor/platformdirs/__main__.py @@ -6,7 +6,6 @@ "user_cache_dir", "user_state_dir", "user_log_dir", - "user_runtime_dir", "site_data_dir", "site_config_dir", ) diff --git a/src/pip/_vendor/platformdirs/android.py b/src/pip/_vendor/platformdirs/android.py index 22675592ca7..c8c6419460d 100644 --- a/src/pip/_vendor/platformdirs/android.py +++ b/src/pip/_vendor/platformdirs/android.py @@ -56,17 +56,6 @@ def user_log_dir(self) -> str: path = os.path.join(path, "log") return path - @property - def user_runtime_dir(self) -> str: - """ - :return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, - e.g. ``/data/user///cache//tmp`` - """ - path = self.user_cache_dir - if self.opinion: - path = os.path.join(path, "tmp") - return path - @lru_cache(maxsize=1) def _android_folder() -> str: diff --git a/src/pip/_vendor/platformdirs/api.py b/src/pip/_vendor/platformdirs/api.py index 43804ce9c7a..7b29173452f 100644 --- a/src/pip/_vendor/platformdirs/api.py +++ b/src/pip/_vendor/platformdirs/api.py @@ -99,11 +99,6 @@ def user_state_dir(self) -> str: def user_log_dir(self) -> str: """:return: log directory tied to the user""" - @property - @abstractmethod - def user_runtime_dir(self) -> str: - """:return: runtime directory tied to the user""" - @property def user_data_path(self) -> Path: """:return: data path tied to the user""" @@ -138,8 +133,3 @@ def user_state_path(self) -> Path: def user_log_path(self) -> Path: """:return: log path tied to the user""" return Path(self.user_log_dir) - - @property - def user_runtime_path(self) -> Path: - """:return: runtime path tied to the user""" - return Path(self.user_runtime_dir) diff --git a/src/pip/_vendor/platformdirs/macos.py b/src/pip/_vendor/platformdirs/macos.py index acd562ba305..4d4d5b02c27 100644 --- a/src/pip/_vendor/platformdirs/macos.py +++ b/src/pip/_vendor/platformdirs/macos.py @@ -46,11 +46,6 @@ def user_log_dir(self) -> str: """:return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``""" return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) - @property - def user_runtime_dir(self) -> str: - """:return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``""" - return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) - __all__ = [ "MacOS", diff --git a/src/pip/_vendor/platformdirs/unix.py b/src/pip/_vendor/platformdirs/unix.py index 9f3fe7bc43d..3f6afec5a6d 100644 --- a/src/pip/_vendor/platformdirs/unix.py +++ b/src/pip/_vendor/platformdirs/unix.py @@ -1,16 +1,8 @@ import os -import sys from pathlib import Path from .api import PlatformDirsABC -if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker - from os import getuid -else: - - def getuid() -> int: - raise RuntimeError("should only be used on Linux") - class Unix(PlatformDirsABC): """ @@ -111,17 +103,6 @@ def user_log_dir(self) -> str: path = os.path.join(path, "log") return path - @property - def user_runtime_dir(self) -> str: - """ - :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or - ``$XDG_RUNTIME_DIR/$appname/$version`` - """ - path = os.environ.get("XDG_RUNTIME_DIR", "") - if not path.strip(): - path = f"/run/user/{getuid()}" - return self._append_app_name_and_version(path) - @property def site_data_path(self) -> Path: """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``""" diff --git a/src/pip/_vendor/platformdirs/version.py b/src/pip/_vendor/platformdirs/version.py index 0d7379d32e8..2b18c715b52 100644 --- a/src/pip/_vendor/platformdirs/version.py +++ b/src/pip/_vendor/platformdirs/version.py @@ -1,4 +1,4 @@ """ Version information """ -__version__ = "2.3.0" -__version_info__ = (2, 3, 0) +__version__ = "2.2.0" +__version_info__ = (2, 2, 0) diff --git a/src/pip/_vendor/platformdirs/windows.py b/src/pip/_vendor/platformdirs/windows.py index 2944d0e9c83..274a8c89e85 100644 --- a/src/pip/_vendor/platformdirs/windows.py +++ b/src/pip/_vendor/platformdirs/windows.py @@ -80,15 +80,6 @@ def user_log_dir(self) -> str: path = os.path.join(path, "Logs") return path - @property - def user_runtime_dir(self) -> str: - """ - :return: runtime directory tied to the user, e.g. - ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname`` - """ - path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) - return self._append_parts(path) - def get_win_folder_from_env_vars(csidl_name: str) -> str: """Get folder from environment variables.""" diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index 2b0306f443e..29fbff8c357 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -6,7 +6,7 @@ html5lib==1.1 msgpack==1.0.2 packaging==21.0 pep517==0.11.0 -platformdirs==2.3.0 +platformdirs==2.2.0 progress==1.5 pyparsing==2.4.7 requests==2.26.0 From 147e8a8a55d92e29c9a1799a10c29411b12eb509 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Thu, 23 Sep 2021 21:15:40 +0100 Subject: [PATCH 06/11] Remove duplicate monkeypatches --- tests/unit/test_appdirs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index fbf9a90a5e9..93285b351f8 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -145,7 +145,6 @@ def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch, platformd def test_site_config_dirs_linux_override( self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg") @@ -159,7 +158,6 @@ def test_site_config_dirs_linux_override( def test_site_config_dirs_linux_empty( self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux ) -> None: - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "") assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] From 8de9e0a96ff477752c23f04380622d16c901752f Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Fri, 24 Sep 2021 19:23:46 +0100 Subject: [PATCH 07/11] Add note to _vendor/README.rst about pkg_resources patch. --- src/pip/_vendor/README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pip/_vendor/README.rst b/src/pip/_vendor/README.rst index 12b421a0417..9fd48210577 100644 --- a/src/pip/_vendor/README.rst +++ b/src/pip/_vendor/README.rst @@ -100,7 +100,8 @@ Modifications * ``setuptools`` is completely stripped to only keep ``pkg_resources``. * ``pkg_resources`` has been modified to import its dependencies from - ``pip._vendor``. + ``pip._vendor``, and to use the vendored copy of ``platformdirs`` + rather than ``appdirs`. * ``packaging`` has been modified to import its dependencies from ``pip._vendor``. * ``html5lib`` has been modified to import six from ``pip._vendor``, to prefer From a36a2c58e54025b26f9e997c38ca0363e0b02397 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Fri, 24 Sep 2021 19:24:43 +0100 Subject: [PATCH 08/11] Remove "platformdirs" from tool.vendoring.typing-stubs --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ec4d1421ab..fac27944798 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,6 @@ drop = [ [tool.vendoring.typing-stubs] six = ["six.__init__", "six.moves.__init__", "six.moves.configparser"] distro = [] -platformdirs = [] [tool.vendoring.license.directories] setuptools = "pkg_resources" From ea8aad79499788d347663b815eed6693f522a048 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Fri, 24 Sep 2021 21:52:34 +0100 Subject: [PATCH 09/11] Update src/pip/_vendor/README.rst Co-authored-by: Pradyun Gedam --- src/pip/_vendor/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_vendor/README.rst b/src/pip/_vendor/README.rst index 9fd48210577..9834e246315 100644 --- a/src/pip/_vendor/README.rst +++ b/src/pip/_vendor/README.rst @@ -101,7 +101,7 @@ Modifications * ``setuptools`` is completely stripped to only keep ``pkg_resources``. * ``pkg_resources`` has been modified to import its dependencies from ``pip._vendor``, and to use the vendored copy of ``platformdirs`` - rather than ``appdirs`. + rather than ``appdirs``. * ``packaging`` has been modified to import its dependencies from ``pip._vendor``. * ``html5lib`` has been modified to import six from ``pip._vendor``, to prefer From 188266b5a3c0400b6e8635be437f56c26c4e2567 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Tue, 28 Sep 2021 10:18:51 +0100 Subject: [PATCH 10/11] Add news entries. --- news/10202.feature.rst | 1 + news/pkg_resources.vendor.rst | 1 + news/platformdirs.vendor.rst | 1 + src/pip/_vendor/README.rst | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 news/10202.feature.rst create mode 100644 news/pkg_resources.vendor.rst create mode 100644 news/platformdirs.vendor.rst diff --git a/news/10202.feature.rst b/news/10202.feature.rst new file mode 100644 index 00000000000..abfb191ec17 --- /dev/null +++ b/news/10202.feature.rst @@ -0,0 +1 @@ +Replace vendored appdirs with platformdirs. diff --git a/news/pkg_resources.vendor.rst b/news/pkg_resources.vendor.rst new file mode 100644 index 00000000000..05cb4d0eea6 --- /dev/null +++ b/news/pkg_resources.vendor.rst @@ -0,0 +1 @@ +Patch pkg_resources to use platformdirs rather than appdirs. diff --git a/news/platformdirs.vendor.rst b/news/platformdirs.vendor.rst new file mode 100644 index 00000000000..1d7980bdc67 --- /dev/null +++ b/news/platformdirs.vendor.rst @@ -0,0 +1 @@ +Patch platformdirs import its submodules from ``pip._vendor.platformdirs``. diff --git a/src/pip/_vendor/README.rst b/src/pip/_vendor/README.rst index 9834e246315..26904ca251a 100644 --- a/src/pip/_vendor/README.rst +++ b/src/pip/_vendor/README.rst @@ -112,7 +112,7 @@ Modifications * ``requests`` has been modified to import its other dependencies from ``pip._vendor`` and to *not* load ``simplejson`` (all platforms) and ``pyopenssl`` (Windows). - +* ``platformdirs`` has been modified to import its submodules from ``pip._vendor.platformdirs``. Automatic Vendoring =================== From 78b8b8b5d28d3783403584bf31debbb9d9298bf9 Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Thu, 30 Sep 2021 15:53:38 +0100 Subject: [PATCH 11/11] Reformat test_appdirs.py, add type annotations, and switch to `@pytest.mark.usefixtures` --- tests/unit/test_appdirs.py | 66 +++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index 93285b351f8..e24523d8d08 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -2,6 +2,7 @@ import os import posixpath import sys +from typing import Generator from unittest import mock import pytest @@ -11,7 +12,8 @@ @pytest.fixture() -def platformdirs_win32(monkeypatch): +def platformdirs_win32(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]: + # Monkeypatch platformdirs to pretend we're running on Windows with monkeypatch.context() as m: @@ -24,7 +26,8 @@ def platformdirs_win32(monkeypatch): @pytest.fixture() -def platformdirs_darwin(monkeypatch): +def platformdirs_darwin(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]: + # Monkeypatch platformdirs to pretend we're running on macOS with monkeypatch.context() as m: @@ -37,7 +40,8 @@ def platformdirs_darwin(monkeypatch): @pytest.fixture() -def platformdirs_linux(monkeypatch): +def platformdirs_linux(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]: + # Monkeypatch platformdirs to pretend we're running on Linux with monkeypatch.context() as m: @@ -50,11 +54,12 @@ def platformdirs_linux(monkeypatch): class TestUserCacheDir: - def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32) -> None: + @pytest.mark.usefixtures("platformdirs_win32") + def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - platformdirs.windows, + platformdirs.windows, # type: ignore[attr-defined] "get_win_folder", _get_win_folder, raising=False, @@ -66,27 +71,31 @@ def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_ ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] - def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: + @pytest.mark.usefixtures("platformdirs_darwin") + def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("HOME", "/home/test") assert appdirs.user_cache_dir("pip") == "/home/test/Library/Caches/pip" - def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None: + @pytest.mark.usefixtures("platformdirs_linux") + def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("XDG_CACHE_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") assert appdirs.user_cache_dir("pip") == "/home/test/.cache/pip" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_cache_dir_linux_override( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux + self, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setenv("XDG_CACHE_HOME", "/home/test/.other-cache") monkeypatch.setenv("HOME", "/home/test") assert appdirs.user_cache_dir("pip") == "/home/test/.other-cache/pip" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_cache_dir_linux_home_slash( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux + self, monkeypatch: pytest.MonkeyPatch ) -> None: # Verify that we are not affected by https://bugs.python.org/issue14768 monkeypatch.delenv("XDG_CACHE_HOME", raising=False) @@ -116,11 +125,12 @@ def my_get_win_folder(csidl_name): class TestSiteConfigDirs: - def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32) -> None: + @pytest.mark.usefixtures("platformdirs_win32") + def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch) -> None: _get_win_folder = mock.Mock(return_value="C:\\ProgramData") monkeypatch.setattr( - platformdirs.windows, + platformdirs.windows, # type: ignore[attr-defined] "get_win_folder", _get_win_folder, raising=False, @@ -129,7 +139,8 @@ def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch, platformdir assert appdirs.site_config_dirs("pip") == ["C:\\ProgramData\\pip"] assert _get_win_folder.call_args_list == [mock.call("CSIDL_COMMON_APPDATA")] - def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: + @pytest.mark.usefixtures("platformdirs_darwin") + def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("HOME", "/home/test") assert appdirs.site_config_dirs("pip") == [ @@ -137,13 +148,15 @@ def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch, platformdir "/Library/Application Support/pip", ] - def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None: + @pytest.mark.usefixtures("platformdirs_linux") + def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] + @pytest.mark.usefixtures("platformdirs_linux") def test_site_config_dirs_linux_override( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux + self, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg") @@ -155,8 +168,9 @@ def test_site_config_dirs_linux_override( "/etc", ] + @pytest.mark.usefixtures("platformdirs_linux") def test_site_config_dirs_linux_empty( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux + self, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "") @@ -164,13 +178,14 @@ def test_site_config_dirs_linux_empty( class TestUserConfigDir: + @pytest.mark.usefixtures("platformdirs_win32") def test_user_config_dir_win_no_roaming( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32 + self, monkeypatch: pytest.MonkeyPatch ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - platformdirs.windows, + platformdirs.windows, # type: ignore[attr-defined] "get_win_folder", _get_win_folder, raising=False, @@ -182,13 +197,14 @@ def test_user_config_dir_win_no_roaming( ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] + @pytest.mark.usefixtures("platformdirs_win32") def test_user_config_dir_win_yes_roaming( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32 + self, monkeypatch: pytest.MonkeyPatch ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Roaming") monkeypatch.setattr( - platformdirs.windows, + platformdirs.windows, # type: ignore[attr-defined] "get_win_folder", _get_win_folder, raising=False, @@ -199,7 +215,8 @@ def test_user_config_dir_win_yes_roaming( ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_APPDATA")] - def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: + @pytest.mark.usefixtures("platformdirs_darwin") + def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("HOME", "/home/test") if os.path.isdir("/home/test/Library/Application Support/"): @@ -210,22 +227,25 @@ def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs else: assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" - def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None: + @pytest.mark.usefixtures("platformdirs_linux") + def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) monkeypatch.setenv("HOME", "/home/test") assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_config_dir_linux_override( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux + self, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config") monkeypatch.setenv("HOME", "/home/test") assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_config_dir_linux_home_slash( - self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux + self, monkeypatch: pytest.MonkeyPatch ) -> None: # Verify that we are not affected by https://bugs.python.org/issue14768 monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)