├── .codeclimate.yml
├── .gitignore
├── .noserc
├── .project
├── .pydevproject
├── .travis.yml
├── LICENSE
├── README.rst
├── appveyor.yml
├── examples
├── Dockerfile
├── decorator_style_task_app.py
├── every_minute_task_app.py
└── supervisord.conf
├── requirements.txt
├── setup.py
├── tests
└── crontab_test.py
└── tornado_crontab
├── __init__.py
└── _crontab.py
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | # https://codeclimate.com/github/gaujin/tornado-crontab
2 | engines:
3 | radon:
4 | enabled: true
5 | config:
6 | threshold: 'B'
7 | pep8:
8 | enabled: true
9 | ratings:
10 | paths:
11 | - "tornado_crontab/**.py"
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | .coverage
4 | .noseids
5 | /.vscode/
6 | /build/
7 | /cover/
8 | /dist/
9 | /tornado_crontab.egg-info/
10 |
--------------------------------------------------------------------------------
/.noserc:
--------------------------------------------------------------------------------
1 | [nosetests]
2 | with-doctest=1
3 | with-coverage=1
4 | cover-package=tornado_crontab
5 | cover-html=1
6 | verbosity=2
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | tornado-crontab
4 |
5 |
6 |
7 |
8 |
9 | org.python.pydev.PyDevBuilder
10 |
11 |
12 |
13 |
14 |
15 | org.python.pydev.pythonNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | /${PROJECT_DIR_NAME}
5 |
6 | python 2.7
7 | Default
8 |
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # https://travis-ci.org/gaujin/tornado-crontab
2 | language: python
3 | python:
4 | - "2.7"
5 | - "pypy2.7-5.8.0"
6 | - "3.4"
7 | - "3.5"
8 | - "3.6"
9 | - "3.7-dev"
10 | - "nightly"
11 | - "pypy3.5-5.8.0"
12 | install: "pip install -r requirements.txt"
13 | script: nosetests -v
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Takehito Yamada
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | tornado-crontab
3 | ===============
4 |
5 | tornado-crontab is a library that can make the task apps like crontab.
6 |
7 | |travis| |appveyor| |codeclimate| |requires|
8 |
9 | Installation
10 | ============
11 |
12 | Automatic installation::
13 |
14 | $ pip install tornado-crontab
15 |
16 | torando-crontab is listed in `PyPI `_ and can be installed with pip or easy_install.
17 |
18 | Manual installation::
19 |
20 | $ git clone https://github.com/gaujin/tornado-crontab.git
21 | $ cd tornado-crontab
22 | $ python setup.py install
23 |
24 | tornado-crontab source code is `hosted on GitHub `_
25 |
26 | Usage
27 | =====
28 |
29 | Here is an example every minute task app::
30 |
31 | from __future__ import print_function
32 | import functools
33 | import tornado.ioloop
34 | import tornado_crontab
35 |
36 | def hello_crontab(value):
37 |
38 | print("Hello, {0}".format(value))
39 |
40 | if __name__ == "__main__":
41 |
42 | _func = functools.partial(hello_crontab, *["crontab"])
43 | tornado_crontab.CronTabCallback(_func, "* * * * *").start()
44 | tornado.ioloop.IOLoop.current().start()
45 |
46 | decorator style task app::
47 |
48 | from __future__ import print_function
49 | import tornado.ioloop
50 | from tornado_crontab import crontab
51 |
52 | @crontab("* * * * *")
53 | def hello_crontab(value):
54 |
55 | print("Hello, {0}".format(value))
56 |
57 | if __name__ == "__main__":
58 |
59 | hello_crontab("crontab")
60 | tornado.ioloop.IOLoop.current().start()
61 |
62 | Prerequisites
63 | =============
64 |
65 | tornado-crontab 0.4.x or earlier runs on Tornado 4.x or earlier.
66 |
67 | Future policy of io_loop argument
68 | =================================
69 |
70 | | ``io_loop`` argument to function and constructor is deprecated for 0.4.0 and removed for 0.5.0.
71 | | About this policy is based on the policy already indicated in Tornado, tornado-crontab also made the same policy.
72 |
73 | Using
74 | =====
75 |
76 | * `Tornado `_
77 | * `crontab `_
78 |
79 | License
80 | =======
81 |
82 | * tornado-crontab license under the `MIT license `_.
83 | * `Tornado is licensed under the Apache license `_.
84 | * `crontab is licensed under the LGPL license version 2.1 `_.
85 |
86 | See the LICENSE file for specific terms.
87 |
88 | .. |travis| image:: https://travis-ci.org/gaujin/tornado-crontab.svg?branch=master
89 | :target: https://travis-ci.org/gaujin/tornado-crontab
90 | :alt: Travis CI
91 |
92 | .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/2wsrfhy8sx100hwq?svg=true
93 | :target: https://ci.appveyor.com/project/gaujin/tornado-crontab
94 | :alt: AppVeyor
95 |
96 | .. |codeclimate| image:: https://codeclimate.com/github/gaujin/tornado-crontab/badges/gpa.svg
97 | :target: https://codeclimate.com/github/gaujin/tornado-crontab
98 | :alt: Code Climate
99 |
100 | .. |requires| image:: https://requires.io/github/gaujin/tornado-crontab/requirements.svg?branch=master
101 | :target: https://requires.io/github/gaujin/tornado-crontab/requirements/?branch=master
102 | :alt: Requirements Status
103 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # https://ci.appveyor.com/project/gaujin/tornado-crontab
2 | environment:
3 | matrix:
4 | - PYTHON: "C:/Python27"
5 | - PYTHON: "C:/Python27-x64"
6 | - PYTHON: "C:/Python34"
7 | - PYTHON: "C:/Python34-x64"
8 | - PYTHON: "C:/Python35"
9 | - PYTHON: "C:/Python35-x64"
10 | - PYTHON: "C:/Python36"
11 | - PYTHON: "C:/Python36-x64"
12 | - PYTHON: "C:/Python37"
13 | - PYTHON: "C:/Python37-x64"
14 |
15 | init:
16 | - "ECHO %PYTHON%"
17 |
18 | version: '#{build}'
19 |
20 | install:
21 | - "%PYTHON%/Scripts/pip.exe install -r requirements.txt"
22 |
23 | build: false
24 |
25 | test_script:
26 | - "%PYTHON%/Scripts/nosetests.exe -vv"
27 |
--------------------------------------------------------------------------------
/examples/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:2-alpine
2 | LABEL maintainer "tornado-crontab@gaujin.jp"
3 |
4 | RUN set -x && \
5 | adduser -S spam-ham && \
6 | easy_install -U tornado-crontab supervisor
7 |
8 | COPY ["decorator_style_task_app.py", "supervisord.conf", "/home/spam-ham/"]
9 |
10 | CMD ["/usr/local/bin/supervisord", "-c", "/home/spam-ham/supervisord.conf"]
11 |
--------------------------------------------------------------------------------
/examples/decorator_style_task_app.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import tornado.ioloop
3 | from tornado_crontab import crontab
4 |
5 |
6 | @crontab("* * * * *")
7 | def hello_crontab(value):
8 |
9 | print("Hello, {0}".format(value))
10 |
11 |
12 | if __name__ == "__main__":
13 |
14 | hello_crontab("crontab")
15 | tornado.ioloop.IOLoop.current().start()
16 |
--------------------------------------------------------------------------------
/examples/every_minute_task_app.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import functools
3 | import tornado.ioloop
4 | import tornado_crontab
5 |
6 |
7 | def hello_crontab(value):
8 |
9 | print("Hello, {0}".format(value))
10 |
11 |
12 | if __name__ == "__main__":
13 |
14 | _func = functools.partial(hello_crontab, *["crontab"])
15 | tornado_crontab.CronTabCallback(_func, "* * * * *").start()
16 | tornado.ioloop.IOLoop.current().start()
17 |
--------------------------------------------------------------------------------
/examples/supervisord.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | nodaemon=true
3 |
4 | [program:example]
5 | command=python /home/spam-ham/decorator_style_task_app.py
6 | user=spam-ham
7 | stdout_logfile=/dev/fd/1
8 | stdout_logfile_maxbytes=0
9 | redirect_stderr=true
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | coverage==4.5.1
2 | crontab==0.22.2
3 | freezegun==0.3.10
4 | mock==2.0
5 | nose==1.3.7
6 | tornado==4.5.3
7 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from encodings import codecs
2 | import io
3 | import os
4 | import re
5 |
6 | from setuptools import setup
7 |
8 |
9 | here = os.path.dirname(__file__)
10 |
11 |
12 | def read(*names, **kwargs):
13 | with io.open(os.path.join(here, *names),
14 | encoding=kwargs.get("encoding", "utf8")) as f:
15 | return f.read()
16 |
17 |
18 | def find_version(*file_paths):
19 | version_file = read(*file_paths)
20 | version_match = re.search(r"^version = ['\"]([^'\"]*)['\"]",
21 | version_file, re.M)
22 | if version_match:
23 | return version_match.group(1)
24 | raise RuntimeError("Unable to find version string.")
25 |
26 |
27 | with codecs.open(os.path.join(here, "README.rst"), encoding="utf8") as f:
28 | long_description = f.read()
29 |
30 |
31 | setup(
32 | name="tornado-crontab",
33 | version=find_version("tornado_crontab", "__init__.py"),
34 | packages=["tornado_crontab"],
35 | install_requires=["tornado<5", "crontab"],
36 | author="Takehito Yamada",
37 | author_email="tornado-crontab@gaujin.jp",
38 | url="https://github.com/gaujin/tornado-crontab",
39 | license="MIT",
40 | description="CronTab callback for Tornado",
41 | long_description=long_description,
42 | classifiers=[
43 | "Development Status :: 4 - Beta",
44 | "License :: OSI Approved :: MIT License",
45 | "Programming Language :: Python :: 2",
46 | "Programming Language :: Python :: 2.7",
47 | "Programming Language :: Python :: 3",
48 | "Programming Language :: Python :: 3.4",
49 | "Programming Language :: Python :: 3.5",
50 | "Programming Language :: Python :: 3.6",
51 | "Programming Language :: Python :: Implementation :: CPython",
52 | "Programming Language :: Python :: Implementation :: PyPy",
53 | ],
54 | )
55 |
--------------------------------------------------------------------------------
/tests/crontab_test.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | try:
4 | from StringIO import StringIO
5 | except ImportError:
6 | from io import StringIO
7 |
8 | from datetime import datetime
9 | import functools
10 | import logging
11 | import os
12 | import time
13 | import unittest
14 |
15 | from freezegun import freeze_time
16 |
17 |
18 | class FakeTimeIOLoop(object):
19 |
20 | def __init__(self, freezer):
21 | self._offset = 0
22 | self._freezer = freezer
23 |
24 | def time(self):
25 | return time.time() + self._offset
26 |
27 | def add_timeout(self, timeout, callback):
28 | self._callback = (timeout, callback)
29 |
30 | def call_later(self, timeout, callback):
31 | self._max = timeout
32 |
33 | def close(self):
34 | pass
35 |
36 | def start(self):
37 | for _ in range(self._max):
38 | _timeout, _callback = self._callback
39 | self._freezer.stop()
40 | self._freezer.time_to_freeze = datetime.utcfromtimestamp(_timeout)
41 | self._freezer.start()
42 | _callback()
43 | self._offset = _timeout - time.time()
44 |
45 | def stop(self):
46 | pass
47 |
48 |
49 | class TestCronTabCallback(unittest.TestCase):
50 |
51 | BEGIN_TIME = "2015-11-08T00:00:00Z"
52 |
53 | def __init__(self, *args, **kwargs):
54 | super(TestCronTabCallback, self).__init__(*args, **kwargs)
55 |
56 | def setUp(self):
57 | unittest.TestCase.setUp(self)
58 |
59 | self._freezer = freeze_time(self.BEGIN_TIME)
60 | self._freezer.start()
61 | self.io_loop = FakeTimeIOLoop(self._freezer)
62 | self.calls = []
63 |
64 | def tearDown(self):
65 | unittest.TestCase.tearDown(self)
66 | self._freezer.stop()
67 |
68 | def crontab_task(self):
69 | self.calls.append(datetime.utcfromtimestamp(
70 | self.io_loop.time()).strftime("%Y-%m-%dT%H:%M:%S"))
71 |
72 | def _target(self, schedule):
73 |
74 | from tornado_crontab import CronTabCallback
75 |
76 | pc = CronTabCallback(self.crontab_task, schedule, self.io_loop)
77 | pc.start()
78 |
79 | def _test(self, schedule, asserts):
80 |
81 | self._target(schedule)
82 | self.io_loop.call_later(10, self.io_loop.stop)
83 | self.io_loop.start()
84 |
85 | self.assertEqual(self.calls, asserts)
86 |
87 | def test_every_minute(self):
88 |
89 | self._test("* * * * *", ["2015-11-08T00:01:00", "2015-11-08T00:02:00",
90 | "2015-11-08T00:03:00", "2015-11-08T00:04:00",
91 | "2015-11-08T00:05:00", "2015-11-08T00:06:00",
92 | "2015-11-08T00:07:00", "2015-11-08T00:08:00",
93 | "2015-11-08T00:09:00", "2015-11-08T00:10:00"])
94 |
95 | def test_every_hour(self):
96 |
97 | self._test("0 * * * *", ["2015-11-08T01:00:00", "2015-11-08T02:00:00",
98 | "2015-11-08T03:00:00", "2015-11-08T04:00:00",
99 | "2015-11-08T05:00:00", "2015-11-08T06:00:00",
100 | "2015-11-08T07:00:00", "2015-11-08T08:00:00",
101 | "2015-11-08T09:00:00", "2015-11-08T10:00:00"])
102 |
103 | def test_every_day(self):
104 |
105 | self._test("0 0 * * *", ["2015-11-09T00:00:00", "2015-11-10T00:00:00",
106 | "2015-11-11T00:00:00", "2015-11-12T00:00:00",
107 | "2015-11-13T00:00:00", "2015-11-14T00:00:00",
108 | "2015-11-15T00:00:00", "2015-11-16T00:00:00",
109 | "2015-11-17T00:00:00", "2015-11-18T00:00:00"])
110 |
111 | def test_every_monday(self):
112 |
113 | self._test("0 0 * * 1", ["2015-11-09T00:00:00", "2015-11-16T00:00:00",
114 | "2015-11-23T00:00:00", "2015-11-30T00:00:00",
115 | "2015-12-07T00:00:00", "2015-12-14T00:00:00",
116 | "2015-12-21T00:00:00", "2015-12-28T00:00:00",
117 | "2016-01-04T00:00:00", "2016-01-11T00:00:00"])
118 |
119 | def test_every_month(self):
120 |
121 | self._test("0 0 1 * *", ["2015-12-01T00:00:00", "2016-01-01T00:00:00",
122 | "2016-02-01T00:00:00", "2016-03-01T00:00:00",
123 | "2016-04-01T00:00:00", "2016-05-01T00:00:00",
124 | "2016-06-01T00:00:00", "2016-07-01T00:00:00",
125 | "2016-08-01T00:00:00", "2016-09-01T00:00:00"])
126 |
127 | def test_every_year(self):
128 |
129 | self._test("0 0 1 1 *", ["2016-01-01T00:00:00", "2017-01-01T00:00:00",
130 | "2018-01-01T00:00:00", "2019-01-01T00:00:00",
131 | "2020-01-01T00:00:00", "2021-01-01T00:00:00",
132 | "2022-01-01T00:00:00", "2023-01-01T00:00:00",
133 | "2024-01-01T00:00:00", "2025-01-01T00:00:00"])
134 |
135 |
136 | class TestCrontabDecorator(TestCronTabCallback):
137 |
138 | def __init__(self, *args, **kwargs):
139 | super(TestCrontabDecorator, self).__init__(*args, **kwargs)
140 |
141 | def _target(self, schedule):
142 |
143 | from tornado_crontab import crontab
144 |
145 | @crontab(schedule, self.io_loop)
146 | def decorate_task():
147 | self.crontab_task()
148 |
149 | decorate_task()
150 |
151 |
152 | class TestCrontabLogging(unittest.TestCase):
153 |
154 | def test__get_func_spec_no_args(self):
155 |
156 | from tornado_crontab import CronTabCallback
157 |
158 | def _func():
159 | pass
160 |
161 | _crontab = CronTabCallback(_func, "* * * * *")
162 | self.assertEqual((_func, [], {}), _crontab._get_func_spec())
163 |
164 | def test__get_func_spec_args(self):
165 |
166 | from tornado_crontab import CronTabCallback
167 |
168 | def _func(arg1, arg2):
169 | pass
170 |
171 | _args = ["value1", "value2"]
172 | _crontab = CronTabCallback(
173 | functools.partial(_func, *_args), "* * * * *")
174 | self.assertEqual((_func, _args, {}), _crontab._get_func_spec())
175 |
176 | _crontab = CronTabCallback(
177 | functools.partial(
178 | functools.partial(_func, _args[0]), _args[1]), "* * * * *")
179 | self.assertEqual((_func, _args, {}), _crontab._get_func_spec())
180 |
181 | def test__get_func_spec_kwargs(self):
182 |
183 | from tornado_crontab import CronTabCallback
184 |
185 | def _func(arg1, arg2):
186 | pass
187 |
188 | _kwargs = {"arg1": "value1", "arg2": "value2"}
189 | _crontab = CronTabCallback(
190 | functools.partial(_func, **_kwargs), "* * * * *")
191 | self.assertEqual((_func, [], _kwargs), _crontab._get_func_spec())
192 |
193 | _crontab = CronTabCallback(
194 | functools.partial(
195 | functools.partial(_func, arg1="value1"), arg2="value2"),
196 | "* * * * *")
197 | self.assertEqual((_func, [], _kwargs), _crontab._get_func_spec())
198 |
199 | def test__logging(self):
200 |
201 | from tornado_crontab import CronTabCallback
202 | from tornado_crontab._crontab import log_crontab
203 |
204 | def _func(arg1, arg2):
205 | pass
206 |
207 | _crontab = CronTabCallback(
208 | functools.partial(_func, "value1", arg2="value2"), "* * * * *")
209 | _crontab._running = True
210 |
211 | _stream = StringIO()
212 | log_crontab.addHandler(logging.StreamHandler(_stream))
213 | _crontab._logging(logging.DEBUG)
214 | _log = _stream.getvalue()
215 |
216 | # for windows
217 | if os.name == "nt":
218 | user = os.environ.get("USERNAME")
219 |
220 | # for other os
221 | else:
222 | import pwd
223 | user = pwd.getpwuid(os.geteuid()).pw_name
224 |
225 | self.assertEqual(
226 | " ".join(["tornado-crontab[%d]:" % os.getpid(), "(%s)" % user,
227 | "FUNC (_func ['value1'] {'arg2': 'value2'})\n"]), _log)
228 |
229 |
230 | if __name__ == "__main__":
231 | unittest.main()
232 |
--------------------------------------------------------------------------------
/tornado_crontab/__init__.py:
--------------------------------------------------------------------------------
1 | from ._crontab import CronTabCallback, crontab
2 |
3 | __all__ = ["CronTabCallback", "crontab", "version", "version_info"]
4 |
5 | version = "0.4.0.dev1"
6 | version_info = tuple([int(v) for v in version.split(".")[:3]])
7 |
--------------------------------------------------------------------------------
/tornado_crontab/_crontab.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import inspect
3 | import logging
4 | import math
5 | import os
6 | import warnings
7 |
8 | from crontab import CronTab
9 | from tornado import version_info as tornado_version_info
10 | from tornado.ioloop import PeriodicCallback
11 |
12 |
13 | log_crontab = logging.getLogger("tornado-crontab.crontab")
14 | FORMAT_LOG_CRONTAB = " ".join(["tornado-crontab[%(pid)d]:",
15 | "(%(user)s)",
16 | "FUNC",
17 | "(%(funcname)s",
18 | "%(args)s %(kwargs)s)"])
19 |
20 | IS_TZ_SUPPORTED = "default_utc" in inspect.getargspec(CronTab.next).args
21 | UNSUPPORTED_TZ_MESSAGE = """\
22 | Since crontab package version is low, UTC can not be used.
23 | When using it with UTC, upgrade the crontab package to 0.22.0+."""
24 | UNSUPPORTED_IOLOOP_MESSAGE = """\
25 | Since tornado package version is high, io_loop can not be used.
26 | When using it with io_loop, downgrade the tornado package to 4.5.3 or earlier."""
27 |
28 |
29 | class CronTabCallback(PeriodicCallback):
30 | """ Crontab Callback Class
31 |
32 | Schedule execution of the function.
33 | Timezone is local time by default.
34 | If you want to schedule with UTC, set `is_utc` to `True`.
35 |
36 | .. versionchanged:: 0.3.3
37 | Supported UTC.
38 | If Timezone is not supported and `is_utc` is set to `True`,
39 | a warning is output and `is_utc` is ignored.
40 |
41 | .. versionchanged:: 0.4
42 | Supported Tornado 5, warning is output and `io_loop` is ignored.
43 | The `io_loop` argument is deprecated.
44 | """
45 |
46 | def __init__(self, callback, schedule, io_loop=None, is_utc=False):
47 | """ CrontabCallback initializer
48 |
49 | :type callback: func
50 | :param callback: target schedule function
51 | :type schedule: str
52 | :param schedule: crotab expression
53 | :type io_loop: tornado.ioloop.IOLoop
54 | :param io_loop: tornado IOLoop
55 | :type is_utc: bool
56 | :param is_utc: schedule timezone is UTC. (True:UTC, False:Local Timezone)
57 | """
58 |
59 | # If Timezone is not supported and `is_utc` is set to `True`,
60 | # a warning is output and `is_utc` is ignored.
61 | if not IS_TZ_SUPPORTED and is_utc:
62 | warnings.warn(UNSUPPORTED_TZ_MESSAGE)
63 | is_utc = False
64 |
65 | self.__crontab = CronTab(schedule)
66 | self.__is_utc = is_utc
67 |
68 | arguments = dict(
69 | callback=callback, callback_time=self._calc_callbacktime())
70 |
71 | if tornado_version_info >= (5,):
72 | if io_loop is not None:
73 | warnings.warn(UNSUPPORTED_IOLOOP_MESSAGE)
74 | else:
75 | arguments.update(io_loop=io_loop)
76 |
77 | super(CronTabCallback, self).__init__(**arguments)
78 |
79 | self.pid = os.getpid()
80 |
81 | if os.name == "nt":
82 | self.user = os.environ.get("USERNAME")
83 | else:
84 | import pwd
85 | self.user = pwd.getpwuid(os.geteuid()).pw_name
86 |
87 | def _calc_callbacktime(self, now=None):
88 |
89 | _kwargs = dict(now=now)
90 |
91 | if IS_TZ_SUPPORTED:
92 | _kwargs.update(dict(default_utc=self.__is_utc))
93 |
94 | return math.ceil(
95 | self.__crontab.next(**_kwargs)) * 1000.0
96 |
97 | def _get_func_spec(self):
98 |
99 | _args = []
100 | _kwargs = {}
101 |
102 | def _get_func(_func):
103 |
104 | if not isinstance(_func, functools.partial):
105 | return _func
106 |
107 | for _arg in reversed(_func.args):
108 | _args.insert(0, _arg)
109 |
110 | if _func.keywords:
111 | _kwargs.update(_func.keywords)
112 |
113 | return _get_func(_func.func)
114 |
115 | _func = _get_func(self.callback)
116 | return _func, _args, _kwargs
117 |
118 | def _logging(self, level):
119 |
120 | if self._running and log_crontab.isEnabledFor(level):
121 |
122 | _func, _args, _kwargs = self._get_func_spec()
123 |
124 | log_crontab.log(level,
125 | FORMAT_LOG_CRONTAB % dict(pid=self.pid,
126 | user=self.user,
127 | funcname=_func.__name__,
128 | args=_args,
129 | kwargs=_kwargs))
130 |
131 | def _run(self):
132 |
133 | self._logging(logging.INFO)
134 |
135 | try:
136 | PeriodicCallback._run(self)
137 |
138 | finally:
139 |
140 | self._logging(logging.DEBUG)
141 |
142 | def _schedule_next(self):
143 | self.callback_time = self._calc_callbacktime()
144 | super(CronTabCallback, self)._schedule_next()
145 |
146 |
147 | def crontab(schedule, io_loop=None, is_utc=False):
148 | """ Crontab Decorator
149 |
150 | Decorate this function to the function you want to execute as scheduled.
151 | Timezone is local time by default.
152 | If you want to schedule with UTC, set `is_utc` to `True`.
153 |
154 | .. versionchanged:: 0.3.3
155 | Supported UTC.
156 | If Timezone is not supported and `is_utc` is set to `True`,
157 | a warning is output and `is_utc` is ignored.
158 |
159 | .. versionchanged:: 0.4
160 | Supported Tornado 5, warning is output and `io_loop` is ignored.
161 | The `io_loop` argument is deprecated.
162 |
163 | :type schedule: str
164 | :param schedule: crotab expression
165 | :type io_loop: tornado.ioloop.IOLoop
166 | :param io_loop: tornado IOLoop
167 | :type is_utc: bool
168 | :param is_utc: schedule timezone is UTC. (True:UTC, False:Local Timezone)
169 | :rtype: func
170 | :return: scheduled execute function
171 | """
172 |
173 | def receive_func(func):
174 |
175 | @functools.wraps(func)
176 | def wrapper(*args, **kwargs):
177 |
178 | _func = functools.partial(func, *args, **kwargs)
179 | CronTabCallback(_func, schedule=schedule,
180 | io_loop=io_loop, is_utc=is_utc).start()
181 |
182 | return wrapper
183 | return receive_func
184 |
--------------------------------------------------------------------------------