Skip to content

Commit 02fd5cc

Browse files
ruromeroclaude
andcommitted
fix(python): address review feedback for pyproject.toml provider
- Add TOML parse error checking (align with CargoProvider pattern) - Exclude optional and dev/test group dependencies (production only) - Preserve Poetry version constraints in dependency strings - Fix ignore matching bug: use getName() instead of getCoordinates() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 29497c1 commit 02fd5cc

3 files changed

Lines changed: 32 additions & 37 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/providers/PythonProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ private void handleIgnoredDependencies(String manifestContent, Sbom sbom) {
170170
Set<String> ignoredDepsNoVersions =
171171
ignoredDeps.stream()
172172
.filter(dep -> dep.getVersion().trim().equals("*"))
173-
.map(PackageURL::getCoordinates)
173+
.map(PackageURL::getName)
174174
.collect(Collectors.toSet());
175175

176176
sbom.setBelongingCriteriaBinaryAlgorithm(Sbom.BelongingCondition.NAME);

src/main/java/io/github/guacsec/trustifyda/providers/PythonPyprojectProvider.java

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ protected Set<PackageURL> getIgnoredDependencies(String manifestContent) {
7575

7676
List<String> parseDependencyStrings() throws IOException {
7777
TomlParseResult toml = Toml.parse(manifest);
78+
if (toml.hasErrors()) {
79+
throw new IOException("Invalid pyproject.toml format: " + toml.errors().get(0).getMessage());
80+
}
7881
List<String> deps = new ArrayList<>();
7982

8083
// [project.dependencies] - PEP 621
@@ -85,48 +88,40 @@ List<String> parseDependencyStrings() throws IOException {
8588
}
8689
}
8790

88-
// [project.optional-dependencies] - PEP 621
89-
TomlTable optionalDeps = toml.getTable("project.optional-dependencies");
90-
if (optionalDeps != null) {
91-
for (String group : optionalDeps.keySet()) {
92-
TomlArray groupDeps = optionalDeps.getArray(group);
93-
if (groupDeps != null) {
94-
for (int i = 0; i < groupDeps.size(); i++) {
95-
deps.add(groupDeps.getString(i));
96-
}
97-
}
98-
}
99-
}
100-
101-
// [tool.poetry.dependencies]
91+
// [tool.poetry.dependencies] - production only
10292
TomlTable poetryDeps = toml.getTable("tool.poetry.dependencies");
10393
if (poetryDeps != null) {
10494
for (String name : poetryDeps.keySet()) {
10595
if (!"python".equalsIgnoreCase(name)) {
106-
deps.add(name);
96+
deps.add(poetryDepToRequirement(name, poetryDeps, name));
10797
}
10898
}
10999
}
110100

111-
// [tool.poetry.group.*.dependencies]
112-
TomlTable poetryGroups = toml.getTable("tool.poetry.group");
113-
if (poetryGroups != null) {
114-
for (String groupName : poetryGroups.keySet()) {
115-
TomlTable groupTable = poetryGroups.getTable(groupName);
116-
if (groupTable != null) {
117-
TomlTable groupDeps = groupTable.getTable("dependencies");
118-
if (groupDeps != null) {
119-
for (String name : groupDeps.keySet()) {
120-
if (!"python".equalsIgnoreCase(name)) {
121-
deps.add(name);
122-
}
123-
}
124-
}
101+
return deps;
102+
}
103+
104+
/**
105+
* Converts a Poetry dependency entry to a pip-compatible requirement string. Poetry deps can be a
106+
* simple string ({@code name = "^1.0"}), a table ({@code name = {version = "^1.0"}}), or just a
107+
* name when the version is unresolvable.
108+
*/
109+
private static String poetryDepToRequirement(String name, TomlTable table, String key) {
110+
if (table.isString(key)) {
111+
String version = table.getString(key);
112+
if (version != null && !version.isEmpty() && !"*".equals(version)) {
113+
return name + version;
114+
}
115+
} else if (table.isTable(key)) {
116+
TomlTable depTable = table.getTable(key);
117+
if (depTable != null) {
118+
String version = depTable.getString("version");
119+
if (version != null && !version.isEmpty() && !"*".equals(version)) {
120+
return name + version;
125121
}
126122
}
127123
}
128-
129-
return deps;
124+
return name;
130125
}
131126

132127
static String extractDepFromTomlLine(String line) {

src/test/java/io/github/guacsec/trustifyda/providers/Python_Pyproject_Provider_Test.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ void test_parse_pep621_dependencies() throws IOException {
5050
}
5151

5252
@Test
53-
void test_parse_pep621_optional_dependencies() throws IOException {
53+
void test_parse_pep621_excludes_optional_dependencies() throws IOException {
5454
Path pyprojectPath =
5555
Path.of("src/test/resources/tst_manifests/pip/pip_pyproject_toml_no_ignore/pyproject.toml");
5656
var provider = new PythonPyprojectProvider(pyprojectPath);
5757
List<String> deps = provider.parseDependencyStrings();
58-
assertThat(deps).contains("click==8.0.4");
58+
assertThat(deps).doesNotContain("click==8.0.4");
5959
}
6060

6161
@Test
@@ -64,7 +64,7 @@ void test_parse_poetry_dependencies() throws IOException {
6464
Path.of("src/test/resources/tst_manifests/pip/pip_pyproject_toml_poetry/pyproject.toml");
6565
var provider = new PythonPyprojectProvider(pyprojectPath);
6666
List<String> deps = provider.parseDependencyStrings();
67-
assertThat(deps).contains("anyio", "flask", "requests");
67+
assertThat(deps).contains("anyio^3.6.2", "flask^2.0.3", "requests^2.25.1");
6868
}
6969

7070
@Test
@@ -77,12 +77,12 @@ void test_parse_poetry_excludes_python() throws IOException {
7777
}
7878

7979
@Test
80-
void test_parse_poetry_group_dependencies() throws IOException {
80+
void test_parse_poetry_excludes_dev_group_dependencies() throws IOException {
8181
Path pyprojectPath =
8282
Path.of("src/test/resources/tst_manifests/pip/pip_pyproject_toml_poetry/pyproject.toml");
8383
var provider = new PythonPyprojectProvider(pyprojectPath);
8484
List<String> deps = provider.parseDependencyStrings();
85-
assertThat(deps).contains("click");
85+
assertThat(deps).doesNotContain("click", "click^8.0.4");
8686
}
8787

8888
@Test

0 commit comments

Comments
 (0)