Skip to content

Commit 6e6be21

Browse files
committed
Support Python's --files_to_stage option in Java xlang.
1 parent a72d6cb commit 6e6be21

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

sdks/java/extensions/python/src/main/java/org/apache/beam/sdk/extensions/python/PythonExternalTransform.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public class PythonExternalTransform<InputT extends PInput, OutputT extends POut
8181

8282
private String expansionService;
8383
private List<String> extraPackages;
84+
private List<String> filesToStage;
8485

8586
// We preseve the order here since Schema's care about order of fields but the order will not
8687
// matter when applying kwargs at the Python side.
@@ -98,6 +99,7 @@ private PythonExternalTransform(String fullyQualifiedName, String expansionServi
9899
this.fullyQualifiedName = fullyQualifiedName;
99100
this.expansionService = expansionService;
100101
this.extraPackages = new ArrayList<>();
102+
this.filesToStage = new ArrayList<>();
101103
this.kwargsMap = new TreeMap<>();
102104
this.typeHints = new HashMap<>();
103105
// TODO(https://github.com/apache/beam/issues/21567): remove a default type hint for
@@ -304,6 +306,28 @@ public PythonExternalTransform<InputT, OutputT> withExtraPackages(List<String> e
304306
return this;
305307
}
306308

309+
/**
310+
* Specifies that the given Python files should be staged for the Python worker.
311+
*
312+
* @param filesToStage a list of local paths to Python files or data files.
313+
* @return updated wrapper for the cross-language transform.
314+
*/
315+
public PythonExternalTransform<InputT, OutputT> withFilesToStage(List<String> filesToStage) {
316+
if (filesToStage.isEmpty()) {
317+
return this;
318+
}
319+
Preconditions.checkState(
320+
Strings.isNullOrEmpty(expansionService),
321+
"Files to stage only apply to auto-started expansion service.");
322+
this.filesToStage = filesToStage;
323+
return this;
324+
}
325+
326+
@VisibleForTesting
327+
List<String> getFilesToStage() {
328+
return filesToStage;
329+
}
330+
307331
@VisibleForTesting
308332
Row buildOrGetKwargsRow() {
309333
if (providedKwargsRow != null) {
@@ -510,6 +534,11 @@ public OutputT expand(InputT input) {
510534
// * It was explicitly requested.
511535
// * Python executable is not available in the system but Docker is available.
512536
if (useTransformService || (!pythonAvailable && dockerAvailable)) {
537+
if (!filesToStage.isEmpty()) {
538+
throw new UnsupportedOperationException(
539+
"Staging local files is not supported when using the Docker-based transform service. "
540+
+ "Please use the default local Python-based expansion service instead.");
541+
}
513542
// A unique project name ensures that this expansion gets a dedicated instance of the
514543
// transform service.
515544
String projectName = UUID.randomUUID().toString();
@@ -550,6 +579,11 @@ public OutputT expand(InputT input) {
550579
if (requirementsFile != null) {
551580
args.add("--requirements_file=" + requirementsFile.getAbsolutePath());
552581
}
582+
if (!filesToStage.isEmpty()) {
583+
for (String file : filesToStage) {
584+
args.add("--files_to_stage=" + file);
585+
}
586+
}
553587
PythonService service =
554588
new PythonService(
555589
"apache_beam.runners.portability.expansion_service_main", args.build())

sdks/java/extensions/python/src/test/java/org/apache/beam/sdk/extensions/python/PythonExternalTransformTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ public void pythonTransformWithDependencies() {
8484
}
8585
}
8686

87+
@Test
88+
public void testFilesToStage() {
89+
PythonExternalTransform<?, ?> transform =
90+
PythonExternalTransform
91+
.<PCollection<KV<String, String>>, PCollection<KV<String, Iterable<String>>>>from(
92+
"DummyTransform")
93+
.withFilesToStage(ImmutableList.of("file1.py", "file2.py"));
94+
95+
assertEquals(ImmutableList.of("file1.py", "file2.py"), transform.getFilesToStage());
96+
}
97+
8798
@Test
8899
public void generateArgsEmpty() {
89100
PythonExternalTransform<?, ?> transform =
@@ -393,4 +404,29 @@ public void testLoopbackEnvironmentWithPythonExternalTransform() {
393404
.apply(Keys.create());
394405
PAssert.that(output).containsInAnyOrder("A", "B");
395406
}
407+
408+
@Test
409+
public void testFilesToStageWithTransformServiceThrows() {
410+
PythonExternalTransformOptions options =
411+
PipelineOptionsFactory.create().as(PythonExternalTransformOptions.class);
412+
options.setUseTransformService(true);
413+
414+
Pipeline p = Pipeline.create(options);
415+
416+
PythonExternalTransform<PCollection<String>, PCollection<String>> transform =
417+
PythonExternalTransform.<PCollection<String>, PCollection<String>>from("DummyTransform")
418+
.withFilesToStage(ImmutableList.of("file1.py"));
419+
420+
PCollection<String> input = p.apply(Create.of("a"));
421+
422+
try {
423+
input.apply(transform);
424+
org.junit.Assert.fail("Expected UnsupportedOperationException");
425+
} catch (UnsupportedOperationException e) {
426+
assertTrue(
427+
e.getMessage()
428+
.contains(
429+
"Staging local files is not supported when using the Docker-based transform service"));
430+
}
431+
}
396432
}

0 commit comments

Comments
 (0)