Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build requirements from setup.py #418

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,46 @@ $ pip install pip-tools
Example usage for `pip-compile`
===============================

Suppose you have a Flask project, and want to pin it for production. Write the
following line to a file:
Requirements from setup.py
--------------------------

Suppose you have a Flask project, and want to pin it for production. If you have a `setup.py` with
`install_requires=['Flask']`, then run `pip-compile` without any arguments:
```console
$ pip-compile
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file requirements.txt setup.py
#
flask==0.10.1
itsdangerous==0.24 # via flask
jinja2==2.7.3 # via flask
markupsafe==0.23 # via jinja2
werkzeug==0.10.4 # via flask
```

`pip-compile` will produce your `requirements.txt`, with all the Flask dependencies
(and all underlying dependencies) pinned. You should put your requirements file under version control.

Without setup.py
----------------

If you don't use `setup.py` ([you should][1]), you can write the following line to a file:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should

This is an opinion not everyone would agree with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dirn I read the post you reference about an hour ago while looking for a suitable URL for the link in question. I don't think it says we should or should not use setup.py, I think the point was more that you shouldn't do stupid stuff like install_requires=list(open('requirements.txt')). Did I miss something?


# requirements.in
Flask

Now, run `pip-compile requirements.in`:
This time, run `pip-compile requirements.in`:

```console
$ pip-compile requirements.in
#
# This file is autogenerated by pip-compile
# Make changes in requirements.in, then run this to update:
# To update, run:
#
# pip-compile requirements.in
# pip-compile --output-file requirements.txt requirements.in
#
flask==0.10.1
itsdangerous==0.24 # via flask
Expand All @@ -47,8 +72,10 @@ werkzeug==0.10.4 # via flask
```

And it will produce your `requirements.txt`, with all the Flask dependencies
(and all underlying dependencies) pinned. Put this file under version control
as well.
(and all underlying dependencies) pinned. Don't forget to put this file under version control as well.

Updating requirements
---------------------

To update all packages, periodically re-run `pip-compile --upgrade`.

Expand All @@ -60,6 +87,8 @@ $ pip-compile --upgrade-package flask --upgrade-package requests # update both
$ pip-compile -P flask -P requests==2.0.0 # update the flask package to the latest, and requests to v2.0.0
```

[1]: https://packaging.python.org/distributing/#setup-py

Example usage for `pip-sync`
============================

Expand Down
24 changes: 18 additions & 6 deletions piptools/scripts/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
log.verbose = verbose

if len(src_files) == 0:
if not os.path.exists(DEFAULT_REQUIREMENTS_FILE):
if os.path.exists(DEFAULT_REQUIREMENTS_FILE):
src_files = (DEFAULT_REQUIREMENTS_FILE,)
elif os.path.exists('setup.py'):
src_files = ('setup.py',)
if not output_file:
output_file = 'requirements.txt'
else:
raise click.BadParameter(("If you do not specify an input file, "
"the default is {}").format(DEFAULT_REQUIREMENTS_FILE))
src_files = (DEFAULT_REQUIREMENTS_FILE,)
"the default is {} or setup.py").format(DEFAULT_REQUIREMENTS_FILE))

if len(src_files) == 1 and src_files[0] == '-':
if not output_file:
Expand Down Expand Up @@ -166,12 +171,19 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,

constraints = []
for src_file in src_files:
if src_file == '-':
is_setup_file = os.path.basename(src_file) == 'setup.py'
if is_setup_file or src_file == '-':
# pip requires filenames and not files. Since we want to support
# piping from stdin, we need to briefly save the input from stdin
# to a temporary file and have pip read that.
# to a temporary file and have pip read that. also used for
# reading requirements from install_requires in setup.py.
with tempfile.NamedTemporaryFile(mode='wt') as tmpfile:
tmpfile.write(sys.stdin.read())
if is_setup_file:
from distutils.core import run_setup
dist = run_setup(src_file)
tmpfile.write('\n'.join(dist.install_requires))
else:
tmpfile.write(sys.stdin.read())
tmpfile.flush()
constraints.extend(parse_requirements(
tmpfile.name, finder=repository.finder, session=repository.session, options=pip_options))
Expand Down