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

Replacement for pkg_resources.require()? #234

Closed
sinoroc opened this issue Oct 11, 2024 · 4 comments
Closed

Replacement for pkg_resources.require()? #234

sinoroc opened this issue Oct 11, 2024 · 4 comments

Comments

@sinoroc
Copy link

sinoroc commented Oct 11, 2024

Is your feature request related to a problem? Please describe.

With setuptools' pkg_resources being deprecated, some are looking for replacement solutions.

Describe the solution you'd like

It would be nice if distlib offered an alternative to pkg_resources.require().

Describe alternatives you've considered

The next best thing right now is probably a mix of importlib.metadata and packaging, but as far as I understood it does not get all the way there.

Additional context


CC: @smheidrich

@vsajip
Copy link
Collaborator

vsajip commented Oct 15, 2024

I made a small change to the repo to refactor a function. With a couple of undocumented functions, you can (I think) do what you want. I did a simple smoke test:

$ python3 -m venv venv1
$ python3 -m venv venv2
$ venv1/bin/pip install textual 2>&1 > /dev/null
$ venv2/bin/pip install textual[syntax] 2>&1 > /dev/null

The textual package has only one extra, syntax.
Now, the following script test_script6.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 Vinay Sajip. MIT-licensed.
#
import argparse
import os
import sys

DEBUGGING = 'PY_DEBUG' in os.environ

from distlib.database import DistributionPath
from distlib.locators import DistPathLocator
# Undocumented functions imported here
from distlib.markers import interpret_parsed
from distlib.util import parse_requirement

def main():
    adhf = argparse.ArgumentDefaultsHelpFormatter
    ap = argparse.ArgumentParser(formatter_class=adhf)
    aa = ap.add_argument
    aa('reqt', metavar='REQT', help='Requirement')
    options = ap.parse_args()
    distpath = DistributionPath(include_egg=True)
    locator = DistPathLocator(distpath)
    sreq = options.reqt
    r = parse_requirement(sreq)
    failures = []
    d = locator.locate(sreq)
    if d is None:
        failures.append(r.name)
    else:
        print(d)
        for s in d.run_requires:
            dr = parse_requirement(s)
            if dr.marker:
                if r.extras:
                    for e in r.extras:
                        c = {'extra': e}
                        try:
                            v = interpret_parsed(dr.marker, c)
                        except Exception:
                            v = False
                        if v:
                            dd = locator.locate(dr.name)
                            if dd:
                                print(dd)
                            else:
                                failures.append(dr.name)
    if failures:
        print('Not found: %s' % failures)

if __name__ == '__main__':
    try:
        rc = main()
    except KeyboardInterrupt:
        rc = 2
    except Exception as e:
        if DEBUGGING:
            s = ' %s:' % type(e).__name__
        else:
            s = ''
        sys.stderr.write('Failed:%s %s\n' % (s, e))
        if DEBUGGING: import traceback; traceback.print_exc()
        rc = 1
    sys.exit(rc)

Seems to give the expected results:

$ venv1/bin/python test_script6.py foo
Not found: ['foo']
$ venv1/bin/python test_script6.py textual
textual 0.83.0
$ venv1/bin/python test_script6.py textual[syntax]
textual 0.83.0
Not found: ['tree-sitter-languages', 'tree-sitter']
$ venv2/bin/python test_script6.py foo
Not found: ['foo']
$ venv2/bin/python test_script6.py textual
textual 0.83.0
$ venv2/bin/python test_script6.py textual[syntax]
textual 0.83.0
tree-sitter 0.20.4
tree-sitter-languages 1.10.2

You can try out this logic with the latest repo and give me feedback. I can document the undocumented functions and cut a release in due course.

@smheidrich
Copy link

As far as I can tell, this takes pretty much the same amount of effort as it does currently with packaging and importlib.metadata, doesn't it? The snippet seems of a similar length as the one from pypa/packaging.python.org#1606 (comment) and the primitive operations used seem very similar (unless I misunderstood any of them):

Task importlib.metadata / packaging distlib
Parse a requirement string into its components packging.requirements.Reqirement parse_requirement
Find out if a package is installed in the current env, allow retrieving its version importlib.metadata.distribution Locator.locate
Evaluate a marker Requirement.marker.evaluate markers.interpret_parsed

Meanwhile, with the old pkg_resources.require it's just a single line that takes care of all of that lower-level stuff behind the scenes, and I guess what this issue was meant to be about is whether a replacement for that should make it into this project.

But as I wrote in pypa/packaging-problems#664 (comment), I personally don't think distlib is the right place for this in any case.

@vsajip
Copy link
Collaborator

vsajip commented Oct 16, 2024

I personally don't think distlib is the right place for this

I agree - in general, the things one might want to do in different branches of the above logic will depend on the exact use case. I put together the above to see how straightforward it would be to do using distlib only. And pkg_resources.require, while indeed simple, doesn't seem to allow quite as much flexibility as might be needed in some situations. Still, if that's all that's needed, fair enough.

@sinoroc
Copy link
Author

sinoroc commented Oct 16, 2024

I only wanted to get the discussion started, I do not particularly feel one way or another regarding this topic. Feel free to close whenever you feel like the discussion has run its course. :)

@vsajip vsajip closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2024
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

No branches or pull requests

3 participants