如何写入运行时定义的文件名?

时间:2018-01-30 11:03:15

标签: apache-beam

我想写一个gs文件,但我不知道编译时的文件名。它的名称基于在运行时定义的行为。我该怎么办?

4 个答案:

答案 0 :(得分:6)

如果你正在使用Beam Java,你可以使用FileIO.writeDynamic()(从目前正在发布的Beam 2.3开始 - 但你已经可以通过版本2.3.0-SNAPSHOT使用它了),或较旧的DynamicDestinations API(在Beam 2.2中可用)。

根据交易类型,使用FileIO.writeDynamic()PCollection银行交易写入GCS上不同路径的示例:

PCollection<BankTransaction> transactions = ...;
transactions.apply(
    FileIO.<BankTransaction, TransactionType>writeDynamic()
      .by(Transaction::getType)
      .via(BankTransaction::toString, TextIO.sink())
      .to("gs://bucket/myfolder/")
      .withNaming(type -> defaultNaming("transactions_", ".txt"));

有关DynamicDestinations使用的示例,请参阅example code in the TextIO unit tests

或者,如果您想将每条记录写入自己的文件,只需使用FileSystems中的FileSystems.create() API(特别是DoFn)。

答案 1 :(得分:1)

正如@anrope所述,apache_beam.io.fileio似乎是用于编写文件的最新Python API。由于使用了WriteToText类,因此WordCount示例目前已过时,该类继承自现已弃用的apache_beam.io.filebasedsink / apache_beam.io.iobase

要添加到现有答案中,这是我的管道,我在其中运行时动态命名输出文件。我的管道接收N个输入文件并创建N个输出文件,这些文件根据其相应的输入文件名进行命名。

with beam.Pipeline(options=pipeline_options) as p:
    (p
        | 'CreateFiles' >> beam.Create(input_file_paths)
        | 'MatchFiles' >> MatchAll()
        | 'OpenFiles' >> ReadMatches()
        | 'LoadData' >> beam.Map(custom_data_loader)
        | 'Transform' >> beam.Map(custom_data_transform)
        | 'Write' >> custom_writer
    )

当我加载数据时,我创建了一个元组记录(file_name, data)的PCollection。我所有的变换都应用于data,但是我将file_name传递到管道的末尾以生成输出文件名。

def custom_data_loader(f: beam.io.fileio.ReadableFile):
    file_name = f.metadata.path.split('/')[-1]
    data = custom_read_function(f.open())
    return file_name, data

def custom_data_transform(record):
    file_name, data = record
    data = custom_transform_function(data)
    return file_name, data

然后我将文件保存为:

def file_naming(record):
    file_name, data = record
    file_name = custom_naming_function(file_name)
    return file_name

def return_destination(*args):
    """Optional: Return only the last arg (destination) to avoid sharding name format"""
    return args[-1]

pickle_writer = WriteToFiles(
    path='path/to/output',
    file_naming=return_destination,
    destination=file_naming,
    sink=TextSink()
)

使用您自己的逻辑替换所有custom_*函数。

答案 2 :(得分:0)

对于Python人群:

在2.14.0 beam.io.fileio.WriteToFiles的Beam python SDK中添加了实验性写法:

my_pcollection | beam.io.fileio.WriteToFiles(
      path='/my/file/path',
      destination=lambda record: 'avro' if record['type'] == 'A' else 'csv',
      sink=lambda dest: AvroSink() if dest == 'avro' else CsvSink(),
      file_naming=beam.io.fileio.destination_prefix_naming())

可用于每条记录写入不同的文件。

如果文件名基于集合中的数据,则可以使用destinationfile_naming根据每条记录的数据创建文件。

更多文档在这里:

https://beam.apache.org/releases/pydoc/2.14.0/apache_beam.io.fileio.html#dynamic-destinations

此处还有JIRA问题:

https://issues.apache.org/jira/browse/BEAM-2857

答案 3 :(得分:0)

我知道这是一个古老的问题,但是我对文档中的示例感到困惑。

这是一个简单的示例,说明如何根据字典项拆分文件。

pipeline_options = PipelineOptions()
pipeline_options.view_as(SetupOptions).save_main_session = False


def file_names(*args):
    file_name = fileio.destination_prefix_naming()(*args)
    destination, *_ = file_name.split("----")
    return f"{destination}.json"


class JsonSink(fileio.TextSink):
    def write(self, element):
        record = json.loads(element)
        record.pop("id")
        self._fh.write(json.dumps(record).encode("utf8"))
        self._fh.write("\n".encode("utf8"))


def destination(element):
    return json.loads(element)["id"]


with beam.Pipeline(options=pipeline_options) as p:

    data = [
        {"id": 0, "message": "whhhhhhyyyyyyy"},
        {"id": 1, "message": "world"},
        {"id": 1, "message": "hi there!"},
        {"id": 1, "message": "what's up!!!?!?!!?"},
    ]

    (
        p
        | "CreateEmails" >> beam.Create(data)
        | "JSONify" >> beam.Map(json.dumps)
        | "Write Files"
        >> fileio.WriteToFiles(
            path="path/",
            destination=destination,
            sink=lambda dest: JsonSink(),
            file_naming=file_names,
        )
    )