diff --git a/CHANGES.txt b/CHANGES.txt index 9d68b7fd4ef..841d2622101 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -38,6 +38,9 @@ * Added pip completion support for fish shell. +* Fix problems on Windows on Python 2 when username or hostname contains + non-ASCII characters (:issue:`3463`, :pull:`3970`). + * Use git fetch --tags to fetch tags in addition to everything else that is normally fetched; this is necessary in case a git requirement url points to a tag or commit that is not on a branch (:pull:`3791`) diff --git a/pip/utils/appdirs.py b/pip/utils/appdirs.py index 15ca3cd03e8..a4948cd9239 100644 --- a/pip/utils/appdirs.py +++ b/pip/utils/appdirs.py @@ -8,6 +8,7 @@ import sys from pip.compat import WINDOWS, expanduser +from pip._vendor.six import PY2, text_type def user_cache_dir(appname): @@ -35,6 +36,11 @@ def user_cache_dir(appname): # Get the base path path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + # When using Python 2, return paths as bytes on Windows like we do on + # other operating systems. See helper function docs for more details. + if PY2 and isinstance(path, text_type): + path = _win_path_to_bytes(path) + # Add our app name and Cache directory to it path = os.path.join(path, appname, "Cache") elif sys.platform == "darwin": @@ -222,3 +228,21 @@ def _get_win_folder_with_ctypes(csidl_name): _get_win_folder = _get_win_folder_with_ctypes except ImportError: _get_win_folder = _get_win_folder_from_registry + + +def _win_path_to_bytes(path): + """Encode Windows paths to bytes. Only used on Python 2. + + Motivation is to be consistent with other operating systems where paths + are also returned as bytes. This avoids problems mixing bytes and Unicode + elsewhere in the codebase. For more details and discussion see + . + + If encoding using ASCII and MBCS fails, return the original Unicode path. + """ + for encoding in ('ASCII', 'MBCS'): + try: + return path.encode(encoding) + except (UnicodeEncodeError, LookupError): + pass + return path diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 32220b4d180..d81ac81a215 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -22,6 +22,7 @@ from pip.utils.encoding import auto_decode from pip.utils.hashes import Hashes, MissingHashes from pip.utils.glibc import check_glibc_version +from pip.utils.appdirs import user_cache_dir from pip._vendor.six import BytesIO @@ -524,3 +525,10 @@ def test_manylinux1_check_glibc_version(self): else: # Didn't find the warning we were expecting assert False + + +class Test_user_cache_dir(object): + + def test_return_path_as_str(self): + """Test that path is bytes on Python 2 and Unicode on Python 3.""" + assert isinstance(user_cache_dir('test'), str)