Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/providers/python_poetry.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export default class Python_poetry extends Base_pyproject {
*/
// eslint-disable-next-line no-unused-vars
async _getDependencyData(manifestDir, _workspaceDir, parsed, opts) {
let treeOutput = this._getPoetryShowTreeOutput(manifestDir, opts)
let hasDevGroup = !!(parsed.tool?.poetry?.group?.dev || parsed.tool?.poetry?.['dev-dependencies'])
let treeOutput = this._getPoetryShowTreeOutput(manifestDir, hasDevGroup, opts)
let showAllOutput = this._getPoetryShowAllOutput(manifestDir, opts)
let versionMap = this._parsePoetryShowAll(showAllOutput)
return this._parsePoetryTree(treeOutput, versionMap)
Expand All @@ -55,15 +56,20 @@ export default class Python_poetry extends Base_pyproject {
/**
* Get poetry show --tree output.
* @param {string} manifestDir
* @param {boolean} hasDevGroup
* @param {Object} opts
* @returns {string}
*/
_getPoetryShowTreeOutput(manifestDir, opts) {
_getPoetryShowTreeOutput(manifestDir, hasDevGroup, opts) {
if (environmentVariableIsPopulated('TRUSTIFY_DA_POETRY_SHOW_TREE')) {
return Buffer.from(process.env['TRUSTIFY_DA_POETRY_SHOW_TREE'], 'base64').toString('utf-8')
}
let poetryBin = getCustomPath('poetry', opts)
return invokeCommand(poetryBin, ['show', '--tree', '--no-ansi'], { cwd: manifestDir }).toString()
let args = ['show', '--tree', '--no-ansi']
if (hasDevGroup) {
args.push('--without', 'dev')
}
return invokeCommand(poetryBin, args, { cwd: manifestDir }).toString()
}

/**
Expand Down
48 changes: 48 additions & 0 deletions test/providers/python_pyproject.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,54 @@ suite('testing the python-pyproject data provider', () => {
}).timeout(TIMEOUT)
})

suite('poetry projects - modern dev dependencies excluded (TC-4096)', () => {
test('dev dependencies excluded from stack analysis', async () => {
let expectedSbom = fs.readFileSync('test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_stack_sbom.json').toString()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let result = await poetryProvider.provideStack('test/providers/tst_manifests/pyproject/poetry_dev_deps/pyproject.toml')
expect(result).to.deep.equal({
ecosystem: 'pip',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
}).timeout(TIMEOUT)

test('dev dependencies excluded from component analysis', async () => {
let expectedSbom = fs.readFileSync('test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_component_sbom.json').toString().trim()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let result = await poetryProvider.provideComponent('test/providers/tst_manifests/pyproject/poetry_dev_deps/pyproject.toml')
expect(result).to.deep.equal({
ecosystem: 'pip',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
}).timeout(TIMEOUT)
})

suite('poetry projects - legacy dev-dependencies excluded (TC-4096)', () => {
test('legacy dev dependencies excluded from stack analysis', async () => {
let expectedSbom = fs.readFileSync('test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_stack_sbom.json').toString()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let result = await poetryProvider.provideStack('test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/pyproject.toml')
expect(result).to.deep.equal({
ecosystem: 'pip',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
}).timeout(TIMEOUT)

test('legacy dev dependencies excluded from component analysis', async () => {
let expectedSbom = fs.readFileSync('test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_component_sbom.json').toString().trim()
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let result = await poetryProvider.provideComponent('test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/pyproject.toml')
expect(result).to.deep.equal({
ecosystem: 'pip',
contentType: 'application/vnd.cyclonedx+json',
content: expectedSbom
})
}).timeout(TIMEOUT)
})

suite('poetry projects - poetry_only_deps manifest', () => {
test('verify pyproject.toml sbom provided for stack analysis with poetry_only_deps', async () => {
let expectedSbom = fs.readFileSync('test/providers/tst_manifests/pyproject/poetry_only_deps/expected_stack_sbom.json').toString()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2023-10-01T00:00:00.000Z",
"component": {
"name": "dev-deps-project",
"version": "1.0.0",
"purl": "pkg:pypi/dev-deps-project@1.0.0",
"type": "application",
"bom-ref": "pkg:pypi/dev-deps-project@1.0.0"
}
},
"components": [
{
"name": "requests",
"version": "2.33.1",
"purl": "pkg:pypi/requests@2.33.1",
"type": "library",
"bom-ref": "pkg:pypi/requests@2.33.1"
}
],
"dependencies": [
{
"ref": "pkg:pypi/dev-deps-project@1.0.0",
"dependsOn": [
"pkg:pypi/requests@2.33.1"
]
},
{
"ref": "pkg:pypi/requests@2.33.1",
"dependsOn": []
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2023-10-01T00:00:00.000Z",
"component": {
"name": "dev-deps-project",
"version": "1.0.0",
"purl": "pkg:pypi/dev-deps-project@1.0.0",
"type": "application",
"bom-ref": "pkg:pypi/dev-deps-project@1.0.0"
}
},
"components": [
{
"name": "requests",
"version": "2.33.1",
"purl": "pkg:pypi/requests@2.33.1",
"type": "library",
"bom-ref": "pkg:pypi/requests@2.33.1"
},
{
"name": "certifi",
"version": "2026.2.25",
"purl": "pkg:pypi/certifi@2026.2.25",
"type": "library",
"bom-ref": "pkg:pypi/certifi@2026.2.25"
},
{
"name": "charset-normalizer",
"version": "3.4.7",
"purl": "pkg:pypi/charset-normalizer@3.4.7",
"type": "library",
"bom-ref": "pkg:pypi/charset-normalizer@3.4.7"
},
{
"name": "idna",
"version": "3.11",
"purl": "pkg:pypi/idna@3.11",
"type": "library",
"bom-ref": "pkg:pypi/idna@3.11"
},
{
"name": "urllib3",
"version": "2.6.3",
"purl": "pkg:pypi/urllib3@2.6.3",
"type": "library",
"bom-ref": "pkg:pypi/urllib3@2.6.3"
}
],
"dependencies": [
{
"ref": "pkg:pypi/dev-deps-project@1.0.0",
"dependsOn": [
"pkg:pypi/requests@2.33.1"
]
},
{
"ref": "pkg:pypi/requests@2.33.1",
"dependsOn": [
"pkg:pypi/certifi@2026.2.25",
"pkg:pypi/charset-normalizer@3.4.7",
"pkg:pypi/idna@3.11",
"pkg:pypi/urllib3@2.6.3"
]
},
{
"ref": "pkg:pypi/certifi@2026.2.25",
"dependsOn": []
},
{
"ref": "pkg:pypi/charset-normalizer@3.4.7",
"dependsOn": []
},
{
"ref": "pkg:pypi/idna@3.11",
"dependsOn": []
},
{
"ref": "pkg:pypi/urllib3@2.6.3",
"dependsOn": []
}
]
}
Loading
Loading