Skip to content

Commit 6e9b467

Browse files
authored
Parse all requirements files in requirements-only Python projects (#7323)
* Parse all requirements files in requirements-only Python projects Previously parseManifest only picked the first matching requirements file via findFirst(). Projects with multiple requirements files (e.g. requirements.txt, requirements-dev.txt) would only have one file with a PythonResolutionResult marker, so dependency recipes like FFVD could not operate on the others. Closes moderneinc/customer-requests#2157 * Simplify to single list with stream map
1 parent 80b3c86 commit 6e9b467

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

rewrite-python/src/integTest/java/org/openrewrite/python/ParseProjectIntegTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,50 @@ void pyprojectTomlTakesPriorityOverRequirementsTxt() throws IOException {
254254
}
255255

256256

257+
@Test
258+
@Timeout(value = 60, unit = TimeUnit.SECONDS)
259+
void includesAllRequirementsTxtFiles() throws IOException {
260+
Path projectDir = tempDir.resolve("multi_requirements");
261+
Files.createDirectories(projectDir);
262+
263+
Files.writeString(projectDir.resolve("main.py"), "x = 1");
264+
Files.writeString(projectDir.resolve("requirements.txt"), """
265+
requests>=2.28.0
266+
""");
267+
Files.writeString(projectDir.resolve("requirements-dev.txt"), """
268+
pytest>=7.0
269+
""");
270+
271+
List<SourceFile> sources = client()
272+
.parseProject(projectDir, new InMemoryExecutionContext())
273+
.collect(Collectors.toList());
274+
275+
assertThat(sources)
276+
.extracting(sf -> sf.getSourcePath().getFileName().toString())
277+
.contains("main.py", "requirements.txt", "requirements-dev.txt");
278+
279+
SourceFile reqsTxt = sources.stream()
280+
.filter(s -> s.getSourcePath().getFileName().toString().equals("requirements.txt"))
281+
.findFirst()
282+
.orElseThrow(() -> new AssertionError("Missing requirements.txt"));
283+
SourceFile reqsDevTxt = sources.stream()
284+
.filter(s -> s.getSourcePath().getFileName().toString().equals("requirements-dev.txt"))
285+
.findFirst()
286+
.orElseThrow(() -> new AssertionError("Missing requirements-dev.txt"));
287+
288+
assertThat(reqsTxt).isInstanceOf(PlainText.class);
289+
assertThat(reqsDevTxt).isInstanceOf(PlainText.class);
290+
291+
PythonResolutionResult baseMarker = reqsTxt.getMarkers().findFirst(PythonResolutionResult.class).orElse(null);
292+
PythonResolutionResult devMarker = reqsDevTxt.getMarkers().findFirst(PythonResolutionResult.class).orElse(null);
293+
assertThat(baseMarker).as("PythonResolutionResult marker on requirements.txt").isNotNull();
294+
assertThat(devMarker).as("PythonResolutionResult marker on requirements-dev.txt").isNotNull();
295+
296+
// Each file should have its own distinct marker pointing to its own path
297+
assertThat(baseMarker.getPath()).isEqualTo("requirements.txt");
298+
assertThat(devMarker.getPath()).isEqualTo("requirements-dev.txt");
299+
}
300+
257301
private PythonRewriteRpc client() {
258302
return PythonRewriteRpc.getOrStart();
259303
}

rewrite-python/src/main/java/org/openrewrite/python/rpc/PythonRewriteRpc.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -340,14 +340,13 @@ private Stream<SourceFile> parseManifest(Path projectPath, @Nullable Path relati
340340

341341
RequirementsTxtParser reqsParser = new RequirementsTxtParser(commandEnv);
342342
try (Stream<Path> entries = Files.list(projectPath)) {
343-
Path reqsPath = entries
344-
.filter(p -> reqsParser.accept(p.getFileName()))
345-
.findFirst()
346-
.orElse(null);
347-
if (reqsPath != null) {
348-
Parser.Input input = Parser.Input.fromFile(reqsPath);
349-
return reqsParser.parseInputs(
350-
Collections.singletonList(input), effectiveRelativeTo, ctx);
343+
List<Parser.Input> reqInputs = new ArrayList<>();
344+
entries.filter(p -> reqsParser.accept(p.getFileName()))
345+
.sorted()
346+
.map(Parser.Input::fromFile)
347+
.forEach(reqInputs::add);
348+
if (!reqInputs.isEmpty()) {
349+
return reqsParser.parseInputs(reqInputs, effectiveRelativeTo, ctx);
351350
}
352351
} catch (IOException e) {
353352
// Silently skip manifest parsing if we can't list the directory

0 commit comments

Comments
 (0)