Skip to content

Instantly share code, notes, and snippets.

@chipx86
Created December 22, 2021 01:42
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chipx86/5688b9a36453057c29b3e6c32743f52f to your computer and use it in GitHub Desktop.
Save chipx86/5688b9a36453057c29b3e6c32743f52f to your computer and use it in GitHub Desktop.
Find the earliest available Python packages matching given dependency ranges
"""Find the earliest available version of a list of Python dependencies.
This takes dependencies on the command line (package names with or without
version specifiers), looks up each in PyPI, and outputs a new list of
dependencies that specify the earliest version in each range.
See https://twitter.com/simonw/status/1473441058667130881 for the reason for
this script.
Example:
$ pypi-find-earliest.py dep1 'dep2~=X.Y' 'dep3>=1.2,<5.0' ...
$ pypi-find-earliest.py -f requirements.txt
"""
import sys
from pkg_resources import Requirement
from setuptools.package_index import PackageIndex
# Ideally we'd use argparse, but let's keep this simple.
if sys.argv[1] == '-r':
filename = sys.argv[2]
reqs = []
try:
# This is absolutely not a perfect parser. It's very naive and will
# probably get tripped up easily.
#
# pip has a parser available in
# pip._internal.req.req_file.RequirementsFileParser, but that requires
# some pip session setup that I'm not bothering hacking around here.
#
# In the real world, you'll want to improve this block of code here.
with open(filename, 'r') as fp:
for line in fp.readlines():
if line:
try:
req = Requirement.parse(line)
except ValueError:
# This line didn't parse, so we'll just ignore it.
# This isn't great. We might lose something.
continue
reqs.append(req)
except IOError as e:
sys.stderr.write('Unable to read %s: %s\n' % (filename, e))
sys.exit(1)
else:
reqs = [
Requirement.parse(_dep)
for _dep in sys.argv[1:]
]
index = PackageIndex()
for req in reqs:
index.find_packages(req)
# Normally, results will be in order of newest to oldest. We'll reverse
# that, and let the first that matches our requirement win.
for dist in reversed(index[req.key]):
if dist in req:
# First one wins.
print('%s==%s' % (dist.project_name, dist.version))
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment