Skip to content

Commit

Permalink
Implements requirements command as per #4959 (#5013)
Browse files Browse the repository at this point in the history
* Implements reqs command

* Add news document

* Process comments

* Rename newsfile

* Adds --dev-only and --hash args

* Linting fixes

Co-authored-by: Imre Persoonlijk <imre1@pop-os.localdomain>
  • Loading branch information
ImreC and Imre Persoonlijk authored Apr 5, 2022
1 parent 45f3237 commit 63ac0d0
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 16 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ atomicwrites = {version = "*", markers="sys_platform == 'win32'"}

[scripts]
tests = "bash ./run-tests.sh"
test = "pytest -vvs"

[pipenv]
allow_prereleases = true
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,25 @@ Magic shell completions are now enabled!
Use a lower-level pip command:
$ pipenv run pip freeze

Generate a requirements.txt file (including dev):
$ pipenv requirements --dev > requirements.txt

Commands:
check Checks for security vulnerabilities and against PEP 508 markers
provided in Pipfile.
clean Uninstalls all packages not specified in Pipfile.lock.
graph Displays currently–installed dependency graph information.
install Installs provided packages and adds them to Pipfile, or (if no
packages are given), installs all packages from Pipfile.
lock Generates Pipfile.lock.
open View a given module in your editor.
run Spawns a command installed into the virtualenv.
scripts Displays the shortcuts in the (optional) [scripts] section of
Pipfile.
shell Spawns a shell within the virtualenv.
sync Installs all packages specified in Pipfile.lock.
uninstall Un-installs a provided package and removes it from Pipfile.
check Checks for security vulnerabilities and against PEP 508 markers
provided in Pipfile.
clean Uninstalls all packages not specified in Pipfile.lock.
graph Displays currently–installed dependency graph information.
install Installs provided packages and adds them to Pipfile, or (if no
packages are given), installs all packages from Pipfile.
lock Generates Pipfile.lock.
open View a given module in your editor.
run Spawns a command installed into the virtualenv.
scripts Displays the shortcuts in the (optional) [scripts] section of
Pipfile.
shell Spawns a shell within the virtualenv.
sync Installs all packages specified in Pipfile.lock.
requirements Generates a requirements.txt compatible output directly from Pipfile.lock
uninstall Un-installs a provided package and removes it from Pipfile.

Locate the project:

Expand Down
39 changes: 37 additions & 2 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,19 +199,47 @@ development dependencies::
py==1.4.34
pytest==3.2.3

Finally, if you wish to generate a requirements file with only the
If you wish to generate a requirements file with only the
development requirements you can do that too, using the ``--dev-only``
flag::

$ pipenv lock -r --dev-only
py==1.4.34
pytest==3.2.3

Sometimes, you would want to generate a requirements file based on your current
environment. However, using pipenv lock -r will still do the locking process which
could update package versions. To keep the packages as is, use the ``--keep-outdated``
flag::

$ pipenv lock -r --keep-outdated
chardet==3.0.4
requests==2.18.4
certifi==2017.7.27.1
idna==2.6
urllib3==1.22

Note that using this approach, packages newly added to the Pipfile will still be
included in requirements.txt. If you really want to use Pipfile.lock and
Pipfile.lock only, you can generate the requirements using::
$ pipenv requirements
chardet==3.0.4
requests==2.18.4
certifi==2017.7.27.1
idna==2.6
urllib3==1.22

This will bypass the locking process completely. As with other commands,
passing ``--dev`` will include both the default and development dependencies.
Passing ``--dev-only`` will include only development dependencies and ``--hash`` will
add package hashes to the output for extra security.

The locked requirements are written to stdout, with shell output redirection
used to write them to a file::

$ pipenv lock -r > requirements.txt
$ pipenv lock -r --dev-only > dev-requirements.txt
$ pipenv requirements --dev > all-requirements.txt
$ cat requirements.txt
chardet==3.0.4
requests==2.18.4
Expand All @@ -221,7 +249,14 @@ used to write them to a file::
$ cat dev-requirements.txt
py==1.4.34
pytest==3.2.3

$ cat all-requirements.txt
chardet==3.0.4
requests==2.18.4
certifi==2017.7.27.1
idna==2.6
urllib3==1.22
py==1.4.34
pytest==3.2.3

☤ Detection of Security Vulnerabilities
---------------------------------------
Expand Down
1 change: 1 addition & 0 deletions news/4959.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implements a ``pipenv requirements`` command which generates a requirements.txt compatible output without locking.
32 changes: 32 additions & 0 deletions pipenv/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,5 +746,37 @@ def verify(state):
sys.exit(0)


@cli.command(
short_help="Generate a requirements.txt from Pipfile.lock.",
context_settings=CONTEXT_SETTINGS,
)
@option("--dev", is_flag=True, default=False, help="Also add development requirements.")
@option(
"--dev-only", is_flag=True, default=False, help="Only add development requirements."
)
@option("--hash", is_flag=True, default=False, help="Add package hashes.")
@pass_state
def requirements(state, dev=False, dev_only=False, hash=False):
lockfile = state.project.lockfile_content
for i, package_index in enumerate(lockfile["_meta"]["sources"]):
prefix = "-i" if i == 0 else "--extra-index-url"
echo(crayons.normal(" ".join([prefix, package_index["url"]])))
if not dev_only:
for req_name, value in lockfile["default"].items():
if hash:
hashes = [f" \\\n --hash={h}" for h in value.get("hashes", [])]
else:
hashes = []
echo(crayons.normal("".join([req_name, value["version"], *hashes])))
if dev or dev_only:
for req_name, value in lockfile["develop"].items():
if hash:
hashes = [f" \\\n --hash={h}" for h in value.get("hashes", [])]
else:
hashes = []
echo(crayons.normal("".join([req_name, value["version"], *hashes])))
sys.exit(0)


if __name__ == "__main__":
cli()
68 changes: 68 additions & 0 deletions tests/integration/test_requirements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import pytest


@pytest.mark.requirements
def test_requirements_generates_requirements_from_lockfile(PipenvInstance):
with PipenvInstance(chdir=True) as p:
packages = ('requests', '2.14.0')
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')
assert c.returncode == 0
assert f'{packages[0]}=={packages[1]}' in c.stdout
assert f'{dev_packages[0]}=={dev_packages[1]}' not in c.stdout

d = p.pipenv('requirements --dev')
assert d.returncode == 0
assert f'{packages[0]}=={packages[1]}' in d.stdout
assert f'{dev_packages[0]}=={dev_packages[1]}' in d.stdout

e = p.pipenv('requirements --dev-only')
assert e.returncode == 0
assert f'{packages[0]}=={packages[1]}' not in e.stdout
assert f'{dev_packages[0]}=={dev_packages[1]}' in e.stdout

e = p.pipenv('requirements --hash')
assert e.returncode == 0
assert f'{packages[0]}=={packages[1]}' in e.stdout
for value in p.lockfile['default'].values():
for hash in value['hashes']:
assert f' --hash={hash}' in e.stdout


@pytest.mark.requirements
def test_requirements_generates_requirements_from_lockfile_multiple_sources(PipenvInstance):
with PipenvInstance(chdir=True) as p:
packages = ('requests', '2.14.0')
dev_packages = ('flask', '0.12.2')
with open(p.pipfile_path, 'w') as f:
contents = f"""
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[[source]]
name = "other_source"
url = "https://some_other_source.org"
verify_ssl = true
[packages]
{packages[0]}= "=={packages[1]}"
[dev-packages]
{dev_packages[0]}= "=={dev_packages[1]}"
""".strip()
f.write(contents)
l = p.pipenv('lock')
assert l.returncode == 0
c = p.pipenv('requirements')
assert c.returncode == 0

assert '-i https://pypi.org/simple' in c.stdout
assert '--extra-index-url https://some_other_source.org' in c.stdout

0 comments on commit 63ac0d0

Please sign in to comment.