Skip to content

Commit

Permalink
♻️ Remove --stop-early, fix recursion depth problem (#4)
Browse files Browse the repository at this point in the history
* 📝 Add package normalization caveat

* ♻️ Remove --stop-early, fix recursion depth problem
  • Loading branch information
ddelange committed Jan 9, 2020
1 parent a7d77c1 commit ea60e15
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 56 deletions.
27 changes: 11 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# pipgrip

[![Actions Status](https://github.com/ddelange/pipgrip/workflows/GH/badge.svg)](https://github.com/ddelange/pipgrip/actions) <!-- use badge.svg?branch=develop to deviate from default branch -->
[![Current Release Version](https://img.shields.io/github/release/ddelange/pipgrip.svg?logo=github)](https://github.com/ddelange/pipgrip/releases/latest)
[![pypi Version](https://img.shields.io/pypi/v/pipgrip.svg?logo=pypi&logoColor=white)](https://pypi.org/project/pipgrip/)
[![python](https://img.shields.io/pypi/pyversions/pipgrip.svg?logo=python&logoColor=white)](https://github.com/ddelange/pipgrip/releases/latest)
[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
Expand All @@ -23,14 +22,15 @@ pip install pipgrip

This package can be used to:
- Alleviate [Python dependency hell](https://medium.com/knerd/the-nine-circles-of-python-dependency-hell-481d53e3e025) by resolving the latest viable combination of required packages
- Render an exhaustive dependency tree for any given pip-compatible package(s)
- Render an exhaustive dependency tree for any given pip-compatible package(s) with `--tree`
- Detect version conflicts for given constraints and give human readable feedback about it
- Find dependency conflicts in local projects:
- `pipgrip --tree .`
- Install complex packages without worries using:
- ``pip install -U --no-deps `pipgrip --pipe aiobotocore[awscli]` ``
- Generate a lockfile with a complete working set of dependencies (see [known caveats](#known-caveats))
- `pipgrip aiobotocore[awscli] | pip install -U --no-deps -r /dev/stdin`
- `pipgrip --lock -tree aiobotocore[awscli] && pip install -U --no-deps -r ./pipgrip.lock`
- Check for dependency conflicts in local projects
- `pipgrip --tree .`

```sh
$ pipgrip --help
Expand All @@ -44,9 +44,7 @@ Options:
--json Output pins as json dict instead of newline-
separated pins.
--tree Output human readable dependency tree (top-down).
Overrides --stop-early.
--reversed-tree Output human readable dependency tree (bottom-up).
Overrides --stop-early.
--max-depth INTEGER Maximum tree rendering depth (defaults to -1).
--cache-dir PATH Use a custom cache dir.
--no-cache-dir Disable pip cache for the wheels downloaded by
Expand All @@ -55,10 +53,6 @@ Options:
https://pypi.org/simple).
--extra_index-url TEXT Extra URLs of package indexes to use in addition to
--index-url.
--stop-early Stop top-down recursion when constraints have been
solved. Will not result in exhaustive output when
dependencies are satisfied and further down the
branch no potential conflicts exist.
--pre Include pre-release and development versions. By
default, pip only finds stable versions.
-v, --verbose Control verbosity: -v will print cyclic dependencies
Expand All @@ -73,7 +67,7 @@ Exhaustive dependency trees without the need to install any packages (at most bu
```sh
$ pipgrip --tree pipgrip

pipgrip (0.0.1rc1)
pipgrip (0.0.1)
├── anytree (2.7.3)
│ └── six (1.13.0)
├── click (7.0)
Expand Down Expand Up @@ -146,11 +140,12 @@ keras==2.2.2 (2.2.2)

## Known caveats

- ``pip install -U --no-deps `pipgrip --stop-early package` `` may result in incomplete installation
- ``pip install -U `pipgrip --stop-early package` `` is unsafe and leaves room for interpretation by pip
- installing packages using pipgrip is not very intuitive, so maybe pipgrip needs a stable `--install` flag
- `--reversed-tree` isn't implemented yet
- ``pip install -U `pipgrip package` `` without `--no-deps` is unsafe while pip doesn't [yet](https://twitter.com/di_codes/status/1193980331004743680) have a built-in dependency resolver, and leaves room for interpretation by pip
- Package names are canonicalised in wheel metadata, resulting in e.g. `path.py -> path-py` and `keras_preprocessing -> keras-preprocessing` in output
- [VCS Support](https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support) isn't implemented yet
- `--reversed-tree` isn't implemented yet
- Since `pip install -r` does not accept `.` as requirement, `--pipe` format must be used when installing local projects
- Installing packages using pipgrip is not very intuitive, so maybe pipgrip needs a stable `--install` flag

## Development

Expand Down Expand Up @@ -190,5 +185,5 @@ pip install -e .

BSD 3-Clause License

Copyright (c) 2020, ddelange
Copyright (c) 2020, ddelange\
All rights reserved.
22 changes: 5 additions & 17 deletions src/pipgrip/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _recurse_dependencies(
return packages


def exhaustive_packages(source, decision_packages):
def build_tree(source, decision_packages):
tree_root = Node("root")
exhaustive = _recurse_dependencies(
source, decision_packages, source._root_dependencies, tree_root, tree_root
Expand Down Expand Up @@ -132,14 +132,12 @@ def render_tree(root_tree, max_depth):
help="Output pins as json dict instead of newline-separated pins.",
)
@click.option(
"--tree",
is_flag=True,
help="Output human readable dependency tree (top-down). Overrides --stop-early.",
"--tree", is_flag=True, help="Output human readable dependency tree (top-down).",
)
@click.option(
"--reversed-tree",
is_flag=True,
help="Output human readable dependency tree (bottom-up). Overrides --stop-early.",
help="Output human readable dependency tree (bottom-up).",
)
@click.option(
"--max-depth",
Expand All @@ -164,18 +162,14 @@ def render_tree(root_tree, max_depth):
@click.option(
"--index-url",
envvar="PIP_INDEX_URL",
default="https://pypi.org/simple",
help="Base URL of the Python Package Index (default https://pypi.org/simple).",
)
@click.option(
"--extra_index-url",
envvar="PIP_EXTRA_INDEX_URL",
help="Extra URLs of package indexes to use in addition to --index-url.",
)
@click.option(
"--stop-early",
is_flag=True,
help="Stop top-down recursion when constraints have been solved. Will not result in exhaustive output when dependencies are satisfied and further down the branch no potential conflicts exist.",
)
@click.option(
"--pre",
is_flag=True,
Expand All @@ -199,7 +193,6 @@ def main(
no_cache_dir,
index_url,
extra_index_url,
stop_early,
pre,
verbose,
):
Expand All @@ -223,8 +216,6 @@ def main(

if reversed_tree:
tree = True
if tree:
stop_early = False
if no_cache_dir:
cache_dir = tempfile.mkdtemp()

Expand All @@ -249,10 +240,7 @@ def main(

logger.debug(decision_packages)

if stop_early:
packages = OrderedDict((k, str(v)) for k, v in decision_packages.items())
else:
packages, root_tree = exhaustive_packages(source, decision_packages)
packages, root_tree = build_tree(source, decision_packages)

if lock:
with io.open(
Expand Down
11 changes: 7 additions & 4 deletions src/pipgrip/libs/mixology/version_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,15 @@ def solve(self): # type: () -> SolverResult
)
self._propagate(self._source.root)

i = 0
while not self.is_solved():
if not self._run() or i > 10:
packages_tried = 0
max_tries = 500
while True:
if packages_tried == max_tries:
raise SolverFailure("Stopping, {} packages tried.".format(max_tries))
if not self._run():
break

i += 1
packages_tried += 1

logger.info("Version solving took {:.3f} seconds.".format(time.time() - start))
logger.info("Tried {} solutions.".format(self._solution.attempted_solutions))
Expand Down
22 changes: 3 additions & 19 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def mock_get_available_versions(package, *args, **kwargs):
],
),
(
["--stop-early", "requests==2.22.0"],
["requests==2.22.0"],
[
"requests==2.22.0",
"chardet==3.0.4",
Expand Down Expand Up @@ -137,27 +137,11 @@ def mock_get_available_versions(package, *args, **kwargs):
["keras_preprocessing"],
["keras-preprocessing==1.1.0", "six==1.13.0", "numpy==1.16.6"],
),
# ( # py3 only, doesnt add aiohttp with --stop-early
# ["--stop-early", "keras"],
# [
# "aiobotocore==0.11.1",
# "async-generator==1.10",
# "botocore==1.13.14",
# "awscli==1.16.278",
# "s3transfer==0.2.1",
# "wrapt==1.11.2",
# "jmespath==0.9.4",
# "docutils==0.15.2",
# "pyyaml==5.1.2",
# "rsa==3.4.2",
# "colorama==0.4.1",
# ],
# ),
],
ids=(
"pipgrip pipgrip",
"--stop-early requests",
"--stop-early keras (cyclic)",
" requests",
" keras (cyclic)",
"--tree keras (cyclic)",
"keras_preprocessing (underscore)",
),
Expand Down

0 comments on commit ea60e15

Please sign in to comment.