简短版本:
Python中是否有可以执行gmake的任务调度程序?特别是,我需要一个以递归方式解析依赖关系的任务调度程序。我查看了Luigi,但似乎只解决了直接的依赖关系。
长版:
我正在尝试构建一个以预定义顺序处理大量数据文件的工作流,后面的任务可能直接依赖于某些早期任务的输出,但反过来,这些输出的正确性依赖甚至更早的任务。
例如,让我们考虑如下的依赖关系图:
A&lt; -B < - C
当我从任务C请求结果时,Luigi将自动安排B,然后由于B依赖于A,它将安排A.所以最终的运行顺序将是[A,B,C]。每个任务都将创建一个官方输出文件,作为成功执行的标志。这对于第一次运行来说很好。
现在,假设我在任务A的输入数据中犯了一个错误。显然,我需要再次重新运行整个链。但是,简单地从A中删除输出文件就不会起作用。因为Luigi看到了B和C的输出,并得出结论,任务C的要求已经完成,并且不需要运行。我必须从 ALL 中删除依赖于A的任务的输出文件,以便它们再次运行。在简单的情况下,我必须删除A,B和C中的所有输出文件,以便Luigi检测到对A的更改。
这是一个非常不方便的功能。如果我有几十个或几百个相互之间具有相当复杂依赖关系的任务,那么当需要重新运行其中一个任务时,很难确定哪些任务受到影响。对于任务调度程序并具有解析依赖关系的能力,我希望Luigi能够采用类似于GNU-Make的方式,其中依赖关系以递归方式检查,并且当最深的源文件之一被更改时,将重建最终目标。
我想知道是否有人可以就此问题提供一些建议。我错过了Luigi的一些关键功能吗?是否还有其他任务调度程序可以充当gmake?我对基于Python的软件包特别感兴趣,并且更喜欢那些支持Windows。
非常感谢!
答案 0 :(得分:2)
似乎可以通过覆盖完成任务的方法来实现。您必须在依赖图中一直向下应用它。
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
答案 1 :(得分:1)
实际上这很不方便,d6tflow检查所有上游依赖项的完整性,而不仅仅是TaskC的输出是否存在。如果重置TaskA,TaskC也将不完整并自动重新运行。
# reset TaskA => makes TaskC incomplete
TaskA().invalidate()
d6tflow.preview(TaskC()) # all tasks pending
有关更多详细信息,请参见下面的完整示例和d6tflow docs。
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)]
'''