Skip to content

feat(pip): add pyproject.toml support via pip --dry-run --report#465

Merged
ruromero merged 11 commits into
guacsec:mainfrom
ruromero:TC-4065
Apr 17, 2026
Merged

feat(pip): add pyproject.toml support via pip --dry-run --report#465
ruromero merged 11 commits into
guacsec:mainfrom
ruromero:TC-4065

Conversation

@ruromero

@ruromero ruromero commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add Python_pip_pyproject provider that resolves PEP 621 pyproject.toml dependencies using pip install --dry-run --ignore-installed --report
  • Fallback provider when no lock file (uv.lock/poetry.lock) is found — registered after uv/poetry in the provider matching order
  • Supports TRUSTIFY_DA_PIP_REPORT env var override for testing without pip installed
  • Fix _getDependencyData signature in Python_pip_pyproject to match base class 4-param contract (TC-4098)
  • Fix _parsePipReport to only exclude the root entry from the graph, not all dir_info entries — preserves local/path dependencies (TC-4099)

Details

The pip report JSON provides:

  • Resolved versions for all packages
  • requires_dist per package for building the dependency tree
  • requested: true/false for root project identification

Extras-only dependencies (e.g. PySocks; extra == "socks") are filtered out.
Only PEP 621 format ([project.dependencies]) is in scope — poetry-format pyproject.toml should use the lock-file-based provider.

Implements TC-4065

Test plan

  • Stack analysis produces correct SBOM with transitive deps
  • Component analysis produces correct SBOM with direct deps only
  • Direct and transitive deps correctly classified
  • Extras-only deps filtered from tree
  • exhortignore marker excludes deps from both analysis types
  • Name canonicalization (charset_normalizercharset-normalizer)
  • validateLockFile always returns true (fallback)
  • Existing uv/poetry tests unaffected (44 tests passing)

🤖 Generated with Claude Code

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add pip provider for PEP 621 pyproject.toml and workspace support

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Add Python_pip_pyproject provider for PEP 621 pyproject.toml without lock files
• Resolve dependencies using pip install --dry-run --ignore-installed --report
• Implement workspace/monorepo support with lock file discovery and boundary detection
• Add comprehensive test coverage for pip provider and workspace scenarios
Diagram
flowchart LR
  A["pyproject.toml<br/>PEP 621 format"] -->|"pip --dry-run<br/>--report"| B["pip report<br/>JSON"]
  B -->|"Parse & build<br/>dependency graph"| C["Python_pip_pyproject<br/>Provider"]
  C -->|"Fallback when<br/>no lock file"| D["SBOM<br/>with transitive deps"]
  E["Workspace detection<br/>uv/poetry markers"] -->|"Walk up directory<br/>tree"| F["Find lock file<br/>or boundary"]
  F -->|"Resolve to<br/>correct lock dir"| G["Accurate analysis<br/>per package"]
Loading

Grey Divider

File Changes

1. src/provider.js ⚙️ Configuration changes +2/-0

Register new pip pyproject provider

src/provider.js


2. src/providers/python_pip_pyproject.js ✨ Enhancement +132/-0

New pip provider for PEP 621 pyproject.toml

src/providers/python_pip_pyproject.js


3. src/providers/base_pyproject.js ✨ Enhancement +68/-3

Add workspace detection and lock file discovery

src/providers/base_pyproject.js


View more (23)
4. src/providers/python_uv.js ✨ Enhancement +32/-2

Support editable installs in workspace members

src/providers/python_uv.js


5. test/providers/python_pyproject.test.js 🧪 Tests +306/-0

Add pip provider and workspace tests

test/providers/python_pyproject.test.js


6. test/providers/tst_manifests/pyproject/pip_pep621/pyproject.toml 🧪 Tests +7/-0

Test fixture for pip PEP 621 project

test/providers/tst_manifests/pyproject/pip_pep621/pyproject.toml


7. test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json 🧪 Tests +62/-0

Mock pip report for testing

test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json


8. test/providers/tst_manifests/pyproject/pip_pep621/expected_stack_sbom.json 🧪 Tests +85/-0

Expected SBOM with transitive dependencies

test/providers/tst_manifests/pyproject/pip_pep621/expected_stack_sbom.json


9. test/providers/tst_manifests/pyproject/pip_pep621/expected_component_sbom.json 🧪 Tests +36/-0

Expected SBOM with direct dependencies only

test/providers/tst_manifests/pyproject/pip_pep621/expected_component_sbom.json


10. test/providers/tst_manifests/pyproject/pip_pep621_ignore/pyproject.toml 🧪 Tests +7/-0

Test fixture with exhortignore marker

test/providers/tst_manifests/pyproject/pip_pep621_ignore/pyproject.toml


11. test/providers/tst_manifests/pyproject/pip_pep621_ignore/pip_report.json 🧪 Tests +62/-0

Mock pip report for ignore test

test/providers/tst_manifests/pyproject/pip_pep621_ignore/pip_report.json


12. test/providers/tst_manifests/pyproject/uv_workspace/pyproject.toml 🧪 Tests +13/-0

UV workspace root with members

test/providers/tst_manifests/pyproject/uv_workspace/pyproject.toml


13. test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/pyproject.toml 🧪 Tests +9/-0

UV workspace member with dependency

test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/pyproject.toml


14. test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/pyproject.toml 🧪 Tests +6/-0

UV workspace leaf package

test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/pyproject.toml


15. test/providers/tst_manifests/pyproject/uv_workspace/expected_stack_sbom.json 🧪 Tests +198/-0

Expected SBOM for UV workspace root

test/providers/tst_manifests/pyproject/uv_workspace/expected_stack_sbom.json


16. test/providers/tst_manifests/pyproject/poetry_workspace/pyproject.toml 🧪 Tests +8/-0

Poetry workspace root configuration

test/providers/tst_manifests/pyproject/poetry_workspace/pyproject.toml


17. test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/pyproject.toml 🧪 Tests +7/-0

Poetry workspace member package

test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/pyproject.toml


18. test/providers/tst_manifests/pyproject/poetry_workspace/expected_stack_sbom.json 🧪 Tests +85/-0

Expected SBOM for poetry workspace

test/providers/tst_manifests/pyproject/poetry_workspace/expected_stack_sbom.json


19. test/providers/tst_manifests/pyproject/poetry_workspace/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/poetry_workspace/expected_component_sbom.json


20. test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_component_sbom.json


21. test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_stack_sbom.json Additional files +85/-0

...

test/providers/tst_manifests/pyproject/poetry_workspace/packages/sub-pkg/expected_stack_sbom.json


22. test/providers/tst_manifests/pyproject/uv_workspace/expected_component_sbom.json Additional files +48/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/expected_component_sbom.json


23. test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_component_sbom.json


24. test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_stack_sbom.json Additional files +98/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/mid-pkg/expected_stack_sbom.json


25. test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_component_sbom.json Additional files +36/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_component_sbom.json


26. test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_stack_sbom.json Additional files +85/-0

...

test/providers/tst_manifests/pyproject/uv_workspace/packages/sub-pkg/expected_stack_sbom.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4)   📘 Rule violations (0)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (2) ☼ Reliability (2)

Grey Divider


Action required

1. Poetry uses wrong project 🐞
Description
Base_pyproject now calls _getDependencyData() using lockFileDir rather than the provided manifest’s
directory, but Python_poetry derives its dependency data only from that passed directory and does
not use the parsed member pyproject. When a workspace member pyproject.toml relies on a poetry.lock
in a parent directory, dependency resolution can be performed against the parent project instead of
the requested member manifest, producing an incorrect SBOM.
Code

src/providers/base_pyproject.js[R351-352]

+		let lockFileDir = this._findLockFileDir(manifestDir, opts) || manifestDir
+		let { directDeps, graph } = await this._getDependencyData(lockFileDir, parsed, opts)
Evidence
_createSbom() reads/parses the requested manifest, but then switches the dependency-resolution
working directory to the lockfile directory (lockFileDir) before calling _getDependencyData(). The
Poetry provider ignores the parsed manifest entirely and runs its dependency commands using only the
passed manifestDir/cwd, so when Base_pyproject passes lockFileDir, Python_poetry has no way to scope
resolution to the originally requested member manifest.

src/providers/base_pyproject.js[346-353]
src/providers/python_poetry.js[17-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Base_pyproject._createSbom()` now calls `_getDependencyData()` with `lockFileDir`, which changes the effective project context for providers that derive dependency data from the working directory (notably `Python_poetry`). This can cause member-manifest analysis to resolve dependencies for the lockfile directory’s project instead of the requested `pyproject.toml`.

## Issue Context
- `Base_pyproject._createSbom()` parses the requested manifest, but passes `lockFileDir` into `_getDependencyData()`.
- `Python_poetry._getDependencyData()` ignores `parsed` and uses `manifestDir` to run `poetry show ...`.

## Fix Focus Areas
- src/providers/base_pyproject.js[346-353]
- src/providers/python_poetry.js[17-42]

## Suggested fix approach
- Change the contract so providers receive both:
 - `projectDir` (directory of the requested manifest)
 - `lockFileDir` (directory where the lock file was found)
- Update `Base_pyproject._createSbom()` to call something like `_getDependencyData({ projectDir: manifestDir, lockFileDir }, parsed, opts)` (or add an extra parameter).
- For `Python_poetry`, ensure commands are executed in `projectDir` (or explicitly point Poetry at the requested project if it supports a directory flag), while still using the correct lockfile location as needed.
- Keep `Python_uv` behavior using `lockFileDir` if that’s required for workspace export, but do so explicitly via the new parameters.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Pip root misidentified 🐞
Description
Python_pip_pyproject identifies the root entry by checking download_info.dir_info and excludes all
dir_info entries from the graph, which can misidentify the root and drop local/path dependencies.
Pip reports already contain a requested flag suitable for reliable root identification, so the
current approach can produce an incorrect dependency tree and wrong direct/transitive
classification.
Code

src/providers/python_pip_pyproject.js[R63-75]

+		let rootEntry = packages.find(p => p.download_info?.dir_info !== undefined)
+		let rootRequires = rootEntry?.metadata?.requires_dist || []
+
+		let directDepNames = new Set()
+		for (let req of rootRequires) {
+			if (this._hasExtraMarker(req)) { continue }
+			let name = this._extractDepName(req)
+			if (name) { directDepNames.add(this._canonicalize(name)) }
+		}
+
+		let graph = new Map()
+		let nonRootPackages = packages.filter(p => p.download_info?.dir_info === undefined)
+
Evidence
The implementation selects the root via packages.find(...dir_info...) and builds the graph only
from packages where dir_info is undefined. The recorded pip report fixture demonstrates that pip
provides an explicit requested boolean for entries, but the implementation does not use it;
relying on dir_info is not unique to the root if local/path dependencies exist.

src/providers/python_pip_pyproject.js[59-99]
test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json[4-13]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Python_pip_pyproject._parsePipReport()` treats the first `download_info.dir_info` entry as the root and removes all `dir_info` entries from the dependency graph. This can misidentify the root project and omit legitimate local/path dependencies.

## Issue Context
Pip report entries contain a `requested` flag (seen in the repo’s pip report fixture) that can be used to identify the root request more reliably than `dir_info`.

## Fix Focus Areas
- src/providers/python_pip_pyproject.js[59-99]
- test/providers/tst_manifests/pyproject/pip_pep621/pip_report.json[4-13]

## Suggested fix approach
- Identify the root entry using `requested: true` (and/or matching the parsed project name), not `dir_info`.
- Build the graph from all non-root packages, including those with `dir_info` (local/path deps), instead of excluding them wholesale.
- Only exclude the actual root project from the graph, not every `dir_info` entry.
- Add/extend tests with a pip report containing a local/path dependency that also has `dir_info` to ensure it stays in the graph and is correctly classified.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Poetry boundary not enforced 🐞
Description
Base_pyproject only treats a Poetry workspace boundary as present when both [tool.poetry] and
poetry.lock exist, so a Poetry project root without poetry.lock will not stop the upward search.
This can cause lockfile discovery to continue into parent directories and potentially select an
unrelated poetry.lock.
Code

src/providers/base_pyproject.js[R95-97]

+			if (content.tool?.poetry && fs.existsSync(path.join(dir, 'poetry.lock'))) {
+				return true
+			}
Evidence
_findLockFileDir() stops searching when _isWorkspaceRoot() returns true; for Poetry,
_isWorkspaceRoot() currently requires both content.tool?.poetry and an on-disk poetry.lock. In
contrast, the JavaScript workspace boundary logic stops at workspace markers even without a lockfile
present, preventing accidental walk-up into unrelated parent projects.

src/providers/base_pyproject.js[77-101]
src/providers/base_javascript.js[114-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Base_pyproject._isWorkspaceRoot()` only considers a Poetry workspace boundary when `[tool.poetry]` is present *and* `poetry.lock` exists. If a Poetry root is missing its lock file, `_findLockFileDir()` may walk above the project root and accidentally match a parent directory’s lock file.

## Issue Context
Other ecosystems in this repo treat workspace markers as hard boundaries even when the lock file is absent.

## Fix Focus Areas
- src/providers/base_pyproject.js[77-101]

## Suggested fix approach
- Treat the presence of `[tool.poetry]` in `pyproject.toml` as a workspace boundary regardless of whether `poetry.lock` exists.
- Keep the existing uv boundary behavior.
- If you still need the current behavior for some reason, add a guard to prevent escaping beyond the nearest directory containing a `pyproject.toml` with `[tool.poetry]`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Editable TOML can crash 🐞
Description
Python_uv parses a member pyproject.toml for editable installs without guarding against TOML parse
failures. A malformed or unreadable member pyproject.toml will throw and abort dependency parsing
for the whole project.
Code

src/providers/python_uv.js[R71-88]

+			if (child.type === 'global_opt') {
+				let optNode = child.children.find(c => c.type === 'option')
+				let pathNode = child.children.find(c => c.type === 'path')
+				if (optNode?.text === '-e' && pathNode && manifestDir) {
+					let memberDir = path.resolve(manifestDir, pathNode.text)
+					let memberManifest = path.join(memberDir, 'pyproject.toml')
+					if (fs.existsSync(memberManifest)) {
+						let memberParsed = parseToml(fs.readFileSync(memberManifest, 'utf-8'))
+						let name = memberParsed.project?.name || memberParsed.tool?.poetry?.name
+						let version = memberParsed.project?.version || memberParsed.tool?.poetry?.version
+						if (name && version) {
+							let key = this._canonicalize(name)
+							currentPkg = { name, version, parents: new Set() }
+							packages.set(key, currentPkg)
+							collectingVia = false
+							continue
+						}
+					}
Evidence
The new editable-install handling reads and parses memberManifest with parseToml(...) without a
try/catch, unlike Base_pyproject’s similar TOML parsing which explicitly catches parse errors. This
makes uv workspace parsing fragile when encountering a bad member manifest.

src/providers/python_uv.js[70-89]
src/providers/base_pyproject.js[90-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Python_uv._parseUvExport()` attempts to parse editable member `pyproject.toml` without error handling. Any TOML parse error will throw and fail the entire analysis.

## Issue Context
`Base_pyproject._isWorkspaceRoot()` already demonstrates the preferred pattern: wrap TOML parsing in a try/catch and treat failures as non-fatal.

## Fix Focus Areas
- src/providers/python_uv.js[70-89]

## Suggested fix approach
- Wrap `parseToml(fs.readFileSync(...))` in a try/catch.
- On failure, fall back to the existing behavior for that node (e.g., set `currentPkg = null` and continue), rather than throwing.
- Consider also guarding `fs.readFileSync` in case of permissions or transient FS issues.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread src/providers/base_pyproject.js Outdated
Comment thread src/providers/python_pip_pyproject.js
@ruromero

ruromero commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator Author

Verification Report — TC-4065

Check Result Details
Review Feedback ⚠️ WARN 2 code change requests → sub-tasks TC-4098, TC-4099
Root-Cause Investigation ⚠️ WARN 1 convention gap (TC-4098: base class directory semantics undocumented), 1 skill gap (TC-4099: broad filter removed all dir_info entries instead of just root)
Scope Containment ⚠️ WARN PR includes TC-4039 workspace support files (out of scope for TC-4065)
Diff Size ⚠️ WARN +2064 lines — includes changes from both TC-4039 and TC-4065
Commit Traceability ⚠️ WARN Commit 1 references TC-4039 only; TC-4065 changes are in commit 2
Sensitive Patterns ✅ PASS No credentials, tokens, or secrets detected
CI Status ⚠️ WARN Pre-existing infrastructure failure (node-gyp build error on @aspect-dev/snoopy — unrelated to this PR)
Acceptance Criteria ✅ PASS 6/6 criteria satisfied
Test Quality ✅ PASS Tests follow project conventions (Mocha TDD/Chai), have doc comments, cover all acceptance criteria
Verification Commands N/A None specified in task

Root-Cause Analysis

TC-4098 — _createSbom passes lockFileDir instead of projectDir

  • Classification: Convention gap
  • The base class _createSbom() was designed for lock-file providers (uv/poetry) where lockFileDir is the correct argument. The pip fallback provider needs the project directory (where pyproject.toml lives) because pip install . runs relative to CWD. This architectural distinction is not documented in CONVENTIONS.md.
  • Recommendation: Document the directory semantics in CONVENTIONS.md — lock-file providers receive the lock file directory, while non-lock-file providers need the project directory.

TC-4099 — _parsePipReport excludes all dir_info entries

  • Classification: Skill gap (implement-task phase)
  • The task description correctly states "Skip the root project entry" (singular), but the implementation used packages.filter(p => p.download_info?.dir_info === undefined) which removes ALL entries with dir_info, not just the root. The Java implementation correctly handles this by only skipping the first dir_info entry.
  • Phase: implement-task — the task description was accurate but the implementation used a broad filter instead of targeting only the first match.

Overall: ❌ FAIL

Blocking issues:

  1. Two code change requests require sub-tasks before merge
  2. Commit traceability partially missing (commit 1 covers TC-4039 only)

This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11.

@ruromero
ruromero changed the base branch from main to TC-4039 April 14, 2026 09:08
@ruromero
ruromero changed the base branch from TC-4039 to main April 14, 2026 09:31
@ruromero

ruromero commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator Author

Verification Report for TC-4065 (commit 7a2cffa)

Check Result Details
Review Feedback ⚠️ WARN 2 code change requests from prior run → sub-tasks TC-4098, TC-4099
Root-Cause Investigation DONE Convention gap (TC-4102: document directory semantics), Skill gap (TC-4103: filter uniqueness for singular entries)
Scope Containment ✅ PASS 9 files, all within task scope
Diff Size ✅ PASS +515/-0, 9 files — proportionate for new provider + tests + fixtures
Commit Traceability ✅ PASS Single commit with "Implements TC-4065"
Sensitive Patterns ✅ PASS No credentials or secrets (env var references are false positives)
CI Status ⚠️ WARN PR title and commit message checks pass; lint/test workflow pending on rebased commit
Acceptance Criteria ✅ PASS 6/6 criteria satisfied
Test Quality ✅ PASS All 8 pip tests have doc comments, no repetitive parameterization candidates
Verification Commands N/A None specified in task

Overall: ⚠️ WARN

The PR is clean after rebase — single commit, correct scope, all acceptance criteria met. Two blocking sub-tasks (TC-4098, TC-4099) from review feedback remain open. CI lint/test workflow has not yet run on the rebased commit (7a2cffa).


This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11.

@ruromero

Copy link
Copy Markdown
Collaborator Author

/review

@ruromero
ruromero requested a review from Strum355 April 15, 2026 12:07
Comment thread src/providers/python_pip_pyproject.js Outdated
Comment thread test/providers/python_pyproject.test.js Outdated
Comment thread .gitignore
Comment thread test/providers/python_pyproject.test.js
Comment thread src/providers/python_pip_pyproject.js
@ruromero

Copy link
Copy Markdown
Collaborator Author

/retest

ruromero and others added 10 commits April 17, 2026 13:27
Add Python_pip_pyproject provider that resolves PEP 621 pyproject.toml
dependencies using `pip install --dry-run --ignore-installed --report`.
This is the fallback provider when no lock file (uv.lock/poetry.lock)
is found, enabling analysis of standard pyproject.toml projects
without requiring uv or poetry.

The pip report JSON provides resolved versions, requires_dist for
building the dependency tree, and requested flags for direct vs
transitive classification. Extras-only deps are filtered out.

Supports TRUSTIFY_DA_PIP_REPORT env var for testing without pip.

Implements TC-4065

Assisted-by: Claude Code
…se class

Python_pip_pyproject._getDependencyData() had only 3 parameters
(manifestDir, parsed, opts) instead of the 4 defined by the base class
(manifestDir, workspaceDir, parsed, opts). When called from _createSbom()
with 4 arguments, workspaceDir was silently received as parsed, and
parsed was received as opts, causing argument misalignment.

Add the missing _workspaceDir parameter to match the base class contract
and sibling providers (python_uv, python_poetry).

Implements TC-4098

Assisted-by: Claude Code
…ir_info entries

_parsePipReport() filtered nonRootPackages by checking
p.download_info?.dir_info === undefined, which excluded ALL packages
with dir_info (including local/path dependencies). Changed to filter
by identity (p !== rootEntry) so only the specific root project entry
is excluded, matching the Java implementation's behavior.

Implements TC-4099

Assisted-by: Claude Code
Extract shared SBOM_CASES constant and use forEach loops to eliminate
repetitive stack/component test pairs across uv, poetry, and pip suites.
Parameterize validateLockFile and workspace tests similarly. Add JSDoc
comments to all 39 test functions.

Implements TC-4065

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tering

Add requests[socks] to the test pyproject.toml and include PySocks in the
pip_report.json install list. This makes the extras-filtering test exercise
the actual code path: PySocks is now present in the report but correctly
excluded from the SBOM via _hasExtraMarker.

Previously the test trivially passed because PySocks was never in the
install list.

Implements TC-4065

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove TRUSTIFY_DA_PIP_REPORT setup/teardown from pip tests and let them
invoke pip directly. Pin fixture pyproject.toml versions to get
deterministic output, add --quiet flag to suppress non-JSON stdout, and
update expected SBOMs with real resolved versions. Add testing convention
to CONVENTIONS.md and ignore *.egg-info build artifacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI uses Python 3.9 (setup-python in test.yml), so pip rejects
pyproject.toml fixtures that require >=3.12.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pip creates .egg-info as a side effect of --dry-run --report. Snapshot
existing .egg-info dirs before invoking pip and remove only newly
created ones afterward to avoid polluting the user's project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pip_pep621_ignore fixture was missing expected_stack_sbom.json and
expected_component_sbom.json, relying on partial manual assertions instead
of the standard SBOM_CASES golden-file pattern. Add the golden files and
switch to deep.equal comparison. Document the golden SBOM convention in
CONVENTIONS.md to prevent this gap in future implementations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pyproject base class built an intermediate tree with a visited set
that prevented a dependency from appearing under multiple parents. This
caused PySocks to be missing from requests' dependsOn when both requests
and urllib3 declared it (via extras). All other providers (npm, cargo,
go, maven) already produce true CycloneDX graphs.

Replace _buildDependencyTree/_collectTransitive/_addAllDependencies with
graph-based BFS reachability + flat edge iteration. No recursion
prevention needed — Python resolvers reject circular dependencies.

Regenerate all pyproject golden SBOM files (pip, uv, poetry, workspace).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread src/providers/base_pyproject.js Outdated
Strum355
Strum355 previously approved these changes Apr 17, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ruromero
ruromero merged commit 320ec49 into guacsec:main Apr 17, 2026
4 checks passed
@ruromero
ruromero deleted the TC-4065 branch April 17, 2026 17:42
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

Successfully merging this pull request may close these issues.

2 participants