From 24691a5f1884350d2e5288972f6697c3ac95add8 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Mon, 20 May 2024 15:52:35 +0900 Subject: [PATCH 1/4] Add --root-only subcommand to requirements --- pipenv/cli/command.py | 15 +++++++- pipenv/routines/requirements.py | 27 +++++++++++++-- tests/integration/test_requirements.py | 48 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index a755346d55..3f7d9349b0 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -744,9 +744,21 @@ def verify(state): default="", help="Only add requirement of the specified categories.", ) +@option( + "--root-only", + is_flag=True, + default=False, + help="Only include root dependencies from Pipfile.", +) @pass_state def requirements( - state, dev=False, dev_only=False, hash=False, exclude_markers=False, categories="" + state, + dev=False, + dev_only=False, + hash=False, + exclude_markers=False, + categories="", + root_only=False, ): from pipenv.routines.requirements import generate_requirements @@ -757,6 +769,7 @@ def requirements( include_hashes=hash, include_markers=not exclude_markers, categories=categories, + root_only=root_only, ) diff --git a/pipenv/routines/requirements.py b/pipenv/routines/requirements.py index 32c5d6705d..f732aa1332 100644 --- a/pipenv/routines/requirements.py +++ b/pipenv/routines/requirements.py @@ -13,8 +13,10 @@ def generate_requirements( include_hashes=False, include_markers=True, categories="", + root_only=False, ): lockfile = project.load_lockfile(expand_env_vars=False) + pipfile_root_package_names = project.pipfile_package_names["combined"] for i, package_index in enumerate(lockfile["_meta"]["sources"]): prefix = "-i" if i == 0 else "--extra-index-url" @@ -26,12 +28,31 @@ def generate_requirements( if categories_list: for category in categories_list: category = get_lockfile_section_using_pipfile_category(category.strip()) - deps.update(lockfile.get(category, {})) + category_deps = lockfile.get(category, {}) + if root_only: + category_deps = { + k: v + for k, v in category_deps.items() + if k in pipfile_root_package_names + } + deps.update(category_deps) else: if dev or dev_only: - deps.update(lockfile["develop"]) + dev_deps = lockfile["develop"] + if root_only: + dev_deps = { + k: v for k, v in dev_deps.items() if k in pipfile_root_package_names + } + deps.update(dev_deps) if not dev_only: - deps.update(lockfile["default"]) + default_deps = lockfile["default"] + if root_only: + default_deps = { + k: v + for k, v in default_deps.items() + if k in pipfile_root_package_names + } + deps.update(default_deps) pip_installable_lines = requirements_from_lockfile( deps, include_hashes=include_hashes, include_markers=include_markers diff --git a/tests/integration/test_requirements.py b/tests/integration/test_requirements.py index ed0cdc5f25..ef98f4a29d 100644 --- a/tests/integration/test_requirements.py +++ b/tests/integration/test_requirements.py @@ -115,6 +115,54 @@ def test_requirements_generates_requirements_from_lockfile_from_categories(pipen assert f'{doc_packages[0]}=={doc_packages[1]}' in d.stdout +@pytest.mark.requirements +def test_requirements_generates_requirements_with_root_only(pipenv_instance_pypi): + with pipenv_instance_pypi() as p: + packages = ('requests', '2.31.0') + sub_packages = ('urllib3', '2.2.1') # subpackages not explicitly written in Pipfile. + dev_packages = ('flask', '0.12.2') + + with open(p.pipfile_path, 'w') as f: + contents = f""" + [packages] + {packages[0]} = "=={packages[1]}" + [dev-packages] + {dev_packages[0]} = "=={dev_packages[1]}" + """.strip() + f.write(contents) + p.pipenv('lock') + + c = p.pipenv('requirements --root-only') + assert c.returncode == 0 + assert f'{packages[0]}=={packages[1]}' in c.stdout + assert f'{sub_packages[0]}=={sub_packages[1]}' not in c.stdout + assert f'{dev_packages[0]}=={dev_packages[1]}' not in c.stdout + + d = p.pipenv('requirements --dev --root-only') + assert d.returncode == 0 + assert f'{packages[0]}=={packages[1]}' in d.stdout + assert f'{sub_packages[0]}=={sub_packages[1]}' not in d.stdout + assert f'{dev_packages[0]}=={dev_packages[1]}' in d.stdout + + e = p.pipenv('requirements --dev-only --root-only') + assert e.returncode == 0 + assert f'{packages[0]}=={packages[1]}' not in e.stdout + assert f'{sub_packages[0]}=={sub_packages[1]}' not in e.stdout + assert f'{dev_packages[0]}=={dev_packages[1]}' in e.stdout + + f = p.pipenv('requirements --categories=dev-packages --root-only') + assert f.returncode == 0 + assert f'{packages[0]}=={packages[1]}' not in f.stdout + assert f'{sub_packages[0]}=={sub_packages[1]}' not in f.stdout + assert f'{dev_packages[0]}=={dev_packages[1]}' in f.stdout + + g = p.pipenv('requirements --categories=packages,dev-packages --root-only') + assert g.returncode == 0 + assert f'{packages[0]}=={packages[1]}' in g.stdout + assert f'{sub_packages[0]}=={sub_packages[1]}' not in g.stdout + assert f'{dev_packages[0]}=={dev_packages[1]}' in g.stdout + + @pytest.mark.requirements def test_requirements_with_git_requirements(pipenv_instance_pypi): req_hash = '3264a0046e1aa3c0a813335286ebdbc651f58b13' From 9778a59808af544ee1ab25928d00f9ae1f48cbfe Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Thu, 23 May 2024 20:06:16 +0900 Subject: [PATCH 2/4] Fix name of option to --from-pipfile --- pipenv/cli/command.py | 8 ++++---- pipenv/routines/requirements.py | 12 +++++++----- tests/integration/test_requirements.py | 12 ++++++------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 3f7d9349b0..a867aee02e 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -745,10 +745,10 @@ def verify(state): help="Only add requirement of the specified categories.", ) @option( - "--root-only", + "--from-pipfile", is_flag=True, default=False, - help="Only include root dependencies from Pipfile.", + help="Only include dependencies from Pipfile.", ) @pass_state def requirements( @@ -758,7 +758,7 @@ def requirements( hash=False, exclude_markers=False, categories="", - root_only=False, + from_pipfile=False, ): from pipenv.routines.requirements import generate_requirements @@ -769,7 +769,7 @@ def requirements( include_hashes=hash, include_markers=not exclude_markers, categories=categories, - root_only=root_only, + from_pipfile=from_pipfile, ) diff --git a/pipenv/routines/requirements.py b/pipenv/routines/requirements.py index f732aa1332..08c5a3703a 100644 --- a/pipenv/routines/requirements.py +++ b/pipenv/routines/requirements.py @@ -13,7 +13,7 @@ def generate_requirements( include_hashes=False, include_markers=True, categories="", - root_only=False, + from_pipfile=False, ): lockfile = project.load_lockfile(expand_env_vars=False) pipfile_root_package_names = project.pipfile_package_names["combined"] @@ -29,7 +29,7 @@ def generate_requirements( for category in categories_list: category = get_lockfile_section_using_pipfile_category(category.strip()) category_deps = lockfile.get(category, {}) - if root_only: + if from_pipfile: category_deps = { k: v for k, v in category_deps.items() @@ -39,14 +39,16 @@ def generate_requirements( else: if dev or dev_only: dev_deps = lockfile["develop"] - if root_only: + if from_pipfile: dev_deps = { - k: v for k, v in dev_deps.items() if k in pipfile_root_package_names + k: v + for k, v in dev_deps.items() + if k in pipfile_root_package_names } deps.update(dev_deps) if not dev_only: default_deps = lockfile["default"] - if root_only: + if from_pipfile: default_deps = { k: v for k, v in default_deps.items() diff --git a/tests/integration/test_requirements.py b/tests/integration/test_requirements.py index ef98f4a29d..4a64a0bbb4 100644 --- a/tests/integration/test_requirements.py +++ b/tests/integration/test_requirements.py @@ -116,7 +116,7 @@ def test_requirements_generates_requirements_from_lockfile_from_categories(pipen @pytest.mark.requirements -def test_requirements_generates_requirements_with_root_only(pipenv_instance_pypi): +def test_requirements_generates_requirements_with_from_pipfile(pipenv_instance_pypi): with pipenv_instance_pypi() as p: packages = ('requests', '2.31.0') sub_packages = ('urllib3', '2.2.1') # subpackages not explicitly written in Pipfile. @@ -132,31 +132,31 @@ def test_requirements_generates_requirements_with_root_only(pipenv_instance_pypi f.write(contents) p.pipenv('lock') - c = p.pipenv('requirements --root-only') + c = p.pipenv('requirements --from-pipfile') assert c.returncode == 0 assert f'{packages[0]}=={packages[1]}' in c.stdout assert f'{sub_packages[0]}=={sub_packages[1]}' not in c.stdout assert f'{dev_packages[0]}=={dev_packages[1]}' not in c.stdout - d = p.pipenv('requirements --dev --root-only') + d = p.pipenv('requirements --dev --from-pipfile') assert d.returncode == 0 assert f'{packages[0]}=={packages[1]}' in d.stdout assert f'{sub_packages[0]}=={sub_packages[1]}' not in d.stdout assert f'{dev_packages[0]}=={dev_packages[1]}' in d.stdout - e = p.pipenv('requirements --dev-only --root-only') + e = p.pipenv('requirements --dev-only --from-pipfile') assert e.returncode == 0 assert f'{packages[0]}=={packages[1]}' not in e.stdout assert f'{sub_packages[0]}=={sub_packages[1]}' not in e.stdout assert f'{dev_packages[0]}=={dev_packages[1]}' in e.stdout - f = p.pipenv('requirements --categories=dev-packages --root-only') + f = p.pipenv('requirements --categories=dev-packages --from-pipfile') assert f.returncode == 0 assert f'{packages[0]}=={packages[1]}' not in f.stdout assert f'{sub_packages[0]}=={sub_packages[1]}' not in f.stdout assert f'{dev_packages[0]}=={dev_packages[1]}' in f.stdout - g = p.pipenv('requirements --categories=packages,dev-packages --root-only') + g = p.pipenv('requirements --categories=packages,dev-packages --from-pipfile') assert g.returncode == 0 assert f'{packages[0]}=={packages[1]}' in g.stdout assert f'{sub_packages[0]}=={sub_packages[1]}' not in g.stdout From 214b7d32017fa9bfa562eaeb3b9fb33b81ed9a75 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Fri, 24 May 2024 10:36:36 +0900 Subject: [PATCH 3/4] Fix linting issue --- pipenv/routines/requirements.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pipenv/routines/requirements.py b/pipenv/routines/requirements.py index 08c5a3703a..e25c8d0ad8 100644 --- a/pipenv/routines/requirements.py +++ b/pipenv/routines/requirements.py @@ -41,9 +41,7 @@ def generate_requirements( dev_deps = lockfile["develop"] if from_pipfile: dev_deps = { - k: v - for k, v in dev_deps.items() - if k in pipfile_root_package_names + k: v for k, v in dev_deps.items() if k in pipfile_root_package_names } deps.update(dev_deps) if not dev_only: From 6ec19bb74f3cf1b78f1a68626ed1b2ff103874d7 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Fri, 24 May 2024 10:48:32 +0900 Subject: [PATCH 4/4] Add news fragment --- news/6156.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/6156.feature.rst diff --git a/news/6156.feature.rst b/news/6156.feature.rst new file mode 100644 index 0000000000..525c98d88d --- /dev/null +++ b/news/6156.feature.rst @@ -0,0 +1 @@ +The ``pipenv requirements`` subcommand now supports the ``--from-pipfile`` flag. When this flag is used, the requirements file will only include the packages explicitly listed in the Pipfile, excluding any sub-packages.