| # | |
| # Licensed to the Apache Software Foundation (ASF) under one or more | |
| # contributor license agreements. See the NOTICE file distributed with | |
| # this work for additional information regarding copyright ownership. | |
| # The ASF licenses this file to You under the Apache License, Version 2.0 | |
| # (the "License"); you may not use this file except in compliance with | |
| # the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| import logging | |
| import apache_beam as beam | |
| from apache_beam.options.pipeline_options import PipelineOptions | |
| from apache_beam.transforms.external import BeamJarExpansionService | |
| from apache_beam.transforms.external_transform_provider import ExternalTransformProvider | |
| from apache_beam.typehints.row_type import RowTypeConstraint | |
| """A Python multi-language pipeline that counts words using multiple Java SchemaTransforms. | |
| This pipeline reads an input text file then extracts the words, counts them, and writes the results Java | |
| SchemaTransforms. The transforms are listed below and can be found in | |
| src/main/java/org/apache/beam/examples/schematransforms/: | |
| - `ExtractWordsProvider` | |
| - `JavaCountProvider` | |
| - `WriteWordsProvider` | |
| These Java transforms are accessible to the Python pipeline via an expansion service. Check out the | |
| [`README.md`](https://github.com/apache/beam/blob/master/examples/multi-language/README.md#1-start-the-expansion-service) | |
| for instructions on how to download the jar and run this expansion service. | |
| This example aims to demonstrate how to use the `ExternalTransformProvider` utility, which dynamically generates and | |
| provides user-friendly wrappers for external transforms. | |
| Example commands for executing this program: | |
| DirectRunner: | |
| $ python wordcount_external.py \ | |
| --runner DirectRunner \ | |
| --input <INPUT FILE> \ | |
| --output <OUTPUT FILE> \ | |
| --expansion_service_port <PORT> | |
| DataflowRunner: | |
| $ python wordcount_external.py \ | |
| --runner DataflowRunner \ | |
| --temp_location $TEMP_LOCATION \ | |
| --project $GCP_PROJECT \ | |
| --region $GCP_REGION \ | |
| --job_name $JOB_NAME \ | |
| --num_workers $NUM_WORKERS \ | |
| --input "gs://dataflow-samples/shakespeare/kinglear.txt" \ | |
| --output "gs://$GCS_BUCKET/wordcount_external/output" \ | |
| --expansion_service_port <PORT> | |
| """ | |
| EXTRACT_IDENTIFIER = "beam:schematransform:org.apache.beam:extract_words:v1" | |
| COUNT_IDENTIFIER = "beam:schematransform:org.apache.beam:count:v1" | |
| WRITE_IDENTIFIER = "beam:schematransform:org.apache.beam:write_words:v1" | |
| def run(input_path, output_path, expansion_service_port, pipeline_args): | |
| pipeline_options = PipelineOptions(pipeline_args) | |
| with beam.Pipeline(options=pipeline_options) as p: | |
| expansion_service = BeamJarExpansionService( | |
| "examples:multi-language:shadowJar") | |
| if expansion_service_port: | |
| expansion_service = "localhost:" + expansion_service_port | |
| provider = ExternalTransformProvider(expansion_service) | |
| # Retrieve portable transforms | |
| Extract = provider.get_urn(EXTRACT_IDENTIFIER) | |
| Count = provider.get_urn(COUNT_IDENTIFIER) | |
| Write = provider.get_urn(WRITE_IDENTIFIER) | |
| _ = (p | |
| | 'Read' >> beam.io.ReadFromText(input_path) | |
| | 'Prepare Rows' >> beam.Map(lambda line: beam.Row(line=line)) | |
| | 'Extract Words' >> Extract(drop=["king", "palace"]) | |
| | 'Count Words' >> Count() | |
| | 'Format Text' >> beam.Map(lambda row: beam.Row(line="%s: %s" % ( | |
| row.word, row.count))).with_output_types( | |
| RowTypeConstraint.from_fields([('line', str)])) | |
| | 'Write' >> Write(file_path_prefix=output_path)) | |
| if __name__ == '__main__': | |
| logging.getLogger().setLevel(logging.INFO) | |
| import argparse | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--input', | |
| dest='input', | |
| required=True, | |
| help='Input file') | |
| parser.add_argument('--output', | |
| dest='output', | |
| required=True, | |
| help='Output file') | |
| parser.add_argument('--expansion_service_port', | |
| dest='expansion_service_port', | |
| required=False, | |
| help='Expansion service port. If left empty, the ' | |
| 'existing multi-language examples service will ' | |
| 'be used by default.') | |
| known_args, pipeline_args = parser.parse_known_args() | |
| run(known_args.input, known_args.output, known_args.expansion_service_port, | |
| pipeline_args) |