Skip to content

Commit 3ddaec9

Browse files
committed
Fix review items: Windows site-packages, dist-info version matching, extra marker regex, SetupCfgParser test coverage
1 parent 78846a9 commit 3ddaec9

2 files changed

Lines changed: 70 additions & 11 deletions

File tree

rewrite-python/src/main/java/org/openrewrite/python/RequirementsTxtParser.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public class RequirementsTxtParser implements Parser {
4747
"requirements(-[\\w-]+)?\\.(txt|in)"
4848
);
4949

50+
private static final Pattern EXTRA_MARKER_PATTERN = Pattern.compile("\\bextra\\s*==");
51+
5052
private final PlainTextParser plainTextParser = new PlainTextParser();
5153

5254
@Override
@@ -192,6 +194,12 @@ static List<ResolvedDependency> linkDependenciesFromMetadata(List<ResolvedDepend
192194
}
193195

194196
private static @Nullable Path findSitePackages(Path workspace) {
197+
// Windows: .venv/Lib/site-packages (no python* subdirectory)
198+
Path windowsSitePackages = workspace.resolve(".venv/Lib/site-packages");
199+
if (Files.isDirectory(windowsSitePackages)) {
200+
return windowsSitePackages;
201+
}
202+
// Unix: .venv/lib/python*/site-packages
195203
Path lib = workspace.resolve(".venv/lib");
196204
if (!Files.isDirectory(lib)) {
197205
return null;
@@ -235,20 +243,20 @@ private static List<String> readRequiresDist(Path sitePackages, String packageNa
235243
if (Files.exists(direct)) {
236244
return direct;
237245
}
238-
// Fallback: glob for matching dist-info directory
246+
// Fallback: glob for matching dist-info directory (must also match version)
239247
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sitePackages, "*.dist-info")) {
240248
String normalizedLower = PythonResolutionResult.normalizeName(packageName);
249+
String expectedSuffix = "-" + version + ".dist-info";
241250
for (Path distInfo : stream) {
242251
String dirName = distInfo.getFileName().toString();
243-
// dirName is like "requests-2.31.0.dist-info"
244-
int dashIdx = dirName.indexOf('-');
245-
if (dashIdx > 0) {
246-
String dirPkgName = dirName.substring(0, dashIdx);
247-
if (PythonResolutionResult.normalizeName(dirPkgName).equals(normalizedLower)) {
248-
Path metadata = distInfo.resolve("METADATA");
249-
if (Files.exists(metadata)) {
250-
return metadata;
251-
}
252+
if (!dirName.endsWith(expectedSuffix)) {
253+
continue;
254+
}
255+
String dirPkgName = dirName.substring(0, dirName.length() - expectedSuffix.length());
256+
if (PythonResolutionResult.normalizeName(dirPkgName).equals(normalizedLower)) {
257+
Path metadata = distInfo.resolve("METADATA");
258+
if (Files.exists(metadata)) {
259+
return metadata;
252260
}
253261
}
254262
}
@@ -274,7 +282,7 @@ static List<String> parseRequiresDist(String metadataContent) {
274282
String value = trimmed.substring("Requires-Dist:".length()).trim();
275283

276284
// Skip entries with "extra ==" markers (optional extras, not always installed)
277-
if (value.contains("extra ==") || value.contains("extra=")) {
285+
if (EXTRA_MARKER_PATTERN.matcher(value).find()) {
278286
continue;
279287
}
280288

rewrite-python/src/test/java/org/openrewrite/python/SetupCfgParserTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,25 @@
1616
package org.openrewrite.python;
1717

1818
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.io.TempDir;
20+
import org.openrewrite.InMemoryExecutionContext;
21+
import org.openrewrite.Parser;
22+
import org.openrewrite.SourceFile;
23+
import org.openrewrite.python.internal.UvExecutor;
24+
import org.openrewrite.python.marker.PythonResolutionResult;
25+
import org.openrewrite.python.marker.PythonResolutionResult.ResolvedDependency;
26+
import org.openrewrite.text.PlainText;
1927

28+
import java.io.IOException;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
2031
import java.nio.file.Paths;
32+
import java.util.Collections;
33+
import java.util.List;
34+
import java.util.stream.Collectors;
2135

2236
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
2338

2439
class SetupCfgParserTest {
2540

@@ -38,4 +53,40 @@ void builderCreatesDslName() {
3853
assertThat(builder.getDslName()).isEqualTo("setup.cfg");
3954
assertThat(builder.build()).isInstanceOf(SetupCfgParser.class);
4055
}
56+
57+
@Test
58+
void markerContainsDependenciesFromFreeze(@TempDir Path tempDir) throws IOException {
59+
assumeTrue(UvExecutor.findUvExecutable() != null, "uv is not installed");
60+
61+
Files.writeString(tempDir.resolve("setup.cfg"), """
62+
[metadata]
63+
name = myapp
64+
version = 1.0.0
65+
66+
[options]
67+
install_requires =
68+
requests>=2.28.0
69+
""");
70+
// setup.py is required for uv pip install to recognize the project
71+
Files.writeString(tempDir.resolve("setup.py"), "from setuptools import setup; setup()");
72+
73+
SetupCfgParser parser = new SetupCfgParser();
74+
Parser.Input input = Parser.Input.fromFile(tempDir.resolve("setup.cfg"));
75+
List<SourceFile> parsed = parser.parseInputs(
76+
Collections.singletonList(input),
77+
tempDir,
78+
new InMemoryExecutionContext(Throwable::printStackTrace)
79+
).collect(Collectors.toList());
80+
81+
assertThat(parsed).hasSize(1);
82+
PlainText text = (PlainText) parsed.get(0);
83+
PythonResolutionResult marker = text.getMarkers()
84+
.findFirst(PythonResolutionResult.class).orElse(null);
85+
assertThat(marker).isNotNull();
86+
87+
assertThat(marker.getResolvedDependencies()).isNotEmpty();
88+
assertThat(marker.getResolvedDependencies().stream().map(ResolvedDependency::getName))
89+
.contains("requests");
90+
assertThat(marker.getPackageManager()).isEqualTo(PythonResolutionResult.PackageManager.Uv);
91+
}
4192
}

0 commit comments

Comments
 (0)