diff --git a/pipenv/core.py b/pipenv/core.py index e8b2ee69..7c439802 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -786,9 +786,11 @@ def do_install_dependencies( ) ) # skip_lock should completely bypass the lockfile (broken in 4dac1676) - lockfile = project.get_or_create_lockfile(from_pipfile=True) + lockfile = project.get_or_create_lockfile( + categories=categories, from_pipfile=True + ) else: - lockfile = project.get_or_create_lockfile() + lockfile = project.get_or_create_lockfile(categories=categories) if not bare: click.echo( click.style( @@ -1052,11 +1054,6 @@ def do_lock( message="Pipfile.lock must exist to use --keep-outdated!", ) cached_lockfile = project.lockfile_content - # Create the lockfile. - if not project.lockfile_exists: - lockfile = project.get_or_create_lockfile() - project.write_lockfile(lockfile._lockfile._data) - lockfile = project.load_lockfile(expand_env_vars=False) # Cleanup lockfile. if not categories: lockfile_categories = project.get_package_categories(for_lockfile=True) @@ -1068,6 +1065,8 @@ def do_lock( if "packages" in categories: lockfile_categories.remove("packages") lockfile_categories.insert(0, "default") + # Create the lockfile. + lockfile = project._lockfile(categories=lockfile_categories) for category in lockfile_categories: for k, v in lockfile.get(category, {}).copy().items(): if not hasattr(v, "keys"): @@ -1142,9 +1141,7 @@ def do_lock( click.echo( "{}".format( click.style( - "Updated Pipfile.lock ({})!".format( - lockfile["_meta"].get("hash", {}).get("sha256")[-6:] - ), + "Updated Pipfile.lock ({})!".format(project.get_lockfile_hash()), bold=True, ) ), @@ -2517,6 +2514,7 @@ def do_uninstall( for normalized, package_name in selected_pkg_map.items() if normalized in (used_packages - bad_pkgs) ] + lockfile = project.get_or_create_lockfile(categories=categories) for category in categories: category = get_lockfile_section_using_pipfile_category(category) for normalized_name, package_name in selected_pkg_map.items(): @@ -2529,7 +2527,6 @@ def do_uninstall( click.style(fix_utf8("Pipfile.lock..."), fg="white"), ) ) - lockfile = project.get_or_create_lockfile() if normalized_name in lockfile[category]: del lockfile[category][normalized_name] lockfile.write() diff --git a/pipenv/project.py b/pipenv/project.py index 8a6d835d..a0b98b79 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -566,12 +566,27 @@ class Project: # Write the changes to disk. self.write_toml(p) - @property - def _lockfile(self): + def _lockfile(self, categories=None): """Pipfile.lock divided by PyPI and external dependencies.""" - with open(self.pipfile_location) as pf: - lockfile = plette.Lockfile.with_meta_from(plette.Pipfile.load(pf)) - for category in self.get_package_categories(for_lockfile=True): + lockfile_loaded = False + if self.lockfile_exists: + try: + lockfile = self.load_lockfile(expand_env_vars=False) + lockfile_loaded = True + except Exception: + pass + if not lockfile_loaded: + with open(self.pipfile_location) as pf: + lockfile = plette.Lockfile.with_meta_from( + plette.Pipfile.load(pf), categories=categories + ) + lockfile = lockfile._data + + # with open(self.pipfile_location) as pf: + # lockfile = plette.Lockfile.with_meta_from(plette.Pipfile.load(pf)) + if categories is None: + categories = self.get_package_categories(for_lockfile=True) + for category in categories: lock_section = lockfile.get(category) if lock_section is None: lockfile[category] = lock_section = {} @@ -581,7 +596,7 @@ class Project: del lock_section[key] lockfile[category][norm_key] = specifier - return lockfile._data + return lockfile @property def _pipfile(self): @@ -693,15 +708,17 @@ class Project: source["verify_ssl"] = str(source["verify_ssl"]).lower() == "true" return source - def get_or_create_lockfile(self, from_pipfile=False): + def get_or_create_lockfile(self, categories, from_pipfile=False): from pipenv.vendor.requirementslib.models.lockfile import ( Lockfile as Req_Lockfile, ) if from_pipfile and self.pipfile_exists: lockfile_dict = {} - for category in self.get_package_categories(for_lockfile=True): - lockfile_dict[category] = self._lockfile.get(category, {}).copy() + categories = self.get_package_categories(for_lockfile=True) + _lockfile = self._lockfile(categories=categories) + for category in categories: + lockfile_dict[category] = _lockfile.get(category, {}).copy() lockfile_dict.update({"_meta": self.get_lockfile_meta()}) lockfile = Req_Lockfile.from_data( path=self.lockfile_location, data=lockfile_dict, meta_from_project=False @@ -715,7 +732,9 @@ class Project: ) else: lockfile = Req_Lockfile.from_data( - path=self.lockfile_location, data=self._lockfile, meta_from_project=False + path=self.lockfile_location, + data=self._lockfile(), + meta_from_project=False, ) if lockfile._lockfile is not None: return lockfile @@ -733,7 +752,7 @@ class Project: lockfile._lockfile = lockfile.projectfile.model = _created_lockfile return lockfile else: - return self.get_or_create_lockfile(from_pipfile=True) + return self.get_or_create_lockfile(categories=categories, from_pipfile=True) def get_lockfile_meta(self): from .vendor.plette.lockfiles import PIPFILE_SPEC_CURRENT @@ -974,13 +993,20 @@ class Project: with io.open(self.lockfile_location, encoding="utf-8") as lock: j = json.load(lock) self._lockfile_newlines = preferred_newlines(lock) - # lockfile is just a string - if not j or not hasattr(j, "keys"): - return j + if not j.get("_meta"): + with open(self.pipfile_location) as pf: + default_lockfile = plette.Lockfile.with_meta_from( + plette.Pipfile.load(pf), categories=[] + ) + j["_meta"] = default_lockfile._data["_meta"] + if j.get("default") is None: + j["default"] = {} + if j.get("develop") is None: + j["develop"] = {} if expand_env_vars: # Expand environment variables in Pipfile.lock at runtime. - for i, _ in enumerate(j["_meta"]["sources"][:]): + for i, _ in enumerate(j["_meta"].get("sources", {})): j["_meta"]["sources"][i]["url"] = os.path.expandvars( j["_meta"]["sources"][i]["url"] ) @@ -997,7 +1023,7 @@ class Project: # Lockfile corrupted return "" if "_meta" in lockfile and hasattr(lockfile, "keys"): - return lockfile["_meta"].get("hash", {}).get("sha256") + return lockfile["_meta"].get("hash", {}).get("sha256") or "" # Lockfile exists but has no hash at all return "" diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index 974b7b84..e45e541d 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -992,7 +992,7 @@ def venv_resolve_deps( if not pipfile: pipfile = getattr(project, category, {}) if not lockfile: - lockfile = project._lockfile + lockfile = project._lockfile(categories=[category]) req_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements") cmd = [ which("python", allow_global=allow_global), diff --git a/pipenv/vendor/plette/lockfiles.py b/pipenv/vendor/plette/lockfiles.py index 9b362171..e8c3091e 100644 --- a/pipenv/vendor/plette/lockfiles.py +++ b/pipenv/vendor/plette/lockfiles.py @@ -79,7 +79,7 @@ class Lockfile(DataView): return cls(data) @classmethod - def with_meta_from(cls, pipfile): + def with_meta_from(cls, pipfile, categories=None): data = { "_meta": { "hash": _copy_jsonsafe(pipfile.get_hash()._data), @@ -87,9 +87,22 @@ class Lockfile(DataView): "requires": _copy_jsonsafe(pipfile._data.get("requires", {})), "sources": _copy_jsonsafe(pipfile.sources._data), }, - "default": _copy_jsonsafe(pipfile._data.get("packages", {})), - "develop": _copy_jsonsafe(pipfile._data.get("dev-packages", {})), } + if categories is None: + data["default"] = _copy_jsonsafe(pipfile._data.get("packages", {})) + data["develop"] = _copy_jsonsafe(pipfile._data.get("dev-packages", {})) + else: + for category in categories: + if category == "default" or category == "packages": + data["default"] = _copy_jsonsafe(pipfile._data.get("packages", {})) + elif category == "develop" or category == "dev-packages": + data["develop"] = _copy_jsonsafe(pipfile._data.get("dev-packages", {})) + else: + data[category] = _copy_jsonsafe(pipfile._data.get(category, {})) + if "default" not in data: + data["default"] = {} + if "develop" not in data: + data["develop"] = {} return cls(data) def __getitem__(self, key): diff --git a/tests/integration/test_install_twists.py b/tests/integration/test_install_twists.py index 18619684..04d7cbf1 100644 --- a/tests/integration/test_install_twists.py +++ b/tests/integration/test_install_twists.py @@ -181,10 +181,10 @@ def test_local_zip_file(pipenv_instance_private_pypi, testsroot): @pytest.mark.urls @pytest.mark.install -def test_install_local_uri_special_character(pipenv_instance_pypi, testsroot): +def test_install_local_uri_special_character(pipenv_instance_private_pypi, testsroot): file_name = "six-1.11.0+mkl-py2.py3-none-any.whl" source_path = os.path.abspath(os.path.join(testsroot, "test_artifacts", file_name)) - with pipenv_instance_pypi() as p: + with pipenv_instance_private_pypi() as p: artifact_dir = "artifacts" artifact_path = os.path.join(p.path, artifact_dir) mkdir_p(artifact_path) diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 36ba5706..09e9275c 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -527,7 +527,6 @@ def test_lockfile_with_empty_dict(pipenv_instance_pypi): f.write('{}') c = p.pipenv('install') assert c.returncode == 0 - assert 'Pipfile.lock is corrupted' in c.stderr assert p.lockfile['_meta']