Repository: pypa/pipenv
Pull request: Add support for multiple categories in exported requirements
Closes: https://github.com/pypa/pipenv/issues/5397

This adds a `category` option to the `requirements` command. If specified a category, it will only generate the requirements of that category.

Problem statement:
Add support for multiple categories in exported requirements
Now that Pipfile supports multiple categories, it would be nice if I could create different requirements files for these categories.
Let's say my Pipfile has categoires: tests, documentation, foo, bar.
I would like to create the following files: requirements-tests.txt, requirements-documentation.txt, requirements-foo.txt and requirements-bar.txt.

I would like to export them with:
```
pipenv requirements --category tests > requirements-tests.txt
```

And so on ...

However, right now only these options are supported:

```
 $ pipenv requirements -h
Usage: pipenv requirements [OPTIONS]

Options:
  --dev              Also add development requirements.
  --dev-only         Only add development requirements.
  --hash             Add package hashes.
  --exclude-markers  Exclude markers.
  -h, --help         Show this message and exit.
```

Hints from issue discussion:
I would find this immensely helpful as well.
HI @oz123,
I would like to take this up. First time here, can you help me on how to start with this?
I would start with the following:

Add the command line options.
Look where this leads you inside pipenv. 
You'll bump into a library we maintain called requirementslib that does the real work of parsing Pipfile. Change the code here. 
One your prototype works, you'll need (probably) to open a PR in requirementslib.
It's probably not a good first issue. But you can try 💪
Thank you.
I would like to give it a try.

Commit message: Cleanup

Patch to review:
diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py
index 7e642eab94..a8872338da 100644
--- a/pipenv/cli/command.py
+++ b/pipenv/cli/command.py
@@ -740,8 +740,9 @@ def verify(state):
 )
 @option("--hash", is_flag=True, default=False, help="Add package hashes.")
 @option("--exclude-markers", is_flag=True, default=False, help="Exclude markers.")
+@option("--category", is_flag=False, default='', help="Only add requirement of the specified category.")
 @pass_state
-def requirements(state, dev=False, dev_only=False, hash=False, exclude_markers=False):
+def requirements(state, dev=False, dev_only=False, hash=False, exclude_markers=False, category=''):
 
     from pipenv.utils.dependencies import convert_deps_to_pip
 
@@ -753,10 +754,13 @@ def requirements(state, dev=False, dev_only=False, hash=False, exclude_markers=F
 
     deps = {}
 
-    if dev or dev_only:
-        deps.update(lockfile["develop"])
-    if not dev_only:
-        deps.update(lockfile["default"])
+    if category:
+        deps.update(lockfile.get(category, {}))
+    else:
+        if dev or dev_only:
+            deps.update(lockfile["develop"])
+        if not dev_only:
+            deps.update(lockfile["default"])
 
     pip_deps = convert_deps_to_pip(
         deps,
diff --git a/tests/integration/test_requirements.py b/tests/integration/test_requirements.py
index 3ad1e97805..fa8ddf6d47 100644
--- a/tests/integration/test_requirements.py
+++ b/tests/integration/test_requirements.py
@@ -72,6 +72,41 @@ def test_requirements_generates_requirements_from_lockfile_multiple_sources(pipe
         assert '--extra-index-url https://some_other_source.org' in c.stdout
 
 
+@pytest.mark.requirements
+def test_requirements_generates_requirements_from_lockfile_from_a_category(pipenv_instance_private_pypi):
+    with pipenv_instance_private_pypi(chdir=True) as p:
+        packages = ('six', '1.12.0')
+        dev_packages = ('itsdangerous', '1.1.0')
+        test_packages = ('pytest', '7.1.3')
+        with open(p.pipfile_path, 'w') as f:
+            contents = f"""
+            [[source]]
+            name = "pypi"
+            url = "https://pypi.org/simple"
+            verify_ssl = true
+            [packages]
+            {packages[0]}= "=={packages[1]}"
+            [dev-packages]
+            {dev_packages[0]}= "=={dev_packages[1]}"
+            [test-packages]
+            {test_packages[0]}= "=={test_packages[1]}"
+            """.strip()
+            f.write(contents)
+        l = p.pipenv('lock')
+        assert l.returncode == 0
+        
+        c = p.pipenv('requirements --dev-only')
+        assert c.returncode == 0
+        assert f'{packages[0]}=={packages[1]}' not in c.stdout
+        assert f'{test_packages[0]}=={test_packages[1]}' not in c.stdout
+        assert f'{dev_packages[0]}=={dev_packages[1]}' in c.stdout
+
+        d = p.pipenv('requirements --category test')
+        assert d.returncode == 0
+        assert f'{packages[0]}=={packages[1]}' not in d.stdout
+        assert f'{dev_packages[0]}=={dev_packages[1]}' not in c.stdout
+        assert f'{test_packages[0]}=={test_packages[1]}' in d.stdout
+
 @pytest.mark.requirements
 def test_requirements_with_git_requirements(pipenv_instance_pypi):
     req_name, req_hash = 'example-repo', 'cc858e89f19bc0dbd70983f86b811ab625dc9292'


Code review comments:
diff_hunk: @@ -740,8 +740,9 @@ def verify(state):
 )
 @option("--hash", is_flag=True, default=False, help="Add package hashes.")
 @option("--exclude-markers", is_flag=True, default=False, help="Exclude markers.")
+@option("--category", is_flag=False, default='', help="Only add requirement of the specified category.")
 @pass_state
-def requirements(state, dev=False, dev_only=False, hash=False, exclude_markers=False):
+def requirements(state, dev=False, dev_only=False, hash=False, exclude_markers=False, category=''):
path: pipenv/cli/command.py
text: @user1:
Let's use the `categories` option that we use in the other parts of the CLI for consistency, this can accept one or multiple categories.

@author:
@user1 Updated the PR.