This is an automated email from the ASF dual-hosted git repository.
robertwb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 4ee26065d9b Accept runner and options in ib.collect. (#32434)
4ee26065d9b is described below
commit 4ee26065d9b4ea074d3d59d38cc4fbc5aa9dbfa5
Author: Robert Bradshaw <[email protected]>
AuthorDate: Thu Sep 12 11:56:41 2024 -0700
Accept runner and options in ib.collect. (#32434)
---
.../display/pcoll_visualization_test.py | 2 +-
.../runners/interactive/interactive_beam.py | 12 ++++++++-
.../interactive/non_interactive_runner_test.py | 30 ++++++++++++++++++++++
.../runners/interactive/pipeline_fragment.py | 12 +++++----
.../runners/interactive/recording_manager.py | 17 ++++++++++--
5 files changed, 64 insertions(+), 9 deletions(-)
diff --git
a/sdks/python/apache_beam/runners/interactive/display/pcoll_visualization_test.py
b/sdks/python/apache_beam/runners/interactive/display/pcoll_visualization_test.py
index d34b966b0ef..7fc76feb749 100644
---
a/sdks/python/apache_beam/runners/interactive/display/pcoll_visualization_test.py
+++
b/sdks/python/apache_beam/runners/interactive/display/pcoll_visualization_test.py
@@ -66,7 +66,7 @@ class PCollectionVisualizationTest(unittest.TestCase):
ie.current_env().track_user_pipelines()
recording_manager = RecordingManager(self._p)
- recording = recording_manager.record([self._pcoll], 5, 5)
+ recording = recording_manager.record([self._pcoll], max_n=5,
max_duration=5)
self._stream = recording.stream(self._pcoll)
def test_pcoll_visualization_generate_unique_display_id(self):
diff --git a/sdks/python/apache_beam/runners/interactive/interactive_beam.py
b/sdks/python/apache_beam/runners/interactive/interactive_beam.py
index 5c76f9c228c..0e170eb0f50 100644
--- a/sdks/python/apache_beam/runners/interactive/interactive_beam.py
+++ b/sdks/python/apache_beam/runners/interactive/interactive_beam.py
@@ -880,6 +880,8 @@ def collect(
n='inf',
duration='inf',
include_window_info=False,
+ runner=None,
+ options=None,
force_compute=False,
force_tuple=False):
"""Materializes the elements from a PCollection into a Dataframe.
@@ -896,6 +898,9 @@ def collect(
a string duration. Default 'inf'.
include_window_info: (optional) if True, appends the windowing information
to each row. Default False.
+ runner: (optional) the runner with which to compute the results
+ options: (optional) any additional pipeline options to use to compute the
+ results
force_compute: (optional) if True, forces recomputation rather than using
cached PCollections
force_tuple: (optional) if True, return a 1-tuple or results rather than
@@ -969,7 +974,12 @@ def collect(
uncomputed = set(pcolls) - set(computed.keys())
if uncomputed:
recording = recording_manager.record(
- uncomputed, max_n=n, max_duration=duration,
force_compute=force_compute)
+ uncomputed,
+ max_n=n,
+ max_duration=duration,
+ runner=runner,
+ options=options,
+ force_compute=force_compute)
try:
for pcoll in uncomputed:
diff --git
a/sdks/python/apache_beam/runners/interactive/non_interactive_runner_test.py
b/sdks/python/apache_beam/runners/interactive/non_interactive_runner_test.py
index 47adf7b36b3..f7fd052fecc 100644
--- a/sdks/python/apache_beam/runners/interactive/non_interactive_runner_test.py
+++ b/sdks/python/apache_beam/runners/interactive/non_interactive_runner_test.py
@@ -257,6 +257,36 @@ class NonInteractiveRunnerTest(unittest.TestCase):
df_expected['cube'],
ib.collect(df['cube'], n=10).reset_index(drop=True))
+ @unittest.skipIf(sys.platform == "win32", "[BEAM-10627]")
+ def test_new_runner_and_options(self):
+ class MyRunner(beam.runners.PipelineRunner):
+ run_count = 0
+
+ @classmethod
+ def run_pipeline(cls, pipeline, options):
+ assert options._all_options['my_option'] == 123
+ cls.run_count += 1
+ return direct_runner.DirectRunner().run_pipeline(pipeline, options)
+
+ clear_side_effect()
+ p = beam.Pipeline(direct_runner.DirectRunner())
+
+ # Initial collection runs the pipeline.
+ pcoll1 = p | beam.Create(['a', 'b', 'c']) | beam.Map(cause_side_effect)
+ collected1 = ib.collect(pcoll1)
+ self.assertEqual(set(collected1[0]), set(['a', 'b', 'c']))
+ self.assertEqual(count_side_effects('a'), 1)
+
+ # Using the PCollection uses the cache with a different runner and options.
+ pcoll2 = pcoll1 | beam.Map(str.upper)
+ collected2 = ib.collect(
+ pcoll2,
+ runner=MyRunner(),
+ options=beam.options.pipeline_options.PipelineOptions(my_option=123))
+ self.assertEqual(set(collected2[0]), set(['A', 'B', 'C']))
+ self.assertEqual(count_side_effects('a'), 1)
+ self.assertEqual(MyRunner.run_count, 1)
+
if __name__ == '__main__':
unittest.main()
diff --git a/sdks/python/apache_beam/runners/interactive/pipeline_fragment.py
b/sdks/python/apache_beam/runners/interactive/pipeline_fragment.py
index 5b385d3f8a0..20dee2b7116 100644
--- a/sdks/python/apache_beam/runners/interactive/pipeline_fragment.py
+++ b/sdks/python/apache_beam/runners/interactive/pipeline_fragment.py
@@ -34,7 +34,7 @@ class PipelineFragment(object):
A pipeline fragment is built from the original pipeline definition to include
only PTransforms that are necessary to produce the given PCollections.
"""
- def __init__(self, pcolls, options=None):
+ def __init__(self, pcolls, options=None, runner=None):
"""Constructor of PipelineFragment.
Args:
@@ -42,6 +42,8 @@ class PipelineFragment(object):
fragment for.
options: (PipelineOptions) the pipeline options for the implicit
pipeline run.
+ runner: (Runner) the pipeline runner for the implicit
+ pipeline run.
"""
assert len(pcolls) > 0, (
'Need at least 1 PCollection as the target data to build a pipeline '
@@ -61,6 +63,7 @@ class PipelineFragment(object):
'given and cannot be used to build a pipeline fragment that produces
'
'the given PCollections.'.format(pcoll))
self._options = options
+ self._runner = runner
# A copied pipeline instance for modification without changing the user
# pipeline instance held by the end user. This instance can be processed
@@ -98,7 +101,7 @@ class PipelineFragment(object):
"""Deduce the pipeline fragment as an apache_beam.Pipeline instance."""
fragment = beam.pipeline.Pipeline.from_runner_api(
self._runner_pipeline.to_runner_api(),
- self._runner_pipeline.runner,
+ self._runner or self._runner_pipeline.runner,
self._options)
ie.current_env().add_derived_pipeline(self._runner_pipeline, fragment)
return fragment
@@ -129,9 +132,8 @@ class PipelineFragment(object):
'the Beam pipeline to use this function '
'on unbouded PCollections.')
result = beam.pipeline.Pipeline.from_runner_api(
- pipeline_instrument_proto,
- self._runner_pipeline.runner,
- self._runner_pipeline._options).run()
+ pipeline_instrument_proto, fragment.runner,
+ fragment._options).run()
result.wait_until_finish()
ie.current_env().mark_pcollection_computed(
pipeline_instrument.cached_pcolls)
diff --git a/sdks/python/apache_beam/runners/interactive/recording_manager.py
b/sdks/python/apache_beam/runners/interactive/recording_manager.py
index cb28a61a95f..6811d3e0d34 100644
--- a/sdks/python/apache_beam/runners/interactive/recording_manager.py
+++ b/sdks/python/apache_beam/runners/interactive/recording_manager.py
@@ -28,7 +28,9 @@ import pandas as pd
import apache_beam as beam
from apache_beam.dataframe.frame_base import DeferredBase
+from apache_beam.options import pipeline_options
from apache_beam.portability.api import beam_runner_api_pb2
+from apache_beam.runners import runner
from apache_beam.runners.interactive import background_caching_job as bcj
from apache_beam.runners.interactive import interactive_environment as ie
from apache_beam.runners.interactive import interactive_runner as ir
@@ -384,8 +386,11 @@ class RecordingManager:
def record(
self,
pcolls: List[beam.pvalue.PCollection],
+ *,
max_n: int,
max_duration: Union[int, str],
+ runner: runner.PipelineRunner = None,
+ options: pipeline_options.PipelineOptions = None,
force_compute: bool = False) -> Recording:
# noqa: F821
@@ -427,12 +432,20 @@ class RecordingManager:
# incomplete.
self._clear()
+ merged_options = pipeline_options.PipelineOptions(
+ **{
+ **self.user_pipeline.options.get_all_options(
+ drop_default=True, retain_unknown_options=True),
+ **options.get_all_options(
+ drop_default=True, retain_unknown_options=True)
+ }) if options else self.user_pipeline.options
+
cache_path = ie.current_env().options.cache_root
is_remote_run = cache_path and ie.current_env(
).options.cache_root.startswith('gs://')
pf.PipelineFragment(
- list(uncomputed_pcolls),
- self.user_pipeline.options).run(blocking=is_remote_run)
+ list(uncomputed_pcolls), merged_options,
+ runner=runner).run(blocking=is_remote_run)
result = ie.current_env().pipeline_result(self.user_pipeline)
else:
result = None