Skip to content

Commit aae20a5

Browse files
authored
RequirementsTxtParser: attach ParseExceptionResult on resolution failure (#7326)
1 parent 7b360d6 commit aae20a5

2 files changed

Lines changed: 31 additions & 4 deletions

File tree

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.jspecify.annotations.Nullable;
1919
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.ParseExceptionResult;
2021
import org.openrewrite.Parser;
2122
import org.openrewrite.SourceFile;
2223
import org.openrewrite.python.internal.PyProjectHelper;
@@ -37,7 +38,6 @@
3738
import java.util.stream.Stream;
3839

3940
import static java.util.Collections.emptyMap;
40-
4141
import static org.openrewrite.Tree.randomId;
4242

4343
/**
@@ -67,7 +67,7 @@ public RequirementsTxtParser(Map<String, String> subprocessEnvironment) {
6767
public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path relativeTo, ExecutionContext ctx) {
6868
return plainTextParser.parseInputs(sources, relativeTo, ctx).map(sf -> {
6969
if (!(sf instanceof PlainText)) {
70-
return sf;
70+
return sf.withMarkers(sf.getMarkers().add(ParseExceptionResult.build(this, new UnsupportedOperationException(), "Creating a PythonResolutionResult can only be done for PlainText LST elements.")));
7171
}
7272
PlainText text = (PlainText) sf;
7373

@@ -78,12 +78,15 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
7878
Path workspace = DependencyWorkspace.getOrCreateRequirementsWorkspace(
7979
text.getText(), originalFilePath, subprocessEnvironment);
8080
if (workspace == null) {
81-
return sf;
81+
return sf.withMarkers(sf.getMarkers().add(ParseExceptionResult.build(this, new UnsupportedOperationException(),
82+
"Failed to create the PythonResolutionResult due to a failure to install the requirement file. " +
83+
"Perhaps you are missing `uv` in the environment or trying to build a requirement text file containing dependencies which are not available?")));
8284
}
8385

8486
List<ResolvedDependency> resolvedDeps = parseFreezeOutput(workspace);
8587
if (resolvedDeps.isEmpty()) {
86-
return sf;
88+
return sf.withMarkers(sf.getMarkers().add(ParseExceptionResult.build(this, new UnsupportedOperationException(),
89+
"Failed to create the PythonResolutionResult: no resolved dependencies.")));
8790
}
8891

8992
List<Dependency> deps = dependenciesFromResolved(resolvedDeps,

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.junit.jupiter.api.Test;
1919
import org.junit.jupiter.api.io.TempDir;
2020
import org.openrewrite.InMemoryExecutionContext;
21+
import org.openrewrite.ParseExceptionResult;
2122
import org.openrewrite.Parser;
2223
import org.openrewrite.SourceFile;
2324
import org.openrewrite.python.internal.UvExecutor;
@@ -263,6 +264,29 @@ void markerContainsDependenciesFromFreeze() {
263264
assertThat(marker.getPackageManager()).isEqualTo(PythonResolutionResult.PackageManager.Uv);
264265
}
265266

267+
@Test
268+
void unresolvableRequirementsAttachesParseExceptionMarker() {
269+
// A non-existent package will fail to install regardless of whether uv is present
270+
// (without uv, workspace creation fails; with uv, the install itself fails).
271+
String requirements = "this-package-definitely-does-not-exist-zzz==9.9.9\n";
272+
273+
RequirementsTxtParser parser = new RequirementsTxtParser();
274+
Parser.Input input = Parser.Input.fromString(Paths.get("requirements.txt"), requirements);
275+
List<SourceFile> parsed = parser.parseInputs(
276+
Collections.singletonList(input),
277+
null,
278+
new InMemoryExecutionContext(t -> {})
279+
).collect(Collectors.toList());
280+
281+
assertThat(parsed).hasSize(1);
282+
SourceFile sf = parsed.get(0);
283+
ParseExceptionResult marker = sf.getMarkers().findFirst(ParseExceptionResult.class).orElse(null);
284+
assertThat(marker).isNotNull();
285+
assertThat(marker.getMessage()).contains("PythonResolutionResult");
286+
// Should not have a successful resolution marker
287+
assertThat(sf.getMarkers().findFirst(PythonResolutionResult.class)).isEmpty();
288+
}
289+
266290
@Test
267291
void builderCreatesDslName() {
268292
RequirementsTxtParser.Builder builder = RequirementsTxtParser.builder();

0 commit comments

Comments
 (0)