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/pyproject.toml b/pyproject.toml index a479b968d6d..fac27944798 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ drop = [ [tool.vendoring.typing-stubs] six = ["six.__init__", "six.moves.__init__", "six.moves.configparser"] -appdirs = [] distro = [] [tool.vendoring.license.directories] diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index a8403b7dee4..5e334126b61 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -7,9 +7,10 @@ """ import os +import sys 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: @@ -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,7 +30,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 sys.platform == "darwin": + # always look in /Library/Application Support/pip as well + return dirval.split(os.pathsep) + ["/Library/Application Support/pip"] + elif sys.platform == "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/README.rst b/src/pip/_vendor/README.rst index 12b421a0417..26904ca251a 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 @@ -111,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 =================== 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/appdirs.py b/src/pip/_vendor/appdirs.py deleted file mode 100644 index 33a3b77410c..00000000000 --- a/src/pip/_vendor/appdirs.py +++ /dev/null @@ -1,633 +0,0 @@ -#!/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. -""" -# 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 - -__version__ = "1.4.4" -__version_info__ = tuple(int(segment) for segment in __version__.split(".")) - - -import sys -import os - -PY3 = sys.version_info[0] == 3 - -if PY3: - 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' -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 - - - -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/ # 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\\ - Win 7 (not roaming): C:\Users\\AppData\Local\\ - Win 7 (roaming): C:\Users\\AppData\Roaming\\ - - For Unix, we follow the XDG spec and support $XDG_DATA_HOME. - That means, by default "~/.local/share/". - """ - if 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 - - -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. - """ - 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 - - -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: same as user_data_dir - 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 - - -# 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. - - "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. - """ - 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 - - -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. - """ - 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 - - -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/". - """ - 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 - - -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. - """ - 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 - - -class AppDirs(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) - - -#---- 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 - - -#---- self test code - -if __name__ == "__main__": - 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 = AppDirs(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) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (without optional 'appauthor')") - dirs = AppDirs(appname) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (with disabled 'appauthor')") - dirs = AppDirs(appname, appauthor=False) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) 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 99% rename from src/pip/_vendor/appdirs.LICENSE.txt rename to src/pip/_vendor/platformdirs/LICENSE.txt index 107c61405e3..f0bbd69f0c8 100644 --- a/src/pip/_vendor/appdirs.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..ed51f27e6d8 --- /dev/null +++ b/src/pip/_vendor/platformdirs/__init__.py @@ -0,0 +1,279 @@ +""" +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_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 + + +__all__ = [ + "__version__", + "__version_info__", + "PlatformDirs", + "AppDirs", + "PlatformDirsABC", + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir", + "user_data_path", + "user_config_path", + "user_cache_path", + "user_state_path", + "user_log_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..97f9de5dd4e --- /dev/null +++ b/src/pip/_vendor/platformdirs/__main__.py @@ -0,0 +1,42 @@ +from pip._vendor.platformdirs import PlatformDirs, __version__ + +PROPS = ( + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_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..c8c6419460d --- /dev/null +++ b/src/pip/_vendor/platformdirs/android.py @@ -0,0 +1,83 @@ +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 + + +@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..7b29173452f --- /dev/null +++ b/src/pip/_vendor/platformdirs/api.py @@ -0,0 +1,135 @@ +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 + 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) diff --git a/src/pip/_vendor/platformdirs/macos.py b/src/pip/_vendor/platformdirs/macos.py new file mode 100644 index 00000000000..4d4d5b02c27 --- /dev/null +++ b/src/pip/_vendor/platformdirs/macos.py @@ -0,0 +1,52 @@ +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")) + + +__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..3f6afec5a6d --- /dev/null +++ b/src/pip/_vendor/platformdirs/unix.py @@ -0,0 +1,125 @@ +import os +from pathlib import Path + +from .api import PlatformDirsABC + + +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 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..2b18c715b52 --- /dev/null +++ b/src/pip/_vendor/platformdirs/version.py @@ -0,0 +1,4 @@ +""" Version information """ + +__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 new file mode 100644 index 00000000000..274a8c89e85 --- /dev/null +++ b/src/pip/_vendor/platformdirs/windows.py @@ -0,0 +1,163 @@ +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 + + +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 991c3af8c49..29fbff8c357 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.2.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 70453c2755c..e24523d8d08 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -2,26 +2,68 @@ import os import posixpath import sys +from typing import Generator 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: pytest.MonkeyPatch) -> Generator[None, None, None]: + + # Monkeypatch platformdirs to pretend we're running on Windows + + with monkeypatch.context() as m: + m.setattr(sys, "platform", "win32") + m.setattr(os, "path", ntpath) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() + yield + + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() + + +@pytest.fixture() +def platformdirs_darwin(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]: + + # Monkeypatch platformdirs to pretend we're running on macOS + + with monkeypatch.context() as m: + m.setattr(sys, "platform", "darwin") + m.setattr(os, "path", posixpath) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() + yield + + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() + + +@pytest.fixture() +def platformdirs_linux(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]: + + # Monkeypatch platformdirs to pretend we're running on Linux + + with monkeypatch.context() as m: + m.setattr(sys, "platform", "linux") + m.setattr(os, "path", posixpath) + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() + yield + + platformdirs.PlatformDirs = platformdirs._set_platform_dir_class() + + class TestUserCacheDir: + @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( - _appdirs, - "_get_win_folder", + platformdirs.windows, # type: ignore[attr-defined] + "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 +71,35 @@ def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch) -> None: ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] + @pytest.mark.usefixtures("platformdirs_darwin") def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "darwin") assert appdirs.user_cache_dir("pip") == "/home/test/Library/Caches/pip" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) 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" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_cache_dir_linux_override( self, monkeypatch: pytest.MonkeyPatch ) -> 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" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_cache_dir_linux_home_slash( self, monkeypatch: pytest.MonkeyPatch ) -> 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 +110,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.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 @@ -91,45 +125,41 @@ def my_get_win_folder(csidl_name): class TestSiteConfigDirs: + @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( - _appdirs, - "_get_win_folder", + platformdirs.windows, # type: ignore[attr-defined] + "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")] + @pytest.mark.usefixtures("platformdirs_darwin") def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) 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", + ] + @pytest.mark.usefixtures("platformdirs_linux") def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) - monkeypatch.setattr(sys, "platform", "linux2") 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 ) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) 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", @@ -138,31 +168,28 @@ def test_site_config_dirs_linux_override( "/etc", ] + @pytest.mark.usefixtures("platformdirs_linux") def test_site_config_dirs_linux_empty( self, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setattr(os, "pathsep", ":") monkeypatch.setenv("XDG_CONFIG_DIRS", "") - monkeypatch.setattr(sys, "platform", "linux2") assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] class TestUserConfigDir: + @pytest.mark.usefixtures("platformdirs_win32") def test_user_config_dir_win_no_roaming( self, monkeypatch: pytest.MonkeyPatch ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( - _appdirs, - "_get_win_folder", + platformdirs.windows, # type: ignore[attr-defined] + "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) @@ -170,30 +197,27 @@ 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 ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Roaming") monkeypatch.setattr( - _appdirs, - "_get_win_folder", + platformdirs.windows, # type: ignore[attr-defined] + "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")] + @pytest.mark.usefixtures("platformdirs_darwin") def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "darwin") if os.path.isdir("/home/test/Library/Application Support/"): assert ( @@ -203,34 +227,28 @@ def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: else: assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) 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" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_config_dir_linux_override( self, monkeypatch: pytest.MonkeyPatch ) -> 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" + @pytest.mark.usefixtures("platformdirs_linux") def test_user_config_dir_linux_home_slash( self, monkeypatch: pytest.MonkeyPatch ) -> 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..6556a860867 --- /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 @@ except ImportError: + importlib_machinery = None + + from . import py31compat +-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') +- 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..4f27b743dde --- /dev/null +++ b/tools/vendoring/patches/platformdirs.patch @@ -0,0 +1,22 @@ +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 _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 +