mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Force Windows local drive names to uppercase
Windows generally use upper cased drive names, but allow (without
normalizing) lower cased names in cmd.exe, which results in
inconsistencies when hashing the full path (to get the name of the
project's virtualenv). Python does not provide a good solution[*], so we
need to roll our own.
[*]: Python does have os.path.normcase(), but it always converts the
whole paths to lowercase. This would break virtually *all* existing
virtualenvs for Windows users.
UNC host names (which Python also treats as drives), on the other hand,
can actually be either cased. I am not sure if they are case-sensitive,
or should be coerced to what case, so this patch keeps with the existing
behaviour, and does not try to coerce them.
This commit is contained in:
+3
-3
@@ -14,7 +14,7 @@ import toml
|
||||
from .utils import (
|
||||
mkdir_p, convert_deps_from_pip, pep423_name, recase_file,
|
||||
find_requirements, is_file, is_vcs, python_version, cleanup_toml,
|
||||
is_installable_file, is_valid_url
|
||||
is_installable_file, is_valid_url, normalize_drive,
|
||||
)
|
||||
from .environments import PIPENV_MAX_DEPTH, PIPENV_VENV_IN_PROJECT
|
||||
from .environments import PIPENV_VIRTUALENV, PIPENV_PIPFILE
|
||||
@@ -23,7 +23,7 @@ if PIPENV_PIPFILE:
|
||||
if not os.path.isfile(PIPENV_PIPFILE):
|
||||
raise RuntimeError('Given PIPENV_PIPFILE is not found!')
|
||||
else:
|
||||
PIPENV_PIPFILE = os.path.abspath(PIPENV_PIPFILE)
|
||||
PIPENV_PIPFILE = normalize_drive(os.path.abspath(PIPENV_PIPFILE))
|
||||
|
||||
|
||||
class Project(object):
|
||||
@@ -224,7 +224,7 @@ class Project(object):
|
||||
loc = pipfile.Pipfile.find(max_depth=PIPENV_MAX_DEPTH)
|
||||
except RuntimeError:
|
||||
loc = None
|
||||
self._pipfile_location = loc
|
||||
self._pipfile_location = normalize_drive(loc)
|
||||
|
||||
return self._pipfile_location
|
||||
|
||||
|
||||
+22
-4
@@ -277,7 +277,7 @@ def get_requirement(dep):
|
||||
remote URIs, and package names, and that we pass only valid requirement strings
|
||||
to the requirements parser. Performs necessary modifications to requirements
|
||||
object if the user input was a local relative path.
|
||||
|
||||
|
||||
:param str dep: A requirement line
|
||||
:returns: :class:`requirements.Requirement` object
|
||||
"""
|
||||
@@ -932,11 +932,11 @@ def proper_case(package_name):
|
||||
def split_section(input_file, section_suffix, test_function):
|
||||
"""
|
||||
Split a pipfile or a lockfile section out by section name and test function
|
||||
|
||||
|
||||
:param dict input_file: A dictionary containing either a pipfile or lockfile
|
||||
:param str section_suffix: A string of the name of the section
|
||||
:param func test_function: A test function to test against the value in the key/value pair
|
||||
|
||||
|
||||
>>> split_section(my_lockfile, 'vcs', is_vcs)
|
||||
{
|
||||
'default': {
|
||||
@@ -992,7 +992,7 @@ def merge_deps(file_dict, project, dev=False, requirements=False, ignore_hashes=
|
||||
Given a file_dict, merges dependencies and converts them to pip dependency lists.
|
||||
:param dict file_dict: The result of calling :func:`pipenv.utils.split_file`
|
||||
:param :class:`pipenv.project.Project` project: Pipenv project
|
||||
:param bool dev=False: Flag indicating whether dev dependencies are to be installed
|
||||
:param bool dev=False: Flag indicating whether dev dependencies are to be installed
|
||||
:param bool requirements=False: Flag indicating whether to use a requirements file
|
||||
:param bool ignore_hashes=False:
|
||||
:param bool blocking=False:
|
||||
@@ -1174,3 +1174,21 @@ def touch_update_stamp():
|
||||
except OSError:
|
||||
with open(p, 'w') as fh:
|
||||
fh.write('')
|
||||
|
||||
|
||||
def normalize_drive(path):
|
||||
"""Normalize drive in path so they stay consistent.
|
||||
|
||||
This currently only affects local drives on Windows, which can be
|
||||
identified with either upper or lower cased drive names. The case is
|
||||
always converted to uppercase because it seems to be preferred.
|
||||
|
||||
See: <https://github.com/pypa/pipenv/issues/1218>
|
||||
"""
|
||||
if os.name != 'nt':
|
||||
return path
|
||||
drive, tail = os.path.splitdrive(path)
|
||||
# Only match (lower cased) local drives (e.g. 'c:'), not UNC mounts.
|
||||
if drive.islower() and len(drive) == 2 and drive[1] == ':':
|
||||
return '{}{}'.format(drive.upper(), tail)
|
||||
return path
|
||||
|
||||
@@ -226,3 +226,27 @@ twine = "*"
|
||||
new_toml = pipenv.utils.cleanup_toml(toml)
|
||||
# testing if the end of the generated file contains a newline
|
||||
assert new_toml[-1] == '\n'
|
||||
|
||||
@pytest.mark.parametrize('input_path, expected', [
|
||||
('c:\\Program Files\\Python36\\python.exe',
|
||||
'C:\\Program Files\\Python36\\python.exe'),
|
||||
('C:\\Program Files\\Python36\\python.exe',
|
||||
'C:\\Program Files\\Python36\\python.exe'),
|
||||
('\\\\host\\share\\file.zip', '\\\\host\\share\\file.zip'),
|
||||
('artifacts\\file.zip', 'artifacts\\file.zip'),
|
||||
('.\\artifacts\\file.zip', '.\\artifacts\\file.zip'),
|
||||
('..\\otherproject\\file.zip', '..\\otherproject\\file.zip'),
|
||||
])
|
||||
@pytest.mark.skipif(os.name != 'nt', reason='Windows file paths tested')
|
||||
def test_win_normalize_drive(self, input_path, expected):
|
||||
assert pipenv.utils.normalize_drive(input_path) == expected
|
||||
|
||||
@pytest.mark.parametrize('input_path, expected', [
|
||||
('/usr/local/bin/python', '/usr/local/bin/python'),
|
||||
('artifacts/file.zip', 'artifacts/file.zip'),
|
||||
('./artifacts/file.zip', './artifacts/file.zip'),
|
||||
('../otherproject/file.zip', '../otherproject/file.zip'),
|
||||
])
|
||||
@pytest.mark.skipif(os.name == 'nt', reason='*nix file paths tested')
|
||||
def test_nix_normalize_drive(self, input_path, expected):
|
||||
assert pipenv.utils.normalize_drive(input_path) == expected
|
||||
|
||||
Reference in New Issue
Block a user