diff --git a/CHANGELOG.md b/CHANGELOG.md index 2892f3fe..bbd2b7d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ All versions prior to 0.0.9 are untracked. were not correctly filtered by "withdrawn" status; "withdrawn" vulnerabilities are now excluded ([#386](https://github.com/pypa/pip-audit/pull/386)) +* Fixed `pip-audit`'s handling of URL-style requirements in `--no-deps` mode + (URL requirements are now treated as skipped, rather than producing + an error due to a lack of pinning) + ([#395](https://github.com/pypa/pip-audit/pull/395/files)) + ## [2.4.4] ### Changed diff --git a/pip_audit/_cli.py b/pip_audit/_cli.py index a00bd9f0..2a4d4f5d 100644 --- a/pip_audit/_cli.py +++ b/pip_audit/_cli.py @@ -448,6 +448,8 @@ def audit() -> None: if len(vulns) > 0: pkg_count += 1 vuln_count += len(vulns) + except DependencySourceError as e: + _fatal(str(e)) except VulnServiceConnectionError as e: # The most common source of connection errors is corporate blocking, # so we offer a bit of advice. diff --git a/pip_audit/_dependency_source/requirement.py b/pip_audit/_dependency_source/requirement.py index 92a97f1e..d305a8c2 100644 --- a/pip_audit/_dependency_source/requirement.py +++ b/pip_audit/_dependency_source/requirement.py @@ -250,26 +250,30 @@ def _collect_preresolved_deps( f"requirement {req.name} does not contain a hash {str(req)}" ) - if not req.specifier: - if req.link is None: + # NOTE: URL dependencies cannot be pinned, so skipping them + # makes sense (under the same principle of skipping dependencies + # that can't be found on PyPI). This is also consistent with + # what `pip --no-deps` does (installs the URL dependency, but + # not any subdependencies). + if req.is_url: + yield req.req, SkippedDependency( + name=req.name, + skip_reason="URL requirements cannot be pinned to a specific package version", + ) + elif not req.specifier: + raise RequirementSourceError(f"requirement {req.name} is not pinned: {str(req)}") + else: + pinned_specifier = PINNED_SPECIFIER_RE.match(str(req.specifier)) + if pinned_specifier is None: raise RequirementSourceError( f"requirement {req.name} is not pinned: {str(req)}" ) - else: - raise RequirementSourceError( - f"requirement {req.name} is not pinned, URL requirements must be pinned " - f"with #egg=your_package_name==your_package_version: {str(req)}" - ) - pinned_specifier = PINNED_SPECIFIER_RE.match(str(req.specifier)) - if pinned_specifier is None: - raise RequirementSourceError(f"requirement {req.name} is not pinned: {str(req)}") - - yield req.req, ResolvedDependency( - req.name, - Version(pinned_specifier.group("version")), - self._build_hash_options_mapping(req.hash_options), - ) + yield req.req, ResolvedDependency( + req.name, + Version(pinned_specifier.group("version")), + self._build_hash_options_mapping(req.hash_options), + ) def _build_hash_options_mapping(self, hash_options: List[str]) -> Dict[str, List[str]]: """ diff --git a/test/dependency_source/test_requirement.py b/test/dependency_source/test_requirement.py index c916791c..8a1d0347 100644 --- a/test/dependency_source/test_requirement.py +++ b/test/dependency_source/test_requirement.py @@ -451,13 +451,15 @@ def test_requirement_source_no_deps_unpinned_url(monkeypatch): monkeypatch.setattr( pip_requirements_parser, "get_file_content", - lambda _: "https://github.com/pallets/flask/archive/refs/tags/2.0.1.tar.gz#egg=flask\n" - "requests>=1.0", + lambda _: "https://github.com/pallets/flask/archive/refs/tags/2.0.1.tar.gz#egg=flask\n", ) - # When dependency resolution is disabled, all requirements must be pinned. - with pytest.raises(DependencySourceError): - list(source.collect()) + assert list(source.collect()) == [ + SkippedDependency( + name="flask", + skip_reason="URL requirements cannot be pinned to a specific package version", + ) + ] def test_requirement_source_dep_caching(monkeypatch):