| | import glob |
| | import io |
| | import json |
| | import os.path |
| | import re |
| |
|
| | import configparser |
| | import setuptools |
| | import pip._internal.req.req_file |
| | from pip._internal.network.session import PipSession |
| | from pip._internal.req.constructors import ( |
| | install_req_from_line, |
| | install_req_from_parsed_requirement, |
| | ) |
| |
|
| | from packaging.requirements import InvalidRequirement, Requirement |
| | |
| | |
| | import tomli |
| |
|
| | |
| | |
| | COMMENT_RE = re.compile(r'(^|\s+)#.*$') |
| |
|
| |
|
| | def parse_pep621_pep735_dependencies(pyproject_path): |
| | with open(pyproject_path, "rb") as file: |
| | project_toml = tomli.load(file) |
| |
|
| | def version_from_req(specifier_set): |
| | if (len(specifier_set) == 1 and |
| | next(iter(specifier_set)).operator in {"==", "==="}): |
| | return next(iter(specifier_set)).version |
| |
|
| | def parse_requirement(entry, pyproject_path, requirement_type=None): |
| | try: |
| | req = Requirement(entry) |
| | except InvalidRequirement as e: |
| | print(json.dumps({"error": repr(e)})) |
| | exit(1) |
| | else: |
| | data = { |
| | "name": req.name, |
| | "version": version_from_req(req.specifier), |
| | "markers": str(req.marker) or None, |
| | "file": pyproject_path, |
| | "requirement": str(req.specifier), |
| | "extras": sorted(list(req.extras)), |
| | "requirement_type": requirement_type, |
| | } |
| | return data |
| |
|
| | def parse_toml_section_pep621_dependencies( |
| | pyproject_path, dependencies, requirement_type=None |
| | ): |
| | requirement_packages = [] |
| |
|
| | for dependency in dependencies: |
| | parsed_dependency = parse_requirement( |
| | dependency, pyproject_path, requirement_type |
| | ) |
| | requirement_packages.append(parsed_dependency) |
| |
|
| | return requirement_packages |
| |
|
| | def parse_toml_section_pep735_dependencies( |
| | pyproject_path, |
| | dependency_groups, |
| | group_name, |
| | visited=None, |
| | ): |
| | requirement_packages = [] |
| | visited = visited or set() |
| |
|
| | if group_name in visited: |
| | return requirement_packages |
| |
|
| | visited.add(group_name) |
| | dependencies = dependency_groups.get(group_name, []) |
| | for entry in dependencies: |
| | |
| | if isinstance(entry, str): |
| | parsed_dependency = parse_requirement( |
| | entry, pyproject_path, group_name |
| | ) |
| | requirement_packages.append(parsed_dependency) |
| | |
| | elif isinstance(entry, dict) and "include-group" in entry: |
| | included_group = entry["include-group"] |
| | requirement_packages.extend( |
| | parse_toml_section_pep735_dependencies( |
| | pyproject_path, |
| | dependency_groups, |
| | included_group, |
| | visited |
| | ) |
| | ) |
| |
|
| | return requirement_packages |
| |
|
| | dependencies = [] |
| |
|
| | if 'project' in project_toml: |
| | project_section = project_toml['project'] |
| |
|
| | if 'dependencies' in project_section: |
| | dependencies_toml = project_section['dependencies'] |
| | runtime_dependencies = parse_toml_section_pep621_dependencies( |
| | pyproject_path, |
| | dependencies_toml, |
| | "dependencies" |
| | ) |
| | dependencies.extend(runtime_dependencies) |
| |
|
| | if 'optional-dependencies' in project_section: |
| | optional_dependencies_toml = project_section[ |
| | 'optional-dependencies' |
| | ] |
| | for group in optional_dependencies_toml: |
| | group_dependencies = parse_toml_section_pep621_dependencies( |
| | pyproject_path, |
| | optional_dependencies_toml[group], |
| | group |
| | ) |
| | dependencies.extend(group_dependencies) |
| |
|
| | if 'dependency-groups' in project_toml: |
| | dependency_groups = project_toml['dependency-groups'] |
| | for group_name in dependency_groups: |
| | group_dependencies = parse_toml_section_pep735_dependencies( |
| | pyproject_path, dependency_groups, group_name |
| | ) |
| | dependencies.extend(group_dependencies) |
| |
|
| | if 'build-system' in project_toml: |
| | build_system_section = project_toml['build-system'] |
| | if 'requires' in build_system_section: |
| | build_system_dependencies = parse_toml_section_pep621_dependencies( |
| | pyproject_path, |
| | build_system_section['requires'], |
| | "build-system.requires" |
| | ) |
| | dependencies.extend(build_system_dependencies) |
| |
|
| | return json.dumps({"result": dependencies}) |
| |
|
| |
|
| | def parse_requirements(directory): |
| | |
| | requirement_packages = [] |
| | requirement_files = glob.glob(os.path.join(directory, '*.txt')) \ |
| | + glob.glob(os.path.join(directory, '**', '*.txt')) |
| |
|
| | pip_compile_files = glob.glob(os.path.join(directory, '*.in')) \ |
| | + glob.glob(os.path.join(directory, '**', '*.in')) |
| |
|
| | def version_from_install_req(install_req): |
| | if install_req.is_pinned: |
| | return next(iter(install_req.specifier)).version |
| |
|
| | for reqs_file in requirement_files + pip_compile_files: |
| | try: |
| | requirements = pip._internal.req.req_file.parse_requirements( |
| | reqs_file, |
| | session=PipSession() |
| | ) |
| | for parsed_req in requirements: |
| | install_req = install_req_from_parsed_requirement(parsed_req) |
| | if install_req.req is None: |
| | continue |
| |
|
| | |
| | if install_req.link is not None and install_req.link.is_file: |
| | continue |
| |
|
| | pattern = r"-[cr] (.*) \(line \d+\)" |
| | abs_path = re.search(pattern, install_req.comes_from).group(1) |
| |
|
| | |
| | if not os.path.isfile(abs_path): |
| | continue |
| |
|
| | rel_path = os.path.relpath(abs_path, directory) |
| |
|
| | requirement_packages.append({ |
| | "name": install_req.req.name, |
| | "version": version_from_install_req(install_req), |
| | "markers": str(install_req.markers) or None, |
| | "file": rel_path, |
| | "requirement": str(install_req.specifier) or None, |
| | "extras": sorted(list(install_req.extras)) |
| | }) |
| | except Exception as e: |
| | print(json.dumps({"error": repr(e)})) |
| | exit(1) |
| |
|
| | return json.dumps({"result": requirement_packages}) |
| |
|
| |
|
| | def parse_setup(directory): |
| | def version_from_install_req(install_req): |
| | if install_req.is_pinned: |
| | return next(iter(install_req.specifier)).version |
| |
|
| | def parse_requirement(req, req_type, filename): |
| | install_req = install_req_from_line(req) |
| | if install_req.original_link: |
| | return |
| |
|
| | setup_packages.append( |
| | { |
| | "name": install_req.req.name, |
| | "version": version_from_install_req(install_req), |
| | "markers": str(install_req.markers) or None, |
| | "file": filename, |
| | "requirement": str(install_req.specifier) or None, |
| | "requirement_type": req_type, |
| | "extras": sorted(list(install_req.extras)), |
| | } |
| | ) |
| |
|
| | def parse_requirements(requires, req_type, filename): |
| | for req in requires: |
| | req = COMMENT_RE.sub('', req) |
| | req = req.strip() |
| | parse_requirement(req, req_type, filename) |
| |
|
| | |
| | setup_py = "setup.py" |
| | setup_py_path = os.path.join(directory, setup_py) |
| | setup_cfg = "setup.cfg" |
| | setup_cfg_path = os.path.join(directory, setup_cfg) |
| | setup_packages = [] |
| |
|
| | if os.path.isfile(setup_py_path): |
| |
|
| | def setup(*args, **kwargs): |
| | for arg in ["setup_requires", "install_requires", "tests_require"]: |
| | requires = kwargs.get(arg, []) |
| | parse_requirements(requires, arg, setup_py) |
| | extras_require_dict = kwargs.get("extras_require", {}) |
| | for key, value in extras_require_dict.items(): |
| | parse_requirements( |
| | value, "extras_require:{}".format(key), setup_py |
| | ) |
| |
|
| | setuptools.setup = setup |
| |
|
| | def noop(*args, **kwargs): |
| | pass |
| |
|
| | def fake_parse(*args, **kwargs): |
| | return [] |
| |
|
| | global fake_open |
| |
|
| | def fake_open(*args, **kwargs): |
| | content = ( |
| | "VERSION = ('0', '0', '1+dependabot')\n" |
| | "__version__ = '0.0.1+dependabot'\n" |
| | "__author__ = 'someone'\n" |
| | "__title__ = 'something'\n" |
| | "__description__ = 'something'\n" |
| | "__author_email__ = 'something'\n" |
| | "__license__ = 'something'\n" |
| | "__url__ = 'something'\n" |
| | ) |
| | return io.StringIO(content) |
| |
|
| | content = open(setup_py_path, "r").read() |
| |
|
| | |
| | content = re.sub(r"print\s*\(", "noop(", content) |
| | content = re.sub(r"log\s*(\.\w+)*\(", "noop(", content) |
| | content = re.sub(r"\b(\w+\.)*(open|file)\s*\(", "fake_open(", content) |
| | content = content.replace("parse_requirements(", "fake_parse(") |
| | version_re = re.compile(r"^.*import.*__version__.*$", re.MULTILINE) |
| | content = re.sub(version_re, "", content) |
| |
|
| | |
| | __version__ = "0.0.1+dependabot" |
| | __author__ = "someone" |
| | __title__ = "something" |
| | __description__ = "something" |
| | __author_email__ = "something" |
| | __license__ = "something" |
| | __url__ = "something" |
| |
|
| | |
| | __name__ = "__main__" |
| |
|
| | |
| | exec(content) in globals(), locals() |
| |
|
| | if os.path.isfile(setup_cfg_path): |
| | try: |
| | config = configparser.ConfigParser() |
| | config.read(setup_cfg_path) |
| |
|
| | for req_type in [ |
| | "setup_requires", |
| | "install_requires", |
| | "tests_require", |
| | ]: |
| | requires = config.get( |
| | 'options', |
| | req_type, fallback='').splitlines() |
| | requires = [req for req in requires if req.strip()] |
| | parse_requirements(requires, req_type, setup_cfg) |
| |
|
| | if config.has_section('options.extras_require'): |
| | extras_require = config._sections['options.extras_require'] |
| | for key, value in extras_require.items(): |
| | requires = value.splitlines() |
| | requires = [req for req in requires if req.strip()] |
| | parse_requirements( |
| | requires, |
| | f"extras_require:{key}", |
| | setup_cfg |
| | ) |
| |
|
| | except Exception as e: |
| | print(json.dumps({"error": repr(e)})) |
| | exit(1) |
| |
|
| | return json.dumps({"result": setup_packages}) |
| |
|