Browse Source

wip

feat/pkg_tree
Dr.Lt.Data 7 months ago
parent
commit
ef368d15ca
  1. 120
      tools/resolve_requirements.py

120
tools/resolve_requirements.py

@ -4,14 +4,25 @@ import re
from typing import Tuple, Optional from typing import Tuple, Optional
from packaging import version, specifiers from packaging import version, specifiers
import re import re
import traceback
import re
from typing import Tuple, Optional
def parse_pip_spec(spec: str) -> Tuple[str, Optional[str], Optional[str]]: def parse_pip_spec(spec: str) -> Tuple[str, Optional[str], Optional[str]]:
match = re.match(r'([^=<>!~\[\]]+)(\[.*\])?(.*)', spec) match = re.match(r'([A-Za-z0-9_-]+)(\[[^\]]+\])?(.*)', spec)
if match: if match:
package_name = match.group(1).strip() package_name = match.group(1).strip()
extras = match.group(2).strip()[1:-1] if match.group(2) else None extras = match.group(2)[1:-1] if match.group(2) else None
version_spec = match.group(3).strip() or None
version_spec = None
if match.group(3):
version_match = re.match(r'\(?([^\)]+)\)?', match.group(3).strip())
if version_match:
version_spec = version_match.group(1).strip()
return package_name, extras, version_spec return package_name, extras, version_spec
else: else:
raise ValueError(f"Invalid package spec: {spec}") raise ValueError(f"Invalid package spec: {spec}")
@ -24,14 +35,14 @@ def fetch_package_versions(package_name):
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
versions = data["releases"].keys() versions = data["releases"].keys()
print(f"Available versions of {package_name}:") print(f"Available versions of '{package_name}':")
return list(versions) return list(versions)
except requests.RequestException as e: except requests.RequestException as e:
print(f"Error fetching package versions: {e}") print(f"Error fetching package versions: {e}")
return [] return []
def fetch_required_dist(package_name, ver, extras): def fetch_required_dist(package_name, ver):
url = f"https://pypi.org/pypi/{package_name}/{ver}/json" url = f"https://pypi.org/pypi/{package_name}/{ver}/json"
try: try:
response = requests.get(url) response = requests.get(url)
@ -47,26 +58,23 @@ def fetch_required_dist(package_name, ver, extras):
dependencies = [] dependencies = []
for dist in requires_dist: for dist in requires_dist:
dist = dist.split(";") dist = dist.split(";")
match = re.match(r'([^=<>!~]+)(.*)', dist[0].strip())
pkg_name = None
ver_spec = None
dist_extra = None dist_extra = None
if len(dist) > 1:
match = re.match(r'extra\s*==\s*"([^"]*)"', dist[1].strip())
if match:
dist_extra = match.group(1).strip()
match = re.match(r'([^=<>!~]+)(.*)', dist[0].strip())
if match: if match:
pkg_name = match.group(1).strip() pkg_name = match.group(1).strip()
ver_spec = match.group(2).strip() or None ver_spec = match.group(2).strip() or None
if len(dist) > 1: if ver_spec is not None and ver_spec.endswith(')'):
match = re.match(r'extra\s*==\s*"([^"]*)"', dist[1].strip()) pass
if match:
dist_extra = match.group(1).strip()
if pkg_name is not None: dependencies.append((pkg_name, dist_extra, ver_spec))
if dist_extra is None:
dependencies.append((pkg_name, ver_spec))
elif dist_extra == extras:
dependencies.append((pkg_name, ver_spec))
# print(f"Processed dependencies for {package_name} version {ver}: {dependencies}") # print(f"Processed dependencies for {package_name} version {ver}: {dependencies}")
return dependencies return dependencies
@ -76,27 +84,79 @@ def fetch_required_dist(package_name, ver, extras):
def filter_versions(versions, spec): def filter_versions(versions, spec):
specifier = specifiers.SpecifierSet(spec) if spec else specifiers.SpecifierSet() try:
return [v for v in versions if v in specifier] specifier = specifiers.SpecifierSet(spec) if spec else specifiers.SpecifierSet()
res = []
for v in versions:
match = re.match(r'^([0-9.]+[0-9])', v)
if match:
if match.group(1) in specifier:
res.append(v)
if __name__ == "__main__": return res
if len(sys.argv) < 2: except Exception as e:
print("Usage: python script.py <package_name>") print(f"ERROR: {e}")
sys.exit(1) traceback.print_exc()
pkg_name, extras, version_spec = parse_pip_spec(sys.argv[1])
def filter_extras(required_dist, extras):
return [x for x in required_dist if x[1] is None or x[1] == extras]
resolved = {}
def resolve_pip_spec(pkg_name, extras, ver):
global resolved
required_dist = fetch_required_dist(pkg_name, ver)
resolved[(pkg_name, extras, ver)] = required_dist
return filter_extras(required_dist, extras)
def best_match(pkg_name, version_spec):
global resolved
candidate_versions = fetch_package_versions(pkg_name) candidate_versions = fetch_package_versions(pkg_name)
if version_spec: if version_spec:
candidate_versions = filter_versions(candidate_versions, version_spec) candidate_versions = filter_versions(candidate_versions, version_spec)
if candidate_versions: if candidate_versions:
best_version = max(candidate_versions, key=version.parse) return max(candidate_versions, key=version.parse)
print(f"Best matching version: {best_version}")
required_dist = fetch_required_dist(pkg_name, best_version, extras)
print(f"Required_dist: {required_dist}")
else: else:
print("No matching versions found.") print("No matching versions found.")
return None
def resolve_recursively(items):
worklist = items
while worklist:
pkg_name, extras, version_spec = worklist[0]
worklist = worklist[1:]
ver = best_match(pkg_name, version_spec)
if (pkg_name, extras, ver) in resolved:
continue
if ver is not None:
nexts = resolve_pip_spec(pkg_name, extras, ver)
worklist += nexts
def main():
global resolved
if len(sys.argv) < 2:
print("Usage: python script.py <package_name>")
sys.exit(1)
pkg_name, extras, version_spec = parse_pip_spec(sys.argv[1])
ver = best_match(pkg_name, version_spec)
required_dist = resolve_pip_spec(pkg_name, extras, ver)
resolve_recursively(required_dist)
print(f"Requirements: {resolved}")
if __name__ == "__main__":
main()
Loading…
Cancel
Save