From ee7f064dc654074275ebd15c117da19bbff44feb Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Thu, 28 Aug 2025 12:31:41 +0600 Subject: [PATCH 1/8] add initial fix with regex --- .../util/PythonDependencyTransformer.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java b/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java index fb5d91dd2b..9fa6bfe7dc 100644 --- a/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java +++ b/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java @@ -8,6 +8,8 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class PythonDependencyTransformer { @@ -15,6 +17,9 @@ public class PythonDependencyTransformer { private static final List IGNORE_AFTER_CHARS = Arrays.asList("#", ";"); private static final List TOKEN_CLEANUP_CHARS = Arrays.asList("\"", "'"); private static final List TOKEN_IGNORE_AFTER_CHARS = Arrays.asList(",", "[", "==", ">=", "~=", "<=", ">", "<"); + private static final Pattern URI_VERSION_PATTERN = Pattern.compile(".*/([A-Za-z0-9_.-]+)-([0-9]+(?:\\.[0-9A-Za-z_-]+)*)"); + private static final Pattern VCS_VERSION_PATTERN = Pattern.compile(".*@(\\d+\\.\\d+(?:\\.\\d+)*)"); + public List transform(File requirementsFile) throws IOException { @@ -39,6 +44,22 @@ public PythonDependency transformLine(String line) { return null; } + // Case 1: Handle PEP 508 direct references (name @ url) + if (formattedLine.contains("@")) { + String[] parts = formattedLine.split("@", 2); + String dependency = parts[0].trim(); + String uri = parts[1].trim(); + + String version = extractVersionFromUri(uri); + + if (!dependency.isEmpty()) { + return new PythonDependency(dependency, version); + } else { + return null; + } + } + + // Case 2: Normal operator-based dependency (==, >=, etc.) // Extract tokens before and after the operator that was found in the line List> extractedTokens = extractTokens(formattedLine); List tokensBeforeOperator = extractedTokens.get(0); @@ -66,6 +87,23 @@ public PythonDependency transformLine(String line) { } } + private String extractVersionFromUri(String uri) { + // Case 1: wheel/archive style: .../package-1.2.3.whl or .../package-1.2.3.zip + Matcher matcher = URI_VERSION_PATTERN.matcher(uri); + if (matcher.find()) { + return matcher.group(2); // version part + } + + // Case 2: VCS reference with @ (tag/commit/semver) + Matcher vcsMatcher = VCS_VERSION_PATTERN.matcher(uri); + if (vcsMatcher.matches()) { + return vcsMatcher.group(1); + } + + // Case 3: no version found + return ""; + } + public List> extractTokens(String formattedLine) { // Note: The line is always a valid line to extract from at this point since it has passed all the checks // Hence it will contain at least the dependency. Version may or may not be present. From daac7f9fa29903a16a8848e612b58aee321685ac Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Thu, 28 Aug 2025 16:02:48 +0600 Subject: [PATCH 2/8] update regex to match PEP508 --- .../util/PythonDependencyTransformer.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java b/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java index 9fa6bfe7dc..de1152975c 100644 --- a/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java +++ b/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java @@ -17,9 +17,9 @@ public class PythonDependencyTransformer { private static final List IGNORE_AFTER_CHARS = Arrays.asList("#", ";"); private static final List TOKEN_CLEANUP_CHARS = Arrays.asList("\"", "'"); private static final List TOKEN_IGNORE_AFTER_CHARS = Arrays.asList(",", "[", "==", ">=", "~=", "<=", ">", "<"); - private static final Pattern URI_VERSION_PATTERN = Pattern.compile(".*/([A-Za-z0-9_.-]+)-([0-9]+(?:\\.[0-9A-Za-z_-]+)*)"); - private static final Pattern VCS_VERSION_PATTERN = Pattern.compile(".*@(\\d+\\.\\d+(?:\\.\\d+)*)"); - + private static final Pattern URI_VERSION_PATTERN = Pattern.compile(".*/([A-Za-z0-9_.-]+)-([0-9]+(?:\\.[0-9A-Za-z_-]+)*).*\\.(whl|zip|tar\\.gz|tar\\.bz2|tar)$"); + private static final Pattern VCS_VERSION_PATTERN = Pattern.compile(".*@([0-9]+(?:\\.[0-9]+)*(?:[A-Za-z0-9._-]*)?).*"); + private static final Pattern ARCHIVE_VERSION_PATTERN = Pattern.compile(".*/(?:archive|releases)/([0-9]+(?:\\.[0-9]+)+).*\\.(zip|tar\\.gz|tar\\.bz2|tar).*"); public List transform(File requirementsFile) throws IOException { @@ -88,22 +88,33 @@ public PythonDependency transformLine(String line) { } private String extractVersionFromUri(String uri) { - // Case 1: wheel/archive style: .../package-1.2.3.whl or .../package-1.2.3.zip + if (uri == null || uri.isEmpty()) { + return ""; + } + + // Case 1: wheel/archive style Matcher matcher = URI_VERSION_PATTERN.matcher(uri); if (matcher.find()) { - return matcher.group(2); // version part + return matcher.group(2); } - // Case 2: VCS reference with @ (tag/commit/semver) + // Case 2: VCS reference with @ Matcher vcsMatcher = VCS_VERSION_PATTERN.matcher(uri); - if (vcsMatcher.matches()) { + if (vcsMatcher.find()) { return vcsMatcher.group(1); } - // Case 3: no version found + // Case 3: Generic archive URL with version in path (like pip archive) + Matcher archiveMatcher = ARCHIVE_VERSION_PATTERN.matcher(uri); + if (archiveMatcher.find()) { + return archiveMatcher.group(1); + } + + // Case 4: fallback – no version found return ""; } + public List> extractTokens(String formattedLine) { // Note: The line is always a valid line to extract from at this point since it has passed all the checks // Hence it will contain at least the dependency. Version may or may not be present. From 5939e4c05f3c1fecbee42a1a1a7169ff6380a0b6 Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Thu, 11 Sep 2025 19:41:00 +0600 Subject: [PATCH 3/8] add python dependency transformer test as per pep508, pep518 and pep621 --- .../unit/PythonDependencyTransformerTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java diff --git a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java new file mode 100644 index 0000000000..eef1ee6dea --- /dev/null +++ b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java @@ -0,0 +1,56 @@ +package com.blackduck.integration.detectable.detectables.setuptools.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; + +import com.blackduck.integration.detectable.python.util.PythonDependency; +import com.blackduck.integration.detectable.python.util.PythonDependencyTransformer; +import org.junit.jupiter.api.Test; + +public class PythonDependencyTransformerTest { + + @Test + public void testTransformLine() { + PythonDependencyTransformer transformer = new PythonDependencyTransformer(); + + // Case 1: Normal dependency with exact version + PythonDependency alembic = transformer.transformLine("alembic==1.12.0"); + assertEquals("alembic", alembic.getName()); + assertEquals("1.12.0", alembic.getVersion()); + + // Case 2: Normal dependency with version range + PythonDependency darkgraylib = transformer.transformLine("darkgraylib>=2.31.0,<3.0"); + assertEquals("darkgraylib", darkgraylib.getName()); + assertEquals("2.31.0", darkgraylib.getVersion()); + + PythonDependency requests = transformer.transformLine("requests>=2.4.0,<3.0.dev0"); + assertEquals("requests", requests.getName()); + assertEquals("2.4.0", requests.getVersion()); + + // Case 3: Normal dependency with single version constraint + PythonDependency toml = transformer.transformLine("toml>=0.10.0"); + assertEquals("toml", toml.getName()); + assertEquals("0.10.0", toml.getVersion()); + + // Case 4: Dependency with direct URL (HTTP/HTTPS) + PythonDependency torch = transformer.transformLine("torch @ https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-linux_x86_64.whl"); + assertEquals("torch", torch.getName()); + assertEquals("2.6.0", torch.getVersion()); + + PythonDependency torchvision = transformer.transformLine("torchvision @ https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-linux_x86_64.whl"); + assertEquals("torchvision", torchvision.getName()); + assertEquals("0.21.0", torchvision.getVersion()); + + // Case 5: Archive dependency + PythonDependency pip = transformer.transformLine("pip @ https://github.com/pypa/pip/archive/1.3.1.zip"); + assertEquals("pip", pip.getName()); + assertEquals("1.3.1", pip.getVersion()); + + // Case 6: Git dependency + PythonDependency flask = transformer.transformLine("flask @ git+https://github.com/pallets/flask.git@2.3.3"); + assertEquals("flask", flask.getName()); + assertEquals("2.3.3", flask.getVersion()); + } +} \ No newline at end of file From c36f86e0531ea75a0c6ad044deb318e8749c6e37 Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Thu, 11 Sep 2025 20:52:41 +0600 Subject: [PATCH 4/8] add test for parsing pyproject.toml --- .../unit/PyprojectTomlParserTest.java | 89 +++++++++++++++++++ .../unit/PythonDependencyTransformerTest.java | 7 +- 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PyprojectTomlParserTest.java diff --git a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PyprojectTomlParserTest.java b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PyprojectTomlParserTest.java new file mode 100644 index 0000000000..60e4f9a9b8 --- /dev/null +++ b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PyprojectTomlParserTest.java @@ -0,0 +1,89 @@ +package com.blackduck.integration.detectable.detectables.setuptools.unit; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; + +import com.blackduck.integration.detectable.detectables.setuptools.parse.SetupToolsParsedResult; +import com.blackduck.integration.detectable.detectables.setuptools.parse.SetupToolsTomlParser; + +class PyprojectTomlParserTest { + + @Test + void testParseComplexPyprojectToml() throws IOException { + String tomlContent = "[build-system]\n" + + "requires = [\"setuptools>=61.0\", \"wheel>=0.37.1\"]\n" + + "build-backend = \"setuptools.build_meta\"\n\n" + + "[project]\n" + + "name = \"complex-setuptools-project\"\n" + + "version = \"0.1.0\"\n" + + "description = \"Sample project testing complex PEP 508 dependencies\"\n" + + "authors = [\n" + + " { name = \"Example User\", email = \"example.user@email.com\" }\n" + + "]\n" + + "readme = \"README.md\"\n" + + "license = { file = \"LICENSE\" }\n" + + "keywords = [\"python\", \"pep508\", \"dependencies\", \"testing\"]\n" + + "classifiers = [\n" + + " \"Programming Language :: Python :: 3\",\n" + + " \"License :: OSI Approved :: MIT License\",\n" + + " \"Operating System :: OS Independent\"\n" + + "]\n\n" + + "dependencies = [\n" + + " \"requests>=2.31.0,<3.0\",\n" + + " \"alembic==1.12.0\",\n" + + " \"beautifulsoup4==4.13.3\",\n" + + " \"six==1.16.0\",\n" + + " \"torch @ https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-linux_x86_64.whl\",\n" + + " \"torchvision @ https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-linux_x86_64.whl\",\n" + + " \"flask @ git+https://github.com/pallets/flask.git@2.3.3\",\n" + + " \"requests[security,socks]==2.31.0\",\n" + + " \"pandas[all]>=2.1.0,<3.0; python_version>'3.8'\"\n" + + "]\n\n" + + "[project.optional-dependencies]\n" + + "dev = [\n" + + " \"pytest>=7.4.0\",\n" + + " \"black==24.3.0\",\n" + + " \"mypy>=1.5.1\"\n" + + "]\n" + + "docs = [\n" + + " \"sphinx>=7.0.0\",\n" + + " \"sphinx-rtd-theme>=1.2.0\"\n" + + "]\n\n" + + "[tool.setuptools]\n" + + "py-modules = [\"main\"]\n\n" + + "[project.scripts]\n" + + "complex-setuptools-project = \"main:main\"\n"; + + Path pyProjectFile = Files.createTempFile("pyproject", ".toml"); + Files.write(pyProjectFile, tomlContent.getBytes()); + + TomlParseResult result = Toml.parse(tomlContent); + + SetupToolsTomlParser tomlParser = new SetupToolsTomlParser(result); + SetupToolsParsedResult parsedResult = tomlParser.parse(); + + // Assertions for project metadata + assertEquals("complex-setuptools-project", parsedResult.getProjectName()); + assertEquals("0.1.0", parsedResult.getProjectVersion()); + + // Assertions for dependencies + assertEquals(9, parsedResult.getDirectDependencies().size()); + assertTrue(parsedResult.getDirectDependencies().stream() + .anyMatch(dep -> dep.getName().equals("requests") && dep.getVersion().equals("2.31.0"))); + assertTrue(parsedResult.getDirectDependencies().stream() + .anyMatch(dep -> dep.getName().equals("torch") && dep.getVersion().equals("2.6.0"))); + + // Assertions for optional dependencies + assertTrue(result.contains("project.optional-dependencies.dev")); + assertTrue(result.contains("project.optional-dependencies.docs")); + + Files.delete(pyProjectFile); // Clean up the temporary file + } +} \ No newline at end of file diff --git a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java index eef1ee6dea..2da802ab2b 100644 --- a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java +++ b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java @@ -1,18 +1,15 @@ package com.blackduck.integration.detectable.detectables.setuptools.unit; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.util.List; import com.blackduck.integration.detectable.python.util.PythonDependency; import com.blackduck.integration.detectable.python.util.PythonDependencyTransformer; import org.junit.jupiter.api.Test; -public class PythonDependencyTransformerTest { +class PythonDependencyTransformerTest { @Test - public void testTransformLine() { + void testTransformLine() { PythonDependencyTransformer transformer = new PythonDependencyTransformer(); // Case 1: Normal dependency with exact version From d22a067d4b593e76683fca7b09659b2bf07861ab Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Fri, 12 Sep 2025 11:05:42 +0600 Subject: [PATCH 5/8] add comment for regex pattern --- .../python/util/PythonDependencyTransformer.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java b/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java index de1152975c..9484d1b9c5 100644 --- a/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java +++ b/detectable/src/main/java/com/blackduck/integration/detectable/python/util/PythonDependencyTransformer.java @@ -17,8 +17,14 @@ public class PythonDependencyTransformer { private static final List IGNORE_AFTER_CHARS = Arrays.asList("#", ";"); private static final List TOKEN_CLEANUP_CHARS = Arrays.asList("\"", "'"); private static final List TOKEN_IGNORE_AFTER_CHARS = Arrays.asList(",", "[", "==", ">=", "~=", "<=", ">", "<"); + + // Matching version from URI of direct reference like "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-linux_x86_64.whl" private static final Pattern URI_VERSION_PATTERN = Pattern.compile(".*/([A-Za-z0-9_.-]+)-([0-9]+(?:\\.[0-9A-Za-z_-]+)*).*\\.(whl|zip|tar\\.gz|tar\\.bz2|tar)$"); + + // Matching version from VCS URL of direct reference like "git+https://github.com/pallets/flask.git@2.3.3" private static final Pattern VCS_VERSION_PATTERN = Pattern.compile(".*@([0-9]+(?:\\.[0-9]+)*(?:[A-Za-z0-9._-]*)?).*"); + + // Matching version from archive or release URL of direct reference like "https://github.com/pypa/pip/archive/1.3.1.zip" private static final Pattern ARCHIVE_VERSION_PATTERN = Pattern.compile(".*/(?:archive|releases)/([0-9]+(?:\\.[0-9]+)+).*\\.(zip|tar\\.gz|tar\\.bz2|tar).*"); public List transform(File requirementsFile) throws IOException { @@ -92,7 +98,7 @@ private String extractVersionFromUri(String uri) { return ""; } - // Case 1: wheel/archive style + // Case 1: wheel/archive style like "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-linux_x86_64.whl" Matcher matcher = URI_VERSION_PATTERN.matcher(uri); if (matcher.find()) { return matcher.group(2); From 3c6d776773b9a4991449e4b42daf2df947470ac7 Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Tue, 16 Sep 2025 15:53:15 +0600 Subject: [PATCH 6/8] replace regular unit test with parameterized test --- .../unit/PythonDependencyTransformerTest.java | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java index 2da802ab2b..182262f689 100644 --- a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java +++ b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/setuptools/unit/PythonDependencyTransformerTest.java @@ -4,50 +4,50 @@ import com.blackduck.integration.detectable.python.util.PythonDependency; import com.blackduck.integration.detectable.python.util.PythonDependencyTransformer; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; class PythonDependencyTransformerTest { - @Test - void testTransformLine() { - PythonDependencyTransformer transformer = new PythonDependencyTransformer(); - - // Case 1: Normal dependency with exact version - PythonDependency alembic = transformer.transformLine("alembic==1.12.0"); - assertEquals("alembic", alembic.getName()); - assertEquals("1.12.0", alembic.getVersion()); - - // Case 2: Normal dependency with version range - PythonDependency darkgraylib = transformer.transformLine("darkgraylib>=2.31.0,<3.0"); - assertEquals("darkgraylib", darkgraylib.getName()); - assertEquals("2.31.0", darkgraylib.getVersion()); - - PythonDependency requests = transformer.transformLine("requests>=2.4.0,<3.0.dev0"); - assertEquals("requests", requests.getName()); - assertEquals("2.4.0", requests.getVersion()); - - // Case 3: Normal dependency with single version constraint - PythonDependency toml = transformer.transformLine("toml>=0.10.0"); - assertEquals("toml", toml.getName()); - assertEquals("0.10.0", toml.getVersion()); - - // Case 4: Dependency with direct URL (HTTP/HTTPS) - PythonDependency torch = transformer.transformLine("torch @ https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-linux_x86_64.whl"); - assertEquals("torch", torch.getName()); - assertEquals("2.6.0", torch.getVersion()); - - PythonDependency torchvision = transformer.transformLine("torchvision @ https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-linux_x86_64.whl"); - assertEquals("torchvision", torchvision.getName()); - assertEquals("0.21.0", torchvision.getVersion()); - - // Case 5: Archive dependency - PythonDependency pip = transformer.transformLine("pip @ https://github.com/pypa/pip/archive/1.3.1.zip"); - assertEquals("pip", pip.getName()); - assertEquals("1.3.1", pip.getVersion()); - - // Case 6: Git dependency - PythonDependency flask = transformer.transformLine("flask @ git+https://github.com/pallets/flask.git@2.3.3"); - assertEquals("flask", flask.getName()); - assertEquals("2.3.3", flask.getVersion()); + private final PythonDependencyTransformer transformer = new PythonDependencyTransformer(); + + static Stream dependencyCases() { + return Stream.of( + new TestCase("alembic==1.12.0", "alembic", "1.12.0"), + new TestCase("darkgraylib>=2.31.0,<3.0", "darkgraylib", "2.31.0"), + new TestCase("requests>=2.4.0,<3.0.dev0", "requests", "2.4.0"), + new TestCase("toml>=0.10.0", "toml", "0.10.0"), + new TestCase("torch @ https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-linux_x86_64.whl", "torch", "2.6.0"), + new TestCase("torchvision @ https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-linux_x86_64.whl", "torchvision", "0.21.0"), + new TestCase("pip @ https://github.com/pypa/pip/archive/1.3.1.zip", "pip", "1.3.1"), + new TestCase("flask @ git+https://github.com/pallets/flask.git@2.3.3", "flask", "2.3.3") + ); + } + + @ParameterizedTest + @MethodSource("dependencyCases") + void testTransformLine(TestCase testCase) { + PythonDependency dependency = transformer.transformLine(testCase.line); + assertEquals(testCase.expectedName, dependency.getName()); + assertEquals(testCase.expectedVersion, dependency.getVersion()); + } + + static class TestCase { + final String line; + final String expectedName; + final String expectedVersion; + + TestCase(String line, String expectedName, String expectedVersion) { + this.line = line; + this.expectedName = expectedName; + this.expectedVersion = expectedVersion; + } + + @Override + public String toString() { + return String.format("line='%s', expectedName=%s, expectedVersion=%s", line, expectedName, expectedVersion); + } } -} \ No newline at end of file +} From 009370ac93593a4d156e8128aca56c983d58b182 Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Wed, 17 Sep 2025 14:50:43 +0600 Subject: [PATCH 7/8] add release note entry --- documentation/src/main/markdown/currentreleasenotes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/src/main/markdown/currentreleasenotes.md b/documentation/src/main/markdown/currentreleasenotes.md index 62bb147667..f15468159a 100644 --- a/documentation/src/main/markdown/currentreleasenotes.md +++ b/documentation/src/main/markdown/currentreleasenotes.md @@ -39,6 +39,7 @@ * (IDETECT-4751) Prevent server-side parsing errors by normalizing IAC Scan `results.json` contents before uploading to Black Duck SCA. * (IDETECT-4799) When constructing the BDIO, ignore the Go toolchain directive, as it is the Go project's build-time configuration setting and not a module dependency. * (IDETECT-4813) Fix Gradle Native Inspector to correctly identify projects with only settings.gradle or settings.gradle.kts file in the root directory. +* (IDETECT-4845) Added support for extracting Python package versions from direct references (PEP 508 URIs) in pyproject.toml. [detect_product_short] now correctly parses versions from wheel URLs, archive URLs, and VCS references for impacted detectors (Setuptools Pip Detector, Setuptools Detector, and UV Lock Detector). When version information is missing or malformed, detectors gracefully fall back to name-only reporting. ### Dependency updates From f59e8df9732f88b708aef723c1de70c77923cb74 Mon Sep 17 00:00:00 2001 From: zahidblackduck Date: Fri, 19 Sep 2025 18:09:02 +0600 Subject: [PATCH 8/8] update release note entry as per review --- documentation/src/main/markdown/currentreleasenotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/markdown/currentreleasenotes.md b/documentation/src/main/markdown/currentreleasenotes.md index f15468159a..b22694f942 100644 --- a/documentation/src/main/markdown/currentreleasenotes.md +++ b/documentation/src/main/markdown/currentreleasenotes.md @@ -39,7 +39,7 @@ * (IDETECT-4751) Prevent server-side parsing errors by normalizing IAC Scan `results.json` contents before uploading to Black Duck SCA. * (IDETECT-4799) When constructing the BDIO, ignore the Go toolchain directive, as it is the Go project's build-time configuration setting and not a module dependency. * (IDETECT-4813) Fix Gradle Native Inspector to correctly identify projects with only settings.gradle or settings.gradle.kts file in the root directory. -* (IDETECT-4845) Added support for extracting Python package versions from direct references (PEP 508 URIs) in pyproject.toml. [detect_product_short] now correctly parses versions from wheel URLs, archive URLs, and VCS references for impacted detectors (Setuptools Pip Detector, Setuptools Detector, and UV Lock Detector). When version information is missing or malformed, detectors gracefully fall back to name-only reporting. +* (IDETECT-4845) With added support for extracting Python package versions from direct references [PEP 508 URIs](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers) in `pyproject.toml` files, [detect_product_short] now correctly parses versions from wheel and archive URLs and VCS references for impacted detectors (Setuptools Pip Detector, Setuptools Detector, and UV Lock Detector). When data is missing or badly formatted, detectors gracefully switch back to reporting only the package name. ### Dependency updates