From 4b290407d31b6059b4f682d2f317c977abf7f90a Mon Sep 17 00:00:00 2001 From: frostming Date: Wed, 28 Mar 2018 16:24:08 +0800 Subject: [PATCH] only change things on way in --- pipenv/core.py | 39 +-------------------- pipenv/project.py | 82 ++++++++++++++++++++++++++++++-------------- tests/test_pipenv.py | 22 ++++++++---- 3 files changed, 73 insertions(+), 70 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index def2212f..fb045b53 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -290,7 +290,7 @@ def ensure_pipfile(validate=True, skip_requirements=False): if validate and project.virtualenv_exists and not PIPENV_SKIP_VALIDATION: # Ensure that Pipfile is using proper casing. p = project.parsed_pipfile - changed = ensure_proper_casing(pfile=p) + changed = project.ensure_proper_casing() # Write changes out to disk. if changed: click.echo( @@ -298,7 +298,6 @@ def ensure_pipfile(validate=True, skip_requirements=False): err=True, ) project.write_toml(p) - project.clear_pipfile_cache() def find_python_from_py(python): @@ -635,42 +634,6 @@ def ensure_project( ensure_pipfile(validate=validate, skip_requirements=skip_requirements) -def ensure_proper_casing(pfile): - """Ensures proper casing of Pipfile packages, writes changes to disk.""" - casing_changed = proper_case_section(pfile.get('packages', {})) - casing_changed |= proper_case_section(pfile.get('dev-packages', {})) - return casing_changed - - -def proper_case_section(section): - """Verify proper casing is retrieved, when available, for each - dependency in the section. - """ - # Casing for section. - changed_values = False - unknown_names = [ - k for k in section.keys() if k not in set(project.proper_names) - ] - # Replace each package with proper casing. - for dep in unknown_names: - try: - # Get new casing for package name. - new_casing = proper_case(dep) - except IOError: - # Unable to normalize package name. - continue - - if new_casing != dep: - changed_values = True - project.register_proper_name(new_casing) - # Replace old value with new value. - old_value = section[dep] - section[new_casing] = old_value - del section[dep] - # Return whether or not values have been changed. - return changed_values - - def shorten_path(location, bold=False): """Returns a visually shorter representation of a given system path.""" original = location diff --git a/pipenv/project.py b/pipenv/project.py index 0e6cb6ba..d17900b9 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -3,7 +3,6 @@ import codecs import json import os import re -import six import sys import shlex import base64 @@ -21,7 +20,7 @@ from .utils import ( mkdir_p, convert_deps_from_pip, pep423_name, - recase_file, + proper_case, find_requirements, is_editable, is_file, @@ -370,20 +369,6 @@ class Project(object): except Exception: return toml.loads(contents) - @property - def _pipfile(self): - """Pipfile divided by PyPI and external dependencies.""" - pfile = self.parsed_pipfile - # mutation time! - self.clear_pipfile_cache() - for section in ('packages', 'dev-packages'): - p_section = pfile.get(section, {}) - for key in list(p_section.keys()): - # Normalize key name to PEP 423. - norm_key = pep423_name(key) - p_section[norm_key] = p_section.pop(key) - return pfile - @property def settings(self): """A dictionary of the settings added to the Pipfile.""" @@ -416,7 +401,6 @@ class Project(object): p['pipenv'] = settings # Write the changes to disk. self.write_toml(p) - self.clear_pipfile_cache() @property def _lockfile(self): @@ -572,6 +556,8 @@ class Project(object): formatted_data = cleanup_toml(formatted_data) with open(path, 'w') as f: f.write(formatted_data) + # pipfile is mutated! + self.clear_pipfile_cache() @property def sources(self): @@ -607,17 +593,19 @@ class Project(object): def remove_package_from_pipfile(self, package_name, dev=False): # Read and append Pipfile. - p = self._pipfile + p = self.parsed_pipfile package_name = pep423_name(package_name) key = 'dev-packages' if dev else 'packages' - if key in p and package_name in p[key]: - del p[key][package_name] - # Write Pipfile. - self.write_toml(recase_file(p)) + if key in p: + for name in dict(p[key]): + if pep423_name(name) == package_name: + del p[key][name] + # Write Pipfile. + return self.write_toml(p) def add_package_to_pipfile(self, package_name, dev=False): # Read and append Pipfile. - p = self._pipfile + p = self.parsed_pipfile # Don't re-capitalize file URLs or VCSs. converted = convert_deps_from_pip(package_name) converted = converted[[k for k in converted.keys()][0]] @@ -631,6 +619,14 @@ class Project(object): p[key] = {} package = convert_deps_from_pip(package_name) package_name = [k for k in package.keys()][0] + for name in p[key]: + # Normalize names to compare + if ( + pep423_name(name) == pep423_name(package_name) and + name != package_name + ): + # Replace the package name + del p[key][name] # Add the package to the group. p[key][package_name] = package[package_name] # Write Pipfile. @@ -639,7 +635,7 @@ class Project(object): def add_index_to_pipfile(self, index): """Adds a given index to the Pipfile.""" # Read and append Pipfile. - p = self._pipfile + p = self.parsed_pipfile source = {'url': index, 'verify_ssl': True} # Add the package to the group. if 'source' not in p: @@ -650,7 +646,8 @@ class Project(object): self.write_toml(p) def recase_pipfile(self): - self.write_toml(recase_file(self._pipfile)) + if self.ensure_proper_casing(): + self.write_toml(self.parsed_pipfile) def get_lockfile_hash(self): if not os.path.exists(self.lockfile_location): @@ -664,3 +661,38 @@ class Project(object): # Update the lockfile if it is out-of-date. p = pipfile.load(self.pipfile_location, inject_env=False) return p.hash + + def ensure_proper_casing(self): + """Ensures proper casing of Pipfile packages""" + pfile = self.parsed_pipfile + casing_changed = self.proper_case_section(pfile.get('packages', {})) + casing_changed |= self.proper_case_section(pfile.get('dev-packages', {})) + return casing_changed + + def proper_case_section(self, section): + """Verify proper casing is retrieved, when available, for each + dependency in the section. + """ + # Casing for section. + changed_values = False + unknown_names = [ + k for k in section.keys() if k not in set(self.proper_names) + ] + # Replace each package with proper casing. + for dep in unknown_names: + try: + # Get new casing for package name. + new_casing = proper_case(dep) + except IOError: + # Unable to normalize package name. + continue + + if new_casing != dep: + changed_values = True + self.register_proper_name(new_casing) + # Replace old value with new value. + old_value = section[dep] + section[new_casing] = old_value + del section[dep] + # Return whether or not values have been changed. + return changed_values diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index a077efab..b03875f7 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -598,17 +598,20 @@ tpfd = "*" contents = """ # Pre comment [packages] -python_dateutil = "*" # Inline comment +python_DateUtil = "*" # Inline comment """ f.write(contents) c = p.pipenv('install') assert c.return_code == 0 - c = p.pipenv('install python-dateutil') + c = p.pipenv('install requests') + assert c.return_code == 0 + assert 'python_DateUtil' in p.pipfile['packages'] + c = p.pipenv('install python_dateutil') assert c.return_code == 0 assert 'python-dateutil' in p.pipfile['packages'] - assert 'python_dateutil' not in p.pipfile['packages'] + assert 'python_DateUtil' not in p.pipfile['packages'] contents = open(p.pipfile_path).read() assert '# Pre comment' in contents assert '# Inline comment' in contents @@ -619,17 +622,22 @@ python_dateutil = "*" # Inline comment with PipenvInstance(pypi=pypi) as p: with open(p.pipfile_path, 'w') as f: contents = """ +# Pre comment [packages] -python_dateutil = "*" +Requests = "*" +python_DateUtil = "*" # Inline comment """ f.write(contents) c = p.pipenv('install') assert c.return_code == 0 - c = p.pipenv('uninstall python-dateutil') - assert 'python_dateutil' not in p.pipfile['packages'] - assert 'python-dateutil' not in p.lockfile['default'] + c = p.pipenv('uninstall python_dateutil') + assert 'Requests' in p.pipfile['packages'] + assert 'python_DateUtil' not in p.pipfile['packages'] + contents = open(p.pipfile_path).read() + assert '# Pre comment' in contents + assert '# Inline comment' in contents @pytest.mark.install @pytest.mark.resolver