diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4adcaa3182..0e3099d108 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,5 @@ 2023.8.20 (2023-08-20) ====================== -Pipenv 2023.8.20 (2023-08-20) -============================= - Bug Fixes --------- @@ -12,9 +9,6 @@ Bug Fixes 2023.8.19 (2023-08-19) ====================== -Pipenv 2023.8.19 (2023-08-19) -============================= - Features & Improvements ----------------------- diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 2925f3cd57..3fdac10920 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -222,6 +222,7 @@ def install(state, **kwargs): site_packages=state.site_packages, extra_pip_args=state.installstate.extra_pip_args, categories=state.installstate.categories, + skip_lock=state.installstate.skip_lock, ) diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index 53cc33ab8f..e1ece8d7ca 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -85,6 +85,7 @@ def __init__(self): self.editables = [] self.extra_pip_args = [] self.categories = [] + self.skip_lock = False class LockOptions: @@ -484,32 +485,32 @@ def validate_pypi_mirror(ctx, param, value): return value -# OLD REMOVED COMMANDS THAT WE STILL DISPLAY HELP TEXT FOR # def skip_lock_option(f): def callback(ctx, param, value): if value: err.print( - "The flag --skip-lock has been functionally removed. " - "Without running the lock resolver it is not possible to manage multiple package indexes. " - "Additionally it bypassed the build consistency guarantees provided by maintaining a lock file.", + "The flag --skip-lock has been reintroduced (but is not recommended). " + "Without the lock resolver it is difficult to manage multiple package indexes, and hash checking is not provided. " + "However it can help manage installs with current deficiencies in locking across platforms.", style="yellow bold", ) - raise ValueError("The flag --skip-lock flag has been removed.") + state = ctx.ensure_object(State) + state.installstate.skip_lock = value return value return option( "--skip-lock", is_flag=True, default=False, - expose_value=False, + expose_value=True, envvar="PIPENV_SKIP_LOCK", callback=callback, type=click_types.BOOL, show_envvar=True, - hidden=True, # This hides the option from the help text. )(f) +# OLD REMOVED COMMANDS THAT WE STILL DISPLAY HELP TEXT FOR WHEN USED # def keep_outdated_option(f): def callback(ctx, param, value): state = ctx.ensure_object(State) diff --git a/pipenv/routines/install.py b/pipenv/routines/install.py index db28d7a136..9df2177536 100644 --- a/pipenv/routines/install.py +++ b/pipenv/routines/install.py @@ -12,6 +12,7 @@ from pipenv.utils.dependencies import ( expansive_install_req_from_line, get_lockfile_section_using_pipfile_category, + install_req_from_pipfile, ) from pipenv.utils.indexes import get_source_list from pipenv.utils.internet import download_file, is_valid_url @@ -42,6 +43,7 @@ def do_install( site_packages=None, extra_pip_args=None, categories=None, + skip_lock=False, ): requirements_directory = fileutils.create_tracked_tempdir( suffix="-requirements", prefix="pipenv-" @@ -70,7 +72,7 @@ def do_install( if not project.pipfile_exists and not (package_args or dev): if not (ignore_pipfile or deploy): raise exceptions.PipfileNotFound(project.path_to("Pipfile")) - elif ignore_pipfile and not project.lockfile_exists: + elif ((skip_lock and deploy) or ignore_pipfile) and not project.lockfile_exists: raise exceptions.LockfileNotFound(project.path_to("Pipfile.lock")) # Load the --pre settings from the Pipfile. if not pre: @@ -171,6 +173,7 @@ def do_install( pypi_mirror=pypi_mirror, extra_pip_args=extra_pip_args, categories=categories, + skip_lock=skip_lock, ) # This is for if the user passed in dependencies, then we want to make sure we @@ -188,6 +191,7 @@ def do_install( pypi_mirror=pypi_mirror, extra_pip_args=extra_pip_args, categories=categories, + skip_lock=skip_lock, ) for pkg_line in pkg_list: @@ -284,6 +288,7 @@ def do_install( pypi_mirror=pypi_mirror, extra_pip_args=extra_pip_args, categories=categories, + skip_lock=skip_lock, ) except Exception as e: # If we fail to install, remove the package from the Pipfile. @@ -358,6 +363,7 @@ def do_install_dependencies( pypi_mirror=None, extra_pip_args=None, categories=None, + skip_lock=False, ): """ Executes the installation functionality. @@ -373,17 +379,39 @@ def do_install_dependencies( categories = ["packages"] for category in categories: - lockfile = project.get_or_create_lockfile(categories=categories) - if not bare: - console.print( - f"Installing dependencies from Pipfile.lock " - f"({lockfile['_meta'].get('hash', {}).get('sha256')[-6:]})...", - style="bold", - ) + # Load the lockfile if it exists, or if dev_only is being used. + lockfile = None + pipfile = None + if skip_lock: + ignore_hashes = True + if not bare: + console.print("Installing dependencies from Pipfile...", style="bold") + pipfile = project.get_pipfile_section(category) + else: + lockfile = project.get_or_create_lockfile(categories=categories) + if not bare: + console.print( + f"Installing dependencies from Pipfile.lock " + f"({lockfile['_meta'].get('hash', {}).get('sha256')[-6:]})...", + style="bold", + ) dev = dev or dev_only - deps_list = list( - lockfile.get_requirements(dev=dev, only=dev_only, categories=[category]) - ) + if skip_lock: + deps_list = [] + for req_name, pipfile_entry in pipfile.items(): + install_req, markers, req_line = install_req_from_pipfile( + req_name, pipfile_entry + ) + deps_list.append( + ( + install_req, + req_line, + ) + ) + else: + deps_list = list( + lockfile.get_requirements(dev=dev, only=dev_only, categories=[category]) + ) editable_or_vcs_deps = [ (dep, pip_line) for dep, pip_line in deps_list if (dep.link and dep.editable) ] @@ -394,15 +422,18 @@ def do_install_dependencies( ] install_kwargs = { - "no_deps": True, + "no_deps": not skip_lock, "ignore_hashes": ignore_hashes, "allow_global": allow_global, "pypi_mirror": pypi_mirror, "sequential_deps": editable_or_vcs_deps, "extra_pip_args": extra_pip_args, } - lockfile_category = get_lockfile_section_using_pipfile_category(category) - lockfile_section = lockfile[lockfile_category] + if skip_lock: + lockfile_section = pipfile + else: + lockfile_category = get_lockfile_section_using_pipfile_category(category) + lockfile_section = lockfile[lockfile_category] batch_install( project, normal_deps, @@ -499,8 +530,10 @@ def batch_install( deps_by_index = defaultdict(list) for dependency, pip_line in deps_to_install: index = project.sources_default["name"] - if dependency.name and lockfile_section[dependency.name].get("index"): - index = lockfile_section[dependency.name]["index"] + if dependency.name and dependency.name in lockfile_section: + entry = lockfile_section[dependency.name] + if isinstance(entry, dict) and "index" in entry: + index = entry["index"] deps_by_index[index].append(pip_line) # Treat each index as its own pip install phase for index_name, dependencies in deps_by_index.items(): @@ -560,6 +593,7 @@ def do_init( pypi_mirror=None, extra_pip_args=None, categories=None, + skip_lock=False, ): """Executes the init functionality.""" python = None @@ -585,7 +619,7 @@ def do_init( suffix="-requirements", prefix="pipenv-" ) # Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored - if project.lockfile_exists and not ignore_pipfile: + if (project.lockfile_exists and not ignore_pipfile) and not skip_lock: old_hash = project.get_lockfile_hash() new_hash = project.calculate_pipfile_hash() if new_hash != old_hash: @@ -620,7 +654,7 @@ def do_init( categories=categories, ) # Write out the lockfile if it doesn't exist. - if not project.lockfile_exists: + if not project.lockfile_exists and not skip_lock: # Unless we're in a virtualenv not managed by pipenv, abort if we're # using the system's python. if (system or allow_global) and not (project.s.PIPENV_VIRTUALENV): @@ -652,6 +686,7 @@ def do_init( pypi_mirror=pypi_mirror, extra_pip_args=extra_pip_args, categories=categories, + skip_lock=skip_lock, ) # Hint the user what to do to activate the virtualenv. diff --git a/pipenv/utils/dependencies.py b/pipenv/utils/dependencies.py index 1e018e4ae5..01455ca00b 100644 --- a/pipenv/utils/dependencies.py +++ b/pipenv/utils/dependencies.py @@ -185,7 +185,7 @@ def unearth_hashes_for_dep(project, dep): break # 1 Try to get hashes directly form index - install_req, markers = install_req_from_pipfile(dep["name"], dep) + install_req, markers, _ = install_req_from_pipfile(dep["name"], dep) if not install_req or not install_req.req: return [] if "https://pypi.org/simple/" in index_url: @@ -981,7 +981,7 @@ def install_req_from_pipfile(name, pipfile): vcs_url_parts = vcs_url.rsplit("@", 1) vcs_url = vcs_url_parts[0] fallback_ref = vcs_url_parts[1] - req_str = f"{vcs_url}{_pipfile.get('ref', fallback_ref)}{extras_str}" + req_str = f"{vcs_url}@{_pipfile.get('ref', fallback_ref)}{extras_str}" if not req_str.startswith(f"{vcs}+"): req_str = f"{vcs}+{req_str}" if f"{vcs}+file://" in req_str: @@ -1011,12 +1011,11 @@ def install_req_from_pipfile(name, pipfile): expand_env=True, ) markers = PipenvMarkers.from_pipfile(name, _pipfile) - return install_req, markers + return install_req, markers, req_str def from_pipfile(name, pipfile): - install_req, markers = install_req_from_pipfile(name, pipfile) - + install_req, markers, req_str = install_req_from_pipfile(name, pipfile) if markers: markers = str(markers) install_req.markers = Marker(markers) diff --git a/tests/integration/test_install_twists.py b/tests/integration/test_install_twists.py index e15509426c..5f410811e7 100644 --- a/tests/integration/test_install_twists.py +++ b/tests/integration/test_install_twists.py @@ -286,3 +286,22 @@ def test_install_remote_wheel_file_with_extras(pipenv_instance_pypi): assert "pre-commit" in p.lockfile["default"] assert "uvicorn" in p.lockfile["default"] +@pytest.mark.install +@pytest.mark.skip_lock +@pytest.mark.needs_internet +def test_install_skip_lock(pipenv_instance_private_pypi): + with pipenv_instance_private_pypi() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "{}" +verify_ssl = true +name = "pypi" +[packages] +six = {} + """.format(p.index_url, '{version = "*", index = "pypi"}').strip() + f.write(contents) + c = p.pipenv('install --skip-lock') + assert c.returncode == 0 + c = p.pipenv('run python -c "import six"') + assert c.returncode == 0 diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 4279a3b0ff..c4be350d70 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -162,3 +162,16 @@ def test_run_in_virtualenv(pipenv_instance_pypi): c = p.pipenv("clean --dry-run") assert c.returncode == 0 assert "click" in c.stdout + +@pytest.mark.project +@pytest.mark.sources +def test_no_sources_in_pipfile(pipenv_instance_pypi): + with pipenv_instance_pypi() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[packages] +pytest = "*" + """.strip() + f.write(contents) + c = p.pipenv('install --skip-lock') + assert c.returncode == 0