Python任务调度器Luigi能否检测间接依赖关系?

Python任务调度器Luigi能否检测间接依赖关系?,python,dependencies,scheduled-tasks,luigi,Python,Dependencies,Scheduled Tasks,Luigi,短版: Python中是否有一个任务调度器可以像gmake那样执行任务?特别是,我需要一个递归解析依赖项的任务调度器。我查看了Luigi,但它似乎只解决了直接依赖关系 长版本: 我试图构建一个工作流,以预定义的顺序处理大量数据文件,后面的任务可能直接依赖于一些早期任务的输出,但反过来,这些输出的正确性依赖于更早期的任务 例如,让我们考虑如下的依赖图: A覆盖任务的完整方法似乎是可能的。您必须在依赖关系图中一直应用此方法 def complete(self): outputs = self

短版:

Python中是否有一个任务调度器可以像gmake那样执行任务?特别是,我需要一个递归解析依赖项的任务调度器。我查看了Luigi,但它似乎只解决了直接依赖关系

长版本:

我试图构建一个工作流,以预定义的顺序处理大量数据文件,后面的任务可能直接依赖于一些早期任务的输出,但反过来,这些输出的正确性依赖于更早期的任务

例如,让我们考虑如下的依赖图:


A覆盖任务的完整方法似乎是可能的。您必须在依赖关系图中一直应用此方法

def complete(self):
    outputs = self.flatten(self.output())
    if not all(map(lambda output: output.exists(), outputs)):
        return False
    for task in self.flatten(self.requires()):
        if not task.complete():
            for output in outputs:
                if output.exists():
                    output.remove()
            return False
    return True

事实上,这是不方便的,它检查所有上游依赖项的完整性,而不仅仅是TaskC是否存在输出。如果重置TaskA,TaskC也将不完整并自动重新运行

# reset TaskA => makes TaskC incomplete
TaskA().invalidate() 
d6tflow.preview(TaskC()) # all tasks pending
有关更多详细信息,请参阅下面的完整示例和


您好@MattMcKnight,非常感谢您指出“完整”方法!我试试这个解决办法。然而,我想知道为什么Luigi在默认情况下不这么做?使用PythonforLoop手动解析依赖关系可能不是很有效,像我这样的普通用户可能会犯错误和/或做得不够理想。但无论如何,非常感谢!:)要使上述代码正常工作,需要进行一些更正。首先,方法
flatten
是由Luigi定义的,因此需要称为
self.flatte
。其次,我发现最好在最后重用默认的
complete
方法来检查任务本身的状态,因此我编写了
returnsuper(MyTask,self).complete()
,假设继承的类名为
MyTask
。但是,在运行下游任务时,仍然会出现“文件已存在”错误,因为它们的输出文件未被删除。知道如何自动更新这些输出文件吗?很抱歉没有尝试代码,只是想给出一个粗略的方法。我想除了从complete返回
False
,还必须删除现有的输出。Task.complete()方法没有那么复杂。也有一些类似的清理类型的进程的请求。嗨@MattMcKnight,谢谢你的更新!我发现Luigi LocalTarget有一个“remove”方法,可以用来删除文件。因此,我将调用output.remove(),而不是使用os.remove方法。我会接受这个答案,并发布我的最终测试版本。非常感谢您的帮助!:)您能简要地评论一下库用于解决递归依赖关系的算法的效率吗?这个库在没有明显延迟的情况下可以处理的任务池的典型大小是多少?不确定,它在实践中从来都不是问题,我用它构建了大规模的ML系统。工作发生在任务内部,库只是协调。一名用户培训了20K ML模型。
import d6tflow
import pandas as pd

class TaskA(d6tflow.tasks.TaskCachePandas):  # save dataframe in memory

    def run(self):        
        self.save(pd.DataFrame({'a':range(10)})) # quickly save dataframe

class TaskB(d6tflow.tasks.TaskCachePandas):

    def requires(self):
        return TaskA() # define dependency

    def run(self):
        df = self.input().load() # quickly load required data
        df = df*2
        self.save(df)

class TaskC(d6tflow.tasks.TaskCachePandas):

    def requires(self):
        return TaskB()

    def run(self):
        df = self.input().load() 
        df = df*2
        self.save(df)

# Check task dependencies and their execution status
d6tflow.preview(TaskC())
'''
└─--[TaskC-{} (PENDING)]
   └─--[TaskB-{} (PENDING)]
      └─--[TaskA-{} (PENDING)]
'''

# Execute the model training task including dependencies
d6tflow.run(TaskC())

'''
===== Luigi Execution Summary =====

Scheduled 3 tasks of which:
* 3 ran successfully:
    - 1 TaskA()
    - 1 TaskB()
    - 1 TaskC()
'''

# all tasks complete
d6tflow.preview(TaskC())

'''
└─--[TaskC-{} (COMPLETE)]
   └─--[TaskB-{} (COMPLETE)]
      └─--[TaskA-{} (COMPLETE)]
'''

# reset TaskA => makes TaskC incomplete
TaskA().invalidate() 
d6tflow.preview(TaskC())
'''
└─--[TaskC-{} (PENDING)]
   └─--[TaskB-{} (PENDING)]
      └─--[TaskA-{} (PENDING)]
'''