{ "info": { "author": "Nextiva", "author_email": "roman.zayev@nextiva.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "\n\u23f0 Krolib: Schedules for Humans\n===============================\n\n[![image](https://img.shields.io/pypi/v/krolib.svg)](https://pypi.org/project/krolib/)\n[![image](https://api.travis-ci.com/nextiva/krolib.svg)](https://travis-ci.com/nextiva/krolib/)\n[![image](https://img.shields.io/pypi/l/krolib.svg)](https://pypi.org/project/krolib/)\n[![image](https://img.shields.io/pypi/pyversions/krolib.svg)](https://pypi.org/project/krolib/)\n\nMagic library and DSL to handle complex schedules.\n\n\n\ud83d\ude80 Installation\n---------------\n\nAs easy as usual.\n\n```bash\n$ pip install krolib\n```\n\nWhy?\n----\n\n**Cron is not enough**\n\nYes, you can create almost any kind of schedule rotation or delay for you app\nbut there are some very special cases when Cron can't help you, like \"do\nsomething monthly, every second weekend at 6am until 2020\". Also, you can\ncreate only app-level job, what if you need periodical function execution\ninside your application?\n\n**Declarative schedule format**\n\nCreate your schedules programmatically, serialize them, store in DB, modify,\nupdate in runtime and always have a whole picture of what is the final result.\n\nReadable and flexible structure like:\n\n```python\n{\n 'timezone': 'Europe/Kiev',\n 'start': {\n 'relative_timeshift': {\n 'delay': 2,\n 'time_units': TimeUnits.MONTHS,\n }\n }\n}\n```\n\n**Supports asyncio out of the box**\n\nWorks like magic and very helpful to create periodical coroutines:\n\n```python\nfrom krolib.asyncio import scheduler\nfrom krolib.structs import TimeUnits, PeriodicalUnits\n\n\n@scheduler({\n 'start': {\n 'relative_timeshift': {\n 'delay': 1,\n 'time_units': TimeUnits.SECONDS,\n }\n },\n 'periodical': {\n 'repeats': PeriodicalUnits.SECONDLY,\n 'every': 1,\n },\n 'stop': {\n 'never': False,\n 'after_num_repeats': 2\n }\n})\nasync def some_coroutine():\n print('PING')\n\nawait some_coroutine() # will be delayed and called twice!\n```\n\nDelay several coroutines concurrently:\n\n```python\nfrom krolib.asyncio import scheduler\nfrom krolib.structs import TimeUnits, PeriodicalUnits\n\n\n@scheduler({\n 'start': {\n 'relative_timeshift': {\n 'delay': 1,\n 'time_units': TimeUnits.SECONDS,\n }\n },\n 'periodical': {\n 'repeats': PeriodicalUnits.SECONDLY,\n 'every': 1,\n },\n 'stop': {\n 'never': False,\n 'after_num_repeats': 2\n }\n})\nasync def some_coroutine():\n print('PING')\n\n@scheduler({\n 'start': {\n 'relative_timeshift': {\n 'delay': 1,\n 'time_units': TimeUnits.SECONDS,\n }\n },\n 'periodical': {\n 'repeats': PeriodicalUnits.SECONDLY,\n 'every': 1,\n },\n 'stop': {\n 'never': False,\n 'after_num_repeats': 2\n }\n})\nasync def another_coroutine():\n print('PONG')\n\n# concurrent execution, get your PING PONG twice!\nawait asyncio.gather(some_coroutine(), another_coroutine())\n```\n\nMore examples\n-------------\n\nDelay for 1 hour:\n\n```python\nfrom krolib.utils import just_now\nfrom krolib.parser import schedule_parser\nfrom krolib.structs import TimeUnits\n\nnow = just_now()\nschedule = {\n 'start': {\n 'relative_timeshift': {\n 'delay': 1,\n 'time_units': TimeUnits.HOURS,\n }\n },\n}\nschedule_gen = schedule_parser(schedule)\nresult = next(schedule_gen)\nassert (result - now).total_seconds() == 3600 # seconds\n```\n\nEvery last day of the month, infinite:\n\n```python\nimport datetime\nimport pytz\n\nfrom krolib.structs import (\n PeriodicalUnits,\n RelativeUnits,\n RelativeIndexUnits,\n)\nfrom krolib.parser import schedule_parser\n\n# today is this date, for example\nnow = datetime.datetime(year=2018, month=1, day=1)\n\nschedule = {\n 'periodical': {\n 'repeats': PeriodicalUnits.MONTHLY,\n 'relative_day': RelativeUnits.DAY,\n 'relative_day_index': RelativeIndexUnits.LAST,\n }\n}\nschedule_gen = schedule_parser(schedule, now_dt=now)\nresults = [next(schedule_gen) for _ in range(3)]\nassert results == [\n datetime.datetime(2018, 1, 31, 0, 0, tzinfo=pytz.UTC),\n datetime.datetime(2018, 2, 28, 0, 0, tzinfo=pytz.UTC),\n datetime.datetime(2018, 3, 31, 0, 0, tzinfo=pytz.UTC),\n]\n```\n\nSince tomorrow, every week with stop after the second one:\n\n```python\nimport datetime\n\nfrom krolib.utils import just_now\nfrom krolib.parser import schedule_parser\nfrom krolib.structs import PeriodicalUnits\n\nnow = just_now()\nstart_on = now + datetime.timedelta(days=1)\nschedule = {\n 'start': {\n 'on': start_on,\n },\n 'periodical': {\n 'repeats': PeriodicalUnits.WEEKLY,\n 'every': 1,\n },\n 'stop': {\n 'never': False,\n 'after_num_repeats': 2\n }\n}\nschedule_gen = schedule_parser(schedule)\none_dt, two_dt = list(schedule_gen)\nassert one_dt == start_on\nassert (two_dt - one_dt).days == 7\n```\n\nSchedule Format\n---------------\nThe general schema structure which is currently supported by Krolib is\npresented here as a pseudo-Python code.\n\n```python\n{\n 'start': {\n 'on': datetime.datetime,\n 'relative_timeshift': {\n 'delay': int,\n 'time_units': 'seconds/minutes/hours/days/weeks/months',\n }\n },\n 'periodical': {\n 'repeats': 'yearly/monthly/weekly/daily/hourly/minutely/secondly',\n 'every': int,\n 'month': 1..12 or None,\n 'day': 1..31 or None,\n 'weekday': [0..6] or None,\n 'hour': 0..23 or None,\n 'minute': 0..59 or None,\n 'second': 0..59 or None,\n },\n 'stop': {\n 'never': True or False,\n 'on': datetime.datetime,\n 'after_num_repeats': int\n },\n 'timezone': str or None,\n}\n```\n\nAlso, there is special form for relative periodical rotations:\n\n```python\n{\n 'start': {\n 'on': datetime.datetime,\n 'relative_timeshift': {\n 'delay': int,\n 'time_units': 'seconds/minutes/hours/days/weeks/months',\n }\n },\n 'periodical': {\n 'repeats': 'yearly/monthly',\n 'relative_day': 'day/weekday/weekend/sunday/monday/tuesday/wednesday/thursday/friday/saturday',\n 'relative_day_index': 'first/second/third/fourth/last',\n 'every': int,\n 'hour': 0..23 or None,\n 'minute': 0..59 or None,\n 'second': 0..59 or None,\n },\n 'stop': {\n 'never': True or False,\n 'on': datetime.datetime,\n 'after_num_repeats': int\n },\n 'timezone': str or None,\n}\n```\n\nSchedule structure\n------------------\n\nThe schedule data structure has three main sections:\n\n- start\n- periodical\n- stop\n\nThe schedule structure also holds a `timezone` attribute, which sets the\ntimezone for all the sections of the schedule. It should hold a string with the\ntimezone identifier, for example `Europe/Kiev`. Supported list of the timezones\ncould be found [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)\n\nPlease take into account, that timezone validation and processing utilizes\n`pytz` library, so finally you might want to dig into the sources of this\nlibrary in case if you face any troubles.\n\n### Start section\n\nStart section is used to specify the exact time or delay before the rule or\nblock starts/continues it's execution (processing).\n\n**`on` key**\n\nThis key holds the exact time (datetime object) when to start the\nexecution/processing. When this key is set, values in the `relative_timeshift`\nsection should not be filled with any values.\n\n\n```python\n{\n 'start': {\n 'on': datetime.datetime.utcnow(),\n }\n}\n```\n\n**`relative_timeshift` key**\n\nThis key holds an object with two keys \u2014 `delay` and `time_units`.\n\n`delay` holds a string with an integer in it for the amount of time units.\n\n`time_units` holds a string with one of the values:\n\n- seconds\n- minutes\n- hours\n- days\n- weeks\n- months\n\nWhen the relative timeshift is set, both delay and time_units should hold the\nproper values.\n\nExample of this kind of start section with the delay for 3 days:\n\n```python\n{\n 'start': {\n 'relative_timeshift': {\n 'delay': '3',\n 'time_units': 'days',\n }\n }\n}\n```\n\n### Periodical section\n\nThe periodical section is used to describe a schedule with the recurring\nevents. If you use this section, you should also set the values in the stop\nsection of the schedule data structure.\n\n**`repeats` key**\n\nThis key holds the type of repeats:\n\n- yearly\n- monthly\n- weekly\n- daily\n- hourly\n- minulety\n- secondly\n\nExample of yearly repeats on September, 3-rd at 20:30:\n\n```python\n{\n 'periodical': {\n 'repeats': 'yearly',\n 'every': 1,\n 'month': 9,\n 'day': 3,\n 'hour': 20,\n 'minute': 30,\n }\n}\n```\n\n**`every` key**\n\nThis key gives additional flexibility for repeats. It holds a non-negative\ninteger greater than zero (a natural number) and is used to calculate skips.\n\nFor example, for daily repeats the every key could be set to 1 \u2014 this would\nmean every day repeats, to 3 \u2014 this would mean skip two days, then execute\nevery third day, skip two days again, etc.\n\n```python\n{\n 'periodical': {\n 'repeats': 'daily',\n 'every': 5,\n 'hour': 20,\n 'minute': 30,\n }\n}\n```\n\n**`month`, `day`, `hour`, `minute` and `second` keys**\n\nThese keys hold integers which correspond to appropriate key. For example, 1-12\n(inclusive) for month, 1-31 (inclusive) for day and so on.\n\n**`weekday` key**\n\nThis key holds the information about the weekdays for the recurring event.\nValid weekday range is from 0 to 6 where 0 is Monday and 6 is Sunday.\n\nExample of weekly repeats (every week without skipping) on Mondays and Fridays\nat 15.00:\n\n```python\n{\n 'periodical': {\n 'repeats': 'weekly',\n 'every': 1,\n 'weekday': [0, 4],\n 'hour': 15,\n 'minute': 0,\n }\n}\n```\n\n### Relative days case\n\nFor `monthly` and `yearly` values of the `repeats` key, altenative relative\ndays schedule could be set.\n\nIn this case the `day` and `weekday` keys should not be set and `relative_day`\nand `relative_day_index` should be. These keys indicate some relative day of\nthe month.\n\n**`relative_day` key**\n\nThis field should hold the string with one of the following values:\n\n- day\n- weekday\n- weekend\n- sunday\n- monday\n- tuesday\n- wednesday\n- thursday\n- friday\n- saturday\n\n**`relative_day_index` key**\n\nThis field should hold a string with one of the following values:\n\n- first\n- second\n- third\n- fourth\n- last\n\nExample of monthly repeats on the third Friday of the month at 15.00:\n\n```python\n{\n 'periodical': {\n 'repeats': 'monthly',\n 'every': 1,\n 'hour': 15,\n 'minute': 0,\n 'relative_day': 'friday',\n 'relative_day_index': 'third'\n }\n}\n```\n\n### Stop section\n\nThis section is obligatory. It holds information about when to stop the\nrecurring event execution. Has `never`, `on` and `after_num_repeats` keys.\n\n**`never` key**\n\nThis key holds a boolean value. If set to `True` \u2014 other keys of this section\nshould not hold any values, because this means that recurring event will never\nstop it's execution. If set to `False` \u2014 other keys should be also set.\n\nStop section with never ending repeats:\n\n```python\n{\n 'stop': {\n 'never': True,\n }\n}\n```\n\n**`on` key**\n\nThis key holds exact time (datetime object) when to stop the recurring:\n\n```python\n{\n 'stop': {\n 'never': False,\n 'on': datetime.datetime.utcnow() + datetime.timedelta(days=1)\n }\n}\n```\n\n**`after_num_repeats` key**\n\nThis key holds an integer indicating amount of times to repeat the recurring\nschedule.\n\nStop section with stop after 25 times executing/processing smth:\n\n```python\n{\n 'stop': {\n 'never': False,\n 'after_num_repeats': 25\n }\n}\n```\n\n\ud83e\udd1d Special Thanks\n-----------------\n\n- Alexander Omyshev [@akalex](https://github.com/akalex)\n- Dmitry Nikonenko [@ndmytro](https://github.com/ndmytro)\n- Phil Steitz [@psteitz](https://github.com/psteitz)\n- Ralph Goers [@rgoers](https://github.com/rgoers)\n- Vitalii Iaskal [@vavilon17](https://github.com/vavilon17)\n\n\ud83d\udcdd License\n----------\n\nPublished under Apache Software License 2.0, see [LICENSE](LICENSE) file.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/nextiva/krolib", "keywords": "", "license": "Apache 2.0", "maintainer": "", "maintainer_email": "", "name": "krolib", "package_url": "https://pypi.org/project/krolib/", "platform": "", "project_url": "https://pypi.org/project/krolib/", "project_urls": { "Homepage": "https://github.com/nextiva/krolib" }, "release_url": "https://pypi.org/project/krolib/1.2.1/", "requires_dist": [ "python-dateutil (==2.8.0)", "pytz (~=2019.1)", "toolz (>=0.8.2)", "tzlocal (>=1.3)", "voluptuous (==0.11.5)" ], "requires_python": ">=3.6.0", "summary": "DSL to handle complex schedules", "version": "1.2.1" }, "last_serial": 5900187, "releases": { "1.2.0": [ { "comment_text": "", "digests": { "md5": "8374514233b26d22b34329710196cd30", "sha256": "85f4f8e1b07f45bcd51c7f3da4c002bb2475017227ad0cb776ba0e8d33c80cbf" }, "downloads": -1, "filename": "krolib-1.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "8374514233b26d22b34329710196cd30", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": ">=3.6.0", "size": 14844, "upload_time": "2019-07-12T16:15:05", "url": "https://files.pythonhosted.org/packages/cc/37/7f57babb66fb89adb9142e350e52081d1ad298fd87a26d97be9d5a93a0be/krolib-1.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a7d918ff8815e6e1bc58804be3b8d7cc", "sha256": "db738d4cd5cc683759d60f0ff84befab07f4d3f3dd90b2143fcd6e8e2a815b54" }, "downloads": -1, "filename": "krolib-1.2.0.tar.gz", "has_sig": false, "md5_digest": "a7d918ff8815e6e1bc58804be3b8d7cc", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 13729, "upload_time": "2019-07-12T16:15:08", "url": "https://files.pythonhosted.org/packages/09/38/9fb48b298d43adda67a2994ff80e09d7ffc84dcf3cdac056d4edf8ef1b83/krolib-1.2.0.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "a9bc7738a2d9460efe888c06f737d1dd", "sha256": "2586e2ec1a8f44c940bc83d51ba2b6c0b8691336c5ced23eda51ce225093e8a6" }, "downloads": -1, "filename": "krolib-1.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "a9bc7738a2d9460efe888c06f737d1dd", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": ">=3.6.0", "size": 18307, "upload_time": "2019-09-28T17:43:36", "url": "https://files.pythonhosted.org/packages/c7/70/462d8c67fb797ff052cd3f53f736a261010955ca0d30be07a18d156dd852/krolib-1.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bf132f2f6626e24a3ea79d1d76e03f02", "sha256": "c374d7744bebc5a9d3e2f529497689843517d808d18e6cdcebc9f88a24a86e55" }, "downloads": -1, "filename": "krolib-1.2.1.tar.gz", "has_sig": false, "md5_digest": "bf132f2f6626e24a3ea79d1d76e03f02", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 21574, "upload_time": "2019-09-28T17:43:38", "url": "https://files.pythonhosted.org/packages/af/64/f6e3d8099242aaf88c153686f9ea413aa829af42d373e5b43d99dba84f96/krolib-1.2.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a9bc7738a2d9460efe888c06f737d1dd", "sha256": "2586e2ec1a8f44c940bc83d51ba2b6c0b8691336c5ced23eda51ce225093e8a6" }, "downloads": -1, "filename": "krolib-1.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "a9bc7738a2d9460efe888c06f737d1dd", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": ">=3.6.0", "size": 18307, "upload_time": "2019-09-28T17:43:36", "url": "https://files.pythonhosted.org/packages/c7/70/462d8c67fb797ff052cd3f53f736a261010955ca0d30be07a18d156dd852/krolib-1.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bf132f2f6626e24a3ea79d1d76e03f02", "sha256": "c374d7744bebc5a9d3e2f529497689843517d808d18e6cdcebc9f88a24a86e55" }, "downloads": -1, "filename": "krolib-1.2.1.tar.gz", "has_sig": false, "md5_digest": "bf132f2f6626e24a3ea79d1d76e03f02", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 21574, "upload_time": "2019-09-28T17:43:38", "url": "https://files.pythonhosted.org/packages/af/64/f6e3d8099242aaf88c153686f9ea413aa829af42d373e5b43d99dba84f96/krolib-1.2.1.tar.gz" } ] }