使用完整的Zope3运行环境创建独立程序
项目描述
gocept.runner
创建具有完整Zope3运行时环境的独立程序。这些程序可以作为一次性运行脚本使用,也可以在定义的时间后自动调用。
创建运行者
gocept.runner 包允许您轻松创建小型、长时间运行的脚本,这些脚本可以与ZODB交互。当脚本运行时,它们已设置完整的组件架构。
使用appmain装饰器定义运行者
>>> import logging >>> import gocept.runner >>> work_count = 0 >>> @gocept.runner.appmain(ticks=0.1) ... def worker(): ... import zope.app.appsetup.product ... log = logging.getLogger('test') ... log.info("Working") ... log.info(sorted( ... zope.app.appsetup.product.getProductConfiguration('test').items())) ... global work_count ... work_count += 1 ... if work_count >= 3: ... return gocept.runner.Exit
被装饰的工作者现在有两个参数
根中一个对象的名称,将其设置为站点或为根设置None。
配置文件(zope.conf)的路径
创建一个简单的zope.conf
>>> import os.path >>> import tempfile >>> zodb_path = tempfile.mkdtemp() >>> site_zcml = os.path.join( ... os.path.dirname(__file__), 'ftesting.zcml') >>> fd, zope_conf = tempfile.mkstemp() >>> zope_conf_file = os.fdopen(fd, 'w') >>> _ = zope_conf_file.write('''\ ... site-definition %s ... <zodb> ... <filestorage> ... path %s/Data.fs ... </filestorage> ... </zodb> ... <product-config test> ... foo bar ... test-principal zope.mgr ... </product-config> ... <eventlog> ... <logfile> ... formatter zope.exceptions.log.Formatter ... path STDOUT ... </logfile> ... </eventlog> ... ''' % (site_zcml, zodb_path)) >>> zope_conf_file.close()
所以调用工作者
>>> worker(None, zope_conf) ------ ... INFO test Working ------ ... INFO test [('foo', 'bar'), ('test-principal', 'zope.mgr')] ------ ... INFO test Working ------ ... INFO test [('foo', 'bar'), ('test-principal', 'zope.mgr')] ------ ... INFO test Working ------ ... INFO test [('foo', 'bar'), ('test-principal', 'zope.mgr')]
信号
工作者进程可以通过SIGTERM和SIGHUP以合理的方式终止。将脚本写入临时文件
>>> import sys >>> runner_path = os.path.abspath( ... os.path.join(os.path.dirname(__file__), '..', '..')) >>> fd, script_name = tempfile.mkstemp(suffix='.py') >>> exchange_fd, exchange_file_name = tempfile.mkstemp() >>> script = os.fdopen(fd, 'w') >>> _ = script.write("""\ ... import sys ... sys.path[0:0] = %s ... sys.path.insert(0, '%s') ... import gocept.runner ... ... f = open('%s', 'w') ... ... @gocept.runner.appmain(ticks=0.1) ... def worker(): ... f.write("Working.\\n") ... f.flush() ... ... worker(None, '%s') ... """ % (sys.path, runner_path, exchange_file_name, zope_conf)) >>> script.close()
调用脚本并等待它生成一些输出
>>> import signal >>> import subprocess >>> import time >>> exchange = os.fdopen(exchange_fd, 'r+') >>> proc = subprocess.Popen( ... [sys.executable, script_name], ... stdout=subprocess.PIPE, ... text=True) >>> while not exchange.read(): ... time.sleep(0.1) ... _ = exchange.seek(0, 0) >>> _ = exchange.seek(0, 0) >>> print(exchange.read()) Working...
好吧,现在杀掉它
>>> os.kill(proc.pid, signal.SIGTERM)
等待进程真正完成并获取输出。运行者记录了它被终止的情况
>>> stdout, stderr = proc.communicate() >>> print(stdout) ------...INFO gocept.runner.runner Received signal 15, terminating...
这也适用于SIGHUP
>>> _ = exchange.truncate(0) >>> proc = subprocess.Popen( ... [sys.executable, script_name], ... stdout=subprocess.PIPE, ... text=True) >>> while not exchange.read(): ... time.sleep(0.1) ... _ = exchange.seek(0, 0) >>> _ = exchange.seek(0, 0) >>> print(exchange.read()) Working...
好吧,现在杀掉它
>>> os.kill(proc.pid, signal.SIGHUP) >>> stdout, stderr = proc.communicate() >>> print(stdout) ------...INFO gocept.runner.runner Received signal 1, terminating...
清理:>> exchange.close() >> os.remove(script_name) >> os.remove(exchange_file_name)
设置主体
也可以创建一个在交互中运行的循环
>>> def get_principal(): ... return 'zope.mgr'
>>> import zope.security.management >>> work_count = 0 >>> def interaction_worker(): ... global work_count ... work_count += 1 ... if work_count >= 3: ... raise SystemExit(1) ... log = logging.getLogger('test') ... interaction = zope.security.management.getInteraction() ... principal = interaction.participations[0].principal ... log.info("Working as %s" % principal.id) >>> worker = gocept.runner.appmain(ticks=0.1, principal=get_principal)( ... interaction_worker)
现在调用工作者
>>> worker(None, zope_conf) ------ ... INFO test Working as zope.mgr ------ ... INFO test Working as zope.mgr
工作者运行后没有交互
>>> zope.security.management.queryInteraction() is None True
通常从zope.conf中读取主体。因此有一个助手使这项任务更容易
>>> work_count = 0 >>> worker = gocept.runner.appmain( ... ticks=0.1, ... principal=gocept.runner.from_config('test', 'test-principal'))( ... interaction_worker) >>> worker(None, zope_conf) ------ ... INFO test Working as zope.mgr ------ ... INFO test Working as zope.mgr
子站点
可以直接在根内的站点上工作。当然,站点必须已经存在,否则将出现错误
>>> worker('a-site', zope_conf) Traceback (most recent call last): ... KeyError: 'a-site'
清理
>>> import shutil >>> shutil.rmtree(zodb_path) >>> os.remove(zope_conf)
一次性命令
通常需要运行一次命令(或通过cron)并使用完整的组件架构。通常使用zopectl run进行此操作。
>>> import logging >>> import gocept.runner >>> import zope.component.hooks >>> @gocept.runner.once() ... def worker(): ... log = logging.getLogger('test') ... log.info("Working.") ... site = zope.component.hooks.getSite() ... if hasattr(site, 'store'): ... log.info("Having attribute store.") ... site.store = True
定义Zope环境
>>> import os.path >>> import tempfile >>> zodb_path = tempfile.mkdtemp() >>> site_zcml = os.path.join( ... os.path.dirname(__file__), 'ftesting.zcml') >>> fd, zope_conf = tempfile.mkstemp() >>> zope_conf_file = os.fdopen(fd, 'w') >>> _ = zope_conf_file.write('''\ ... site-definition %s ... <zodb> ... <filestorage> ... path %s/Data.fs ... </filestorage> ... </zodb> ... <product-config test> ... foo bar ... </product-config> ... <eventlog> ... <logfile> ... formatter zope.exceptions.log.Formatter ... path STDOUT ... </logfile> ... </eventlog> ... ''' % (site_zcml, zodb_path)) >>> zope_conf_file.close()
所以第一次调用工作者。它将在一次调用后终止
>>> worker(None, zope_conf) ------ ... INFO test Working.
第二次调用表示在第一次运行中更改了属性
>>> worker(None, zope_conf) ------ ... INFO test Working. ------ ... INFO test Having attribute store. ...
运行者详情
主循环
主循环会一直循环,直到遇到KeyboardInterrupt或SystemExit异常,并且每秒调用工作者一次。
定义一个工作者函数,它在第三次被调用时退出
>>> work_count = 0 >>> def worker(): ... print("Working") ... global work_count ... work_count += 1 ... if work_count >= 3: ... raise SystemExit(1)
调用主循环
>>> import gocept.runner.runner >>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)() Working Working Working >>> work_count 3
在循环期间设置站点
>>> import zope.component.hooks >>> zope.component.hooks.getSite() is None True >>> def worker(): ... print(zope.component.hooks.getSite()) ... raise SystemExit(1) >>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)() <zope.site.folder.Folder object at 0x...>
循环后,不再设置站点
>>> zope.component.hooks.getSite() is None True
当工作者无错误地通过时,将提交事务
>>> work_count = 0 >>> def worker(): ... print("Working") ... global work_count ... work_count += 1 ... if work_count >= 2: ... raise SystemExit(1) ... site = zope.component.hooks.getSite() ... site.worker_done = 1 >>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)() Working Working
我们现在设置了属性worker_done
>>> getRootFolder().worker_done 1
当工作者产生错误时,将中止事务
>>> work_count = 0 >>> def worker(): ... global work_count ... work_count += 1 ... print("Working") ... site = zope.component.hooks.getSite() ... site.worker_done += 1 ... if work_count < 3: ... raise ValueError('hurz') ... raise SystemExit(1) >>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)() Working Working Working
我们仍然将属性worker_done设置为1:b
>>> getRootFolder().worker_done 1
控制休眠时间
工作者函数可以控制休眠时间。
注册日志处理程序,以便我们可以观察此操作
>>> from io import StringIO >>> import logging >>> log = StringIO() >>> log_handler = logging.StreamHandler(log) >>> logging.root.addHandler(log_handler) >>> old_log_level = logging.root.level >>> logging.root.setLevel(logging.DEBUG)
>>> work_count = 0 >>> def worker(): ... global work_count ... work_count += 1 ... new_sleep = work_count * 0.1 ... if work_count == 3: ... print("Will sleep default") ... return None ... if work_count > 3: ... raise SystemExit(1) ... print("Will sleep %s" % new_sleep) ... return new_sleep >>> gocept.runner.runner.MainLoop(getRootFolder(), 0.15, worker)() Will sleep 0.1 Will sleep 0.2 Will sleep default
实际的休眠值在日志中
>>> print(log.getvalue()) new transaction commit Sleeping 0.1 seconds new transaction commit Sleeping 0.2 seconds new transaction commit Sleeping 0.15 seconds new transaction abort...
当工作者内部发生错误时,将使用默认的休眠时间
>>> _ = log.seek(0) >>> _ = log.truncate() >>> work_count = 0 >>> def worker(): ... global work_count ... work_count += 1 ... if work_count == 1: ... new_sleep = 0.1 ... elif work_count == 2: ... print("Failing") ... raise Exception("Fail!") ... elif work_count == 3: ... print("Will sleep default") ... return None ... elif work_count > 3: ... return gocept.runner.Exit ... print("Will sleep %s" % new_sleep) ... return new_sleep >>> gocept.runner.runner.MainLoop(getRootFolder(), 0.15, worker)() Will sleep 0.1 Failing Will sleep default
实际的休眠值在日志中
>>> print(log.getvalue()) new transaction commit Sleeping 0.1 seconds new transaction Error in worker: Exception('Fail!'...) Traceback (most recent call last): ... Exception: Fail! abort Sleeping 0.15 seconds new transaction commit Sleeping 0.15 seconds new transaction commit...
恢复旧的日志处理程序
>>> logging.root.removeHandler(log_handler) >>> log.close() >>> logging.root.setLevel(old_log_level)
多次提交助手
有时您有多个操作,并希望在每一步之间提交。gocept.runner 提供了一个装饰器 transaction_per_item,这使得编写它变得简单,例如
@gocept.runner.transaction_per_item def multiple_steps(): for item in source: yield lambda: process(item)
这将按顺序调用每个 process(item),并在每个之后提交。如果步骤引发异常,则中止事务,循环继续进行下一个步骤。ZODB ConflictErrors也会被忽略。
变更
3.0 (2023-07-20)
停止支持Python 2.7。
添加对Python 3.9、3.10、3.11的支持。
2.2 (2020-09-14)
去除对 ZODB3 的依赖。
2.1 (2019-12-02)
添加对Python 3.7和3.8的支持。
迁移到Github。
2.0 (2018-03-14)
不兼容的更改:不再依赖于 zope.app.server,也不再不必要地使用其 ZConfig 架构,该架构不必要地需要一个 <accesslog> 部分,而在这个上下文中这部分没有任何意义。因此,在由本包使用的 ZConfing 配置文件中不再允许使用 <accesslog>。
1.0 (2016-01-12)
不再依赖于 zope.app.component。
0.7.1 (2015-01-28)
修复了 0.7.0 版本的错误。
0.7.0 (2015-01-28)
当工作进程产生无法用 us-ascii 表示的异常时,会正确记录错误信息。这曾经导致日志模块抛出异常。这些异常的根源很难追踪。
移除了未声明且不必要的 zope.app.security 依赖。
将项目主页迁移到 <https://bitbucket.org/gocept/gocept.runner/>。
0.6.0 (2011-12-01)
添加了 transaction_per_item 装饰器。
使用 stdlib 的 doctest 而不是 zope.testing。
0.5.3 (2010-04-14)
使用 log.error/log.warning 而不是 log.exception。冲突错误现在作为警告记录,因为它们并不是真正的问题。
0.5.2 (2010-04-14)
将记录的异常转换为 str。
0.5.1 (2009-10-13)
正确声明了依赖关系。
0.5 (2009-09-21)
不再使用 zope.app.twisted,而是使用 zope.app.server。
0.4 (2009-09-03)
现在可以通过一个函数计算由 appmain/once 设置的主。
0.3.2 (2009-05-21)
修复了 appmain 中的子站处理。
0.3.1 (2009-05-21)
声明了命名空间包。
0.3 (2009-04-15)
当工作进程失败时,将使用默认的睡眠时间(而不是最后一个时间)。
0.2 (2009-04-09)
添加了一种优雅的方式退出运行器(通过返回 gocept.runner.Exit)。
0.1 (2009-04-07)
第一个公开版本
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪一个,请了解有关 安装包 的更多信息。