在我们的旧作业调度软件(构建于crontab之上),我们使用apache配置格式(parser)来编写作业定义,我们使用perl config general来解析配置文件。该软件是高度自定义的,具有在检查是否满足该命令的依赖性之后在我的作业中运行命令,在命令失败时重新安排作业,支持自定义通知等功能。
我们现在计划在python中重写这个软件,并考虑像YAML而不是apache config这样的选项来编写作业定义。 YAML是否适合编写这样的动态配置?
作业定义示例(每天凌晨2点运行此作业,检查是否是星期二而不是假期在印度,如果是,请保留我的航班并发送通知):
// python function to check if it is tuesday
checkIfTuesdayAndNotHoliday()
<job>
calendar: indian
<dependency: arbitrary_python_code: checkIfTuesdayAndNotHoliday()>
<command>
check availability of flight
</command>
<success: notify: email: agrawall/>
<failure: notify: email: ops>
<command>
some command to book my flight
</command>
</job>
<crontab> 0 2 * * * </crontab>
我很难理解我应该使用什么格式来定义作业(YAML,Apache Config,XML,JSON等)。请注意,此作业定义将在我的python脚本中转换为作业对象。
我们当前使用https://metacpan.org/source/TLINDEN/Config-General-2.63/General.pm#L769
的perl中的Apache配置解析器我在python中的Apache配置解析器我们计划使用https://github.com/etingof/apacheconfig
答案 0 :(得分:2)
在Python 1.6中(即2000之前),基于Python的配置文件至少以distutils
'setup.py
的形式出现。使用这种格式的主要缺点是难以以编程方式更新config中的值。即使您只是想提供一些其他实用程序来分析这些文件,您也必须格外小心,您可以在不执行代码的情况下导入这样的配置文件,也无需通过导入获取各种依赖关系。这可以通过使用if __name__ == '__main__':
来实现,或者更容易地通过仅将配置信息作为文件中的数据结构来实现。
因此,如果更新文件永远不会成为问题,那么您将使用基于Python的数据结构,并且这些结构非常易读。
XML和JSON并不是手动编辑的好格式。 XML必须有许多<
和>
才能轻松键入而无需特殊工具。 JSON有很多双引号,这使内容难以阅读,但是它也存在各种问题,因为JSON不允许在数组和对象中尾随逗号,导致人们编写如下对象:
{
"a": 1
, "b": 2
}
这可以防止您删除最后一行而忘记删除分隔键/值对的逗号,但是IMO可读性不同。
另一方面,您可以使用YAML标记(以及与这些标记关联的合适的Python对象),因此您不必依赖于从某些键值对中解释键来了解该值所解释的内容:
- !Job
calendar: !Calendar indian
dependency: !Arbitrary_python_code checkIfTuesdayAndNotHoliday()
command: !CommandTester
exec: !Exec check availability of flight
success: !Commands
- !Notify
email: agrawall
- !Exec some command to book my flight
failure: !Commands
- !Notify
email: ops
(底部是与这些标记关联的类的 partial 示例实现)
使用ruamel.yaml
时,YAML也可以以编程方式更新,即使不会丢失注释,键顺序和标签(免责声明:我是该程序包的作者)。
我一直在参数化我的Python封装(我管理了100多个软件包,其中一些在PyPI上,其他仅用于特定客户端),这已经有一段时间了,方法是从每个软件包读取我的通用setup.py
的配置参数包的__init__.py
文件中的内容。我已经尝试过插入Python的JSON子集,但最终开发了PON(Python对象表示法),可以轻松地由setup.py
进行解析,而无需将__init__.py
文件导入较小的(100行)在Python标准库中包含的AST literal_eval
上的扩展名。
PON可以在没有任何库的情况下使用(因为它是Python数据结构的子集,包括dict,list,set,tuple和基本类型,例如整数,浮点数,布尔值,字符串,日期,日期时间。因为它基于AST评估程序,您可以在配置文件中进行计算(secs_per_day = 24 * 60 * 60
)和其他评估。
PON自述文件还更详细地描述了该格式相对于YAML,JSON,INI,XML的优点(和缺点)。
不需要使用PON软件包来使用配置数据,仅当您要对PON数据进行编程往返(加载-编辑-转储)时才需要。
import sys
from ruamel.yaml import YAML, yaml_object
yaml = YAML()
@yaml_object(yaml)
class CommandTester:
yaml_tag = u'!CommandTester'
def __init__(self, exec=None, success=None, failure=None):
self.exec = exec
self.success = success
self.failure = failure
def __call__(self):
if self.exec():
self.success()
else:
self.failure()
@yaml_object(yaml)
class Commands:
"""a list of commands"""
yaml_tag = u'!Commands'
def __init__(self, commands):
self._commands = commands # list of commands to execute
@classmethod
def from_yaml(cls, constructor, node):
for m in yaml.constructor.construct_yaml_seq(node):
pass
return cls(m)
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_sequence(cls.yaml_tag, node._commands)
def __call__(self, verbose=0, stop_on_error=False):
res = True
for cmd in self._cmd:
try:
res = subprocess.check_output(cmd)
except Exception as e:
res = False
if stop_on_error:
break
return res
@yaml_object(yaml)
class Command(Commands):
"""a single command"""
yaml_tag = u'!Exec'
def __init__(self, command):
Commands.__init__(self, [command])
@classmethod
def from_yaml(cls, constructor, node):
return cls(node.value)
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_scalar(cls.yaml_tag, node._commands[0])
@yaml_object(yaml)
class Notifier:
yaml_tag = u'!Notify'
with open("job.yaml") as fp:
job = yaml.load(fp)
yaml.dump(job, sys.stdout)
答案 1 :(得分:1)
新趋势是使用Python文件作为配置。这是在Django和Flask中完成的。它是人类可读的,易于定义和更新,当然也很容易转换为Python对象。
另请参阅对“Pros and cons for different configuration formats?”的回答。
另请参阅本文“Configuration files in Python”。
以下是一个示例(setting.py
):
def check_if_tuesday_and_not_holiday():
"""check if it is tuesday and not holiday"""
return True
JOB = {
'calendar': 'indian',
'dependency': {
'arbitrary_python_code': check_if_tuesday_and_not_holiday # callback
},
'command': 'check availability of flight',
'success': {
'notify': {
'email': 'agrawall'
},
'command': 'some command to book my flight'
},
'failure': {
'notify': {
'email': 'ops'
}
}
}
CRONTAB = '0 2 * * *'
注意:我不确定要了解您的配置文件,所以我会尽力使它适应Python ...