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

Support specifying which packages and versions to update. #409

Merged
merged 3 commits into from
Nov 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ 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 periodically re-run `pip-compile --upgrade` to update the packages.
as well.

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

To update a specific package to the latest or a specific version use the `--upgrade-package` or `-P` flag:

```console
$ pip-compile --upgrade-package flask # only update the flask package
$ pip-compile --upgrade-package flask --upgrade-package requests # update both the flask and requests packages
$ pip-compile -P flask -P requests==2.0.0 # update the flask package to the latest, and requests to v2.0.0
```

Example usage for `pip-sync`
============================
Expand Down
74 changes: 68 additions & 6 deletions piptools/scripts/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,16 @@ class PipCommand(pip.basecommand.Command):
help="Annotate results, indicating where dependencies come from")
@click.option('-U', '--upgrade', is_flag=True, default=False,
help='Try to upgrade all dependencies to their latest versions')
@click.option('-P', '--upgrade-package', nargs=1, multiple=True,
help="Specify particular packages to upgrade.")
@click.option('-o', '--output-file', nargs=1, type=str, default=None,
help=('Output file name. Required if more than one input file is given. '
'Will be derived from input file otherwise.'))
@click.option('--allow-unsafe', is_flag=True, default=False,
help="Pin packages considered unsafe: pip, setuptools & distribute")
@click.argument('src_files', nargs=-1, type=click.Path(exists=True, allow_dash=True))
def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
client_cert, trusted_host, header, index, annotate, upgrade,
client_cert, trusted_host, header, index, annotate, upgrade, upgrade_package,
output_file, allow_unsafe, src_files):
"""Compiles requirements.txt from requirements.in specs."""
log.verbose = verbose
Expand All @@ -81,6 +83,15 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
base_name, _, _ = src_files[0].rpartition('.')
dst_file = base_name + '.txt'

if upgrade and upgrade_package:
raise click.BadParameter('Only one of --upgrade or --upgrade-package can be provided as an argument.')

# Process the arguments to upgrade-package into name and version pairs.
upgrade_package_reqs = [
package.split('==') if '==' in package else (package, None)
for package in upgrade_package
]

###
# Setup
###
Expand Down Expand Up @@ -116,17 +127,26 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
pip_options, _ = pip_command.parse_args(pip_args)

session = pip_command._build_session(pip_options)
repository = PyPIRepository(pip_options, session)

repository = live_repository = PyPIRepository(pip_options, session)
local_repository = None
use_local_repo = not upgrade and os.path.exists(dst_file)

# Proxy with a LocalRequirementsRepository if --upgrade is not specified
# (= default invocation)
if not upgrade and os.path.exists(dst_file):
if use_local_repo:
existing_pins = dict()
ireqs = parse_requirements(dst_file, finder=repository.finder, session=repository.session, options=pip_options)
for ireq in ireqs:
if is_pinned_requirement(ireq):
existing_pins[name_from_req(ireq.req).lower()] = ireq
repository = LocalRequirementsRepository(existing_pins, repository)

for package, _ in upgrade_package_reqs:
if package not in existing_pins:
log.error("Asked to upgrade %s but it's not present in existing requirements. Quitting..." % package)
sys.exit(2)

repository = local_repository = LocalRequirementsRepository(existing_pins, repository)

log.debug('Using indexes:')
for index_url in repository.finder.index_urls:
Expand All @@ -143,6 +163,7 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
###

constraints = []
repository = local_repository if use_local_repo else live_repository
for src_file in src_files:
if src_file == '-':
# pip requires filenames and not files. Since we want to support
Expand All @@ -157,16 +178,57 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
constraints.extend(parse_requirements(
src_file, finder=repository.finder, session=repository.session, options=pip_options))

if upgrade_package:
existing_constraints = {c.req.key: c for c in constraints}
existing_requirements = existing_pins
upgraded_requirements = {}

# pip requires filenames, not files.
with tempfile.NamedTemporaryFile(mode='wt') as tmpfile:
for package, version in upgrade_package_reqs:
line = '{}\n'.format(package) if not version else '{}=={}\n'.format(package, version)
tmpfile.write(line)
tmpfile.flush()

upgrade_candidates = list(
parse_requirements(
tmpfile.name, finder=live_repository.finder, session=live_repository.session, options=pip_options))

for candidate in upgrade_candidates:
if candidate.req.key not in existing_constraints:
log.error("Asked to upgrade {} but it's not in the source file. Quitting..."
.format(candidate.req.key))
sys.exit(2)

constraint_candidate = existing_constraints[candidate.req.key]
if constraint_candidate.req.specifier:
log.error("Asked to upgrade {} but it's pinned to a version in the source file. Quitting..."
.format(candidate.req.key))
sys.exit(2)
else:
upgraded_requirements[candidate.req.key] = candidate

existing_requirements.update(upgraded_requirements)
constraints = existing_requirements.values()

try:
resolver = Resolver(constraints, repository, prereleases=pre,
clear_caches=rebuild)
repository = local_repository if use_local_repo and not upgrade_package else live_repository
resolver = Resolver(constraints, repository, prereleases=pre, clear_caches=rebuild)
results = resolver.resolve()
except PipToolsError as e:
log.error(str(e))
sys.exit(2)

log.debug('')

if upgrade_package and upgraded_requirements:
results_map = {c.req.key: c for c in results}
log.debug('Upgraded packages: ')
for upgraded_req in upgraded_requirements.values():
key = upgraded_req.req.key
log.debug(' {}'.format(results_map[key].req))
log.debug('')

##
# Output
##
Expand Down