我有一个luigi
预处理任务,可以将原始数据拆分为较小的文件。然后,这些文件将由实际管道处理。
关于参数,我想要求每个管道都有一个预处理文件id作为参数。但是,此文件ID仅在预处理步骤中生成,因此仅在运行时已知。为了说明我的想法,我提供了这个无效的代码:
import luigi
import subprocess
import random
class GenPipelineFiles(luigi.Task):
input_file = luigi.Parameter()
def requires(self):
pass
def output(self):
for i in range(random.randint(0,10)):
yield luigi.LocalTarget("output/{}_{}.txt".format(self.input_file, i))
def run(self):
for iout in self.output:
command = "touch {}".format(iout.fname)
subprocess.call(command, shell=True)
class RunPipelineOnSmallChunk(luigi.Task):
pass
class Experiment(luigi.WrapperTask):
input_file = luigi.Parameter(default="ex")
def requires(self):
file_ids = GenPipelineFiles(input_file=self.input_file)
for file_id in file_ids:
yield RunPipelineOnSmallChunk(directory=self.input_file, file_id=file_id)
luigi.run()
包装器任务Experiment
应该
首先,不知何故需要将原始数据拆分为文档
其次,要求实际管道获得预处理的文件ID。
GenPipelineFiles
中的随机输出文件数表示无法将其硬编码到Experiment
的{{1}}中。
可能与此问题相关的问题是,requires
任务正确地只有一个输入目标和一个输出目标。可能关于如何在luigi
中建模多个输出的说明也可以解决问题。
答案 0 :(得分:1)
处理多个输出的一个简单方法是创建一个以输入文件命名的目录,并将拆分中的输出文件放入以输入文件命名的目录中。这样,依赖任务可以检查目录是否存在。假设我有一个输入文件123.txt,然后我创建一个目录123_split,文件1.txt,2.txt,3.txt作为GenPipelineFiles
的输出,然后一个目录123_processed with 1.txt,2.txt,3.txt作为RunPipelineOnSmallChunk
的输出。
对于requires
中的Experiment
方法,您必须在列表中返回要运行的任务。你写file_ids = GenPipelineFiles(input_file=self.input_file)
的方式让我觉得没有调用该对象的run方法,因为它没有被方法返回。
这里有一些示例代码,它们基于每个文件(但不是每个文件的任务)来处理目标。我仍然认为拥有一个目录的单个输出目标或某种类型的sentinel文件来表明你已经完成更安全。除非任务确保创建每个目标,否则原子性将丢失。
PYTHONPATH=. luigi --module sampletask RunPipelineOnSmallChunk --local-scheduler
sampletask.py
import luigi
import os
import subprocess
import random
class GenPipelineFiles(luigi.Task):
inputfile = luigi.Parameter()
num_targets = random.randint(0,10)
def requires(self):
pass
def get_prefix(self):
return self.inputfile.split(".")[0]
def get_dir(self):
return "split_{}".format(self.get_prefix())
def output(self):
targets = []
for i in range(self.num_targets):
targets.append(luigi.LocalTarget(" {}/{}_{}.txt".format(self.get_dir(), self.get_prefix(), i)))
return targets
def run(self):
if not os.path.exists(self.get_dir()):
os.makedirs(self.get_dir())
for iout in self.output():
command = "touch {}".format(iout.path)
subprocess.call(command, shell=True)
class RunPipelineOnSmallChunk(luigi.Task):
inputfile = luigi.Parameter(default="test")
def get_prefix(self):
return self.inputfile.split(".")[0]
def get_dir(self):
return "processed_{}".format(self.get_prefix())
@staticmethod
def clean_input_path(path):
return path.replace("split", "processed")
def requires(self):
return GenPipelineFiles(self.inputfile)
def output(self):
targets = []
for target in self.input():
targets.append(luigi.LocalTarget(RunPipelineOnSmallChunk.clean_input_path(target.path)))
return targets
def run(self):
if not os.path.exists(self.get_dir()):
os.makedirs(self.get_dir())
for iout in self.output():
command = "touch {}".format(iout.path)
subprocess.call(command, shell=True)