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

Evaluate resolvelib #31

Closed
woodruffw opened this issue Sep 14, 2021 · 8 comments · Fixed by #41
Closed

Evaluate resolvelib #31

woodruffw opened this issue Sep 14, 2021 · 8 comments · Fixed by #41
Assignees

Comments

@woodruffw
Copy link
Member

woodruffw commented Sep 14, 2021

As a parallel or perhaps complementary track to #29: resolvelib provides an abstract interface for resolving dependencies that are produced by a client-supplied "provider" (presumably something like us querying the PyPI APIs).

This might be too generic of a library for our use case, but it might be useful if we go further into reworking pip-tools to supply a Python API.

PyPI Link: https://pypi.org/project/resolvelib/

@woodruffw
Copy link
Member Author

Some questions we want answered w/r/t resolvelib:

  • Does it resolve entire dependency trees using the given provider, or only specific versions of the supplied requirements?
  • Are there samples of using it against PyPI's APIs?

@woodruffw
Copy link
Member Author

@tetsuo-cpp
Copy link
Contributor

I pasted the PyPIProvider into a file and had a play round with resolvelib. A few things:

  • The PyPIProvider is not part of the package, the package itself just contains the core dependency resolution logic. If we want to use resolvelib, we'll have to rip that provider out and maintain it in pip-audit.
  • It works as you'd expect. For a requirement like flask==2.0.1, you get a result like this:
Result(mapping={'flask': <flask==2.0.1>, 'werkzeug': <werkzeug==2.0.1>, 'jinja2': <jinja2==3.0.1>, 'itsdangerous': <itsdangerous==2.0.1>, 'click': <click==8.0.1>, 'markupsafe': <markupsafe==2.0.1>}, graph=<resolvelib.structs.DirectedGraph object at 0x1064fb310>, criteria={'flask': Criterion((<Requirement('flask==2.0.1')>, via=None)), 'werkzeug': Criterion((<Requirement('Werkzeug>=2.0')>, via=<flask==2.0.1>)), 'jinja2': Criterion((<Requirement('Jinja2>=3.0')>, via=<flask==2.0.1>)), 'itsdangerous': Criterion((<Requirement('itsdangerous>=2.0')>, via=<flask==2.0.1>)), 'click': Criterion((<Requirement('click>=7.1.2')>, via=<flask==2.0.1>)), 'markupsafe': Criterion((<Requirement('MarkupSafe>=2.0')>, via=<jinja2==3.0.1>))})

So as you can see, it also picks up the transitive dependencies like markupsafe which comes from the jinja2 dependency.

  • One more thing I noticed is that when I try with ansible, I get this error:
Traceback (most recent call last):
  File "/Users/tetsuo/Workspace/resolvelib/env/lib/python3.9/site-packages/resolvelib/resolvers.py", line 341, in resolve
    self._add_to_criteria(self.state.criteria, r, parent=None)
  File "/Users/tetsuo/Workspace/resolvelib/env/lib/python3.9/site-packages/resolvelib/resolvers.py", line 173, in _add_to_criteria
    raise RequirementsConflicted(criterion)
resolvelib.resolvers.RequirementsConflicted: Requirements conflict: <Requirement('ansible')>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/tetsuo/Workspace/resolvelib/resolve_test.py", line 256, in <module>
    result = resolver.resolve(requirements)
  File "/Users/tetsuo/Workspace/resolvelib/env/lib/python3.9/site-packages/resolvelib/resolvers.py", line 472, in resolve
    state = resolution.resolve(requirements, max_rounds=max_rounds)
  File "/Users/tetsuo/Workspace/resolvelib/env/lib/python3.9/site-packages/resolvelib/resolvers.py", line 343, in resolve
    raise ResolutionImpossible(e.criterion.information)
resolvelib.resolvers.ResolutionImpossible: [RequirementInformation(requirement=<Requirement('ansible')>, parent=None)]

I haven't looked too deeply into this so I'm not sure whether this is about the PyPI provider or the core resolver logic. I'm sure we can figure it out if we decide to use the library, but I figured I'd mention it just to warn that it might not be a "plug and play" deal.

@tetsuo-cpp
Copy link
Contributor

@woodruffw
To answer your questions specifically:

Does it resolve entire dependency trees using the given provider, or only specific versions of the supplied requirements?

It seems to get the entire dependency tree. I think the example I posted above of flask shows it because it includes transitive dependencies by checking flask => jinja2 => markupsafe.

Are there samples of using it against PyPI's APIs?

Yep, as you linked, it's in the examples/ directory. As I mentioned in the comment above, there are some details that we need to figure out if we decide to go down that path.

@woodruffw
Copy link
Member Author

Awesome, thanks for digging into that. Adapting the PyPIProvider seems like a promising route given the information above.

I'm not sure if it's related, but the source of the problem with ansible might be that the current get_project_from_pypi helper only handles "wheel" distributions, not source distributions. It's possible that ansible itself or one of its deps is a source-dist only distribution, causing the resolution to fail.

@woodruffw
Copy link
Member Author

woodruffw commented Sep 16, 2021

Yeah, looking at the simple index for ansible, they've only ever done source distributions for stable releases: https://pypi.org/simple/ansible/

...so the current provider ignores all of the other versions. Our task here would be to support source distributions as well, which (I think) involves just two steps:

  • Updating the PyPIProvider to not filter out sdists
  • Adding an equivalent to get_metadata_for_wheel for sdists

Edit: For sdists, the corresponding metadata file is PKG-INFO. In other words, the resolution logic should be:

  • Fetch all compatible versions
  • Prioritize wheels over source distributions
  • If $dist is a wheel, grab its metadata from .dist-info/METADATA
  • If $dist is a source dist, grab its metadata from PKG-INFO

@woodruffw
Copy link
Member Author

(We shouldn't need to parse the metadata ourselves, but here's the relevant most recent PEP anyways: https://www.python.org/dev/peps/pep-0566/)

@tetsuo-cpp
Copy link
Contributor

I have a PR open with just the basics + unit tests. If we decide to merge it, I intend to open issues for:

  • Supporting extras
  • Supporting source distributions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants