asyncz 0.14.2__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {asyncz-0.14.2 → asyncz-0.15.0}/.gitignore +2 -1
- asyncz-0.15.0/PKG-INFO +218 -0
- asyncz-0.15.0/README.md +145 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/__init__.py +1 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/list.py +60 -9
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/pause.py +5 -17
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/resume.py +8 -27
- asyncz-0.15.0/asyncz/cli/commands/run.py +64 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/types.py +1 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/utils.py +1 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/conf/global_settings.py +32 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/application.py +1 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/controllers/_helpers.py +138 -164
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/controllers/home.py +17 -2
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/controllers/logs.py +9 -4
- asyncz-0.15.0/asyncz/contrib/dashboard/controllers/tasks.py +330 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/logs/handler.py +19 -6
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/mixins.py +9 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/index.html +27 -3
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/tasks/_table.html +19 -12
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/tasks/tasks.html +56 -13
- asyncz-0.15.0/asyncz/enums.py +29 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/asyncio.py +1 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/base.py +3 -5
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/pool.py +0 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/base.py +235 -36
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/defaults.py +1 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/types.py +44 -0
- asyncz-0.15.0/asyncz/tasks/__init__.py +4 -0
- asyncz-0.15.0/asyncz/tasks/inspection.py +44 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/tasks/types.py +83 -1
- {asyncz-0.14.2 → asyncz-0.15.0}/pyproject.toml +42 -70
- asyncz-0.14.2/PKG-INFO +0 -326
- asyncz-0.14.2/README.md +0 -247
- asyncz-0.14.2/asyncz/cli/commands/run.py +0 -107
- asyncz-0.14.2/asyncz/contrib/dashboard/controllers/tasks.py +0 -589
- asyncz-0.14.2/asyncz/enums.py +0 -12
- asyncz-0.14.2/asyncz/tasks/__init__.py +0 -3
- {asyncz-0.14.2 → asyncz-0.15.0}/LICENSE +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/_parsers.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/app.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/bootstrap_loader.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/add.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/remove.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/commands/start.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/cli/loader.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/conf/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/backends.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/core.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/middleware/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/middleware/auth_gate.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/middleware/forward_root_path.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/admin/protocols.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/config.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/controllers/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/engine.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/logs/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/logs/storage.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/messages.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/statics/css/asyncz.css +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/statics/css/toastify.min.css +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/statics/js/asyncz.js +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/statics/js/toastify.min.js +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/404.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/layout.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/login.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/logs/index.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/logs/partials/table.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/shared/loading.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/contrib/dashboard/templates/shared/messages.html +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/datastructures.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/events/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/events/base.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/events/constants.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/exceptions.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/debug.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/process_pool.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/executors/types.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/file_locking.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/locks.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/monkay.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/protocols.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/py.typed +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/asyncio.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/datastructures.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/schedulers/utils.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/state.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/base.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/file.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/memory.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/mongo.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/redis.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/sqlalchemy.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/stores/types.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/tasks/base.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/base.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/combination.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/cron/__init__.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/cron/constants.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/cron/expressions.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/cron/fields.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/cron/trigger.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/cron/types.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/date.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/interval.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/shutdown.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/triggers/types.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/typing.py +0 -0
- {asyncz-0.14.2 → asyncz-0.15.0}/asyncz/utils.py +0 -0
asyncz-0.15.0/PKG-INFO
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asyncz
|
|
3
|
+
Version: 0.15.0
|
|
4
|
+
Summary: The scheduler that nobody wants but every application needs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/dymmond/asyncz
|
|
6
|
+
Project-URL: Documentation, https://asyncz.dymmond.com/
|
|
7
|
+
Project-URL: Changelog, https://asyncz.dymmond.com/release-notes/
|
|
8
|
+
Project-URL: Funding, https://github.com/sponsors/tarsil
|
|
9
|
+
Project-URL: Source, https://github.com/dymmond/asyncz
|
|
10
|
+
Author-email: Tiago Silva <tiago.arasilva@gmail.com>
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: apscheduler,asgi,asyncz,cron,fastapi,framework,pydantic,scheduler,starlette
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: AnyIO
|
|
16
|
+
Classifier: Framework :: AsyncIO
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: Information Technology
|
|
19
|
+
Classifier: Intended Audience :: System Administrators
|
|
20
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
30
|
+
Classifier: Topic :: Internet
|
|
31
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
32
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
33
|
+
Classifier: Topic :: Software Development
|
|
34
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
35
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
36
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
37
|
+
Classifier: Typing :: Typed
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Requires-Dist: cryptography
|
|
40
|
+
Requires-Dist: monkay>=0.5.0
|
|
41
|
+
Requires-Dist: pydantic<3.0.0,>=2.5.3
|
|
42
|
+
Requires-Dist: sayer>=0.7.4
|
|
43
|
+
Provides-Extra: dashboard
|
|
44
|
+
Requires-Dist: lilya[standard]>=0.22.15; extra == 'dashboard'
|
|
45
|
+
Provides-Extra: docs
|
|
46
|
+
Requires-Dist: griffe-typingdoc<1.0,>=0.2.2; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocs-meta-descriptions-plugin>=2.3.0; extra == 'docs'
|
|
48
|
+
Requires-Dist: mkdocstrings-python>=2.0.3; extra == 'docs'
|
|
49
|
+
Requires-Dist: mkdocstrings>=0.28.3; extra == 'docs'
|
|
50
|
+
Requires-Dist: pyyaml<7.0.0,>=6.0; extra == 'docs'
|
|
51
|
+
Requires-Dist: sayer>=0.7.4; extra == 'docs'
|
|
52
|
+
Requires-Dist: typing-extensions>=3.10.0; extra == 'docs'
|
|
53
|
+
Requires-Dist: zensical<0.1.0,>=0.0.23; extra == 'docs'
|
|
54
|
+
Provides-Extra: localtime
|
|
55
|
+
Requires-Dist: tzlocal; extra == 'localtime'
|
|
56
|
+
Provides-Extra: testing
|
|
57
|
+
Requires-Dist: freezegun; extra == 'testing'
|
|
58
|
+
Requires-Dist: httpx; extra == 'testing'
|
|
59
|
+
Requires-Dist: ipdb; extra == 'testing'
|
|
60
|
+
Requires-Dist: lilya[standard]>=0.22.15; extra == 'testing'
|
|
61
|
+
Requires-Dist: pymongo<5.0.0,>=4.3.3; extra == 'testing'
|
|
62
|
+
Requires-Dist: pytest-asyncio<2.0.0,>=1.0; extra == 'testing'
|
|
63
|
+
Requires-Dist: pytest-cov<8.0.0,>=2.12.0; extra == 'testing'
|
|
64
|
+
Requires-Dist: pytest<10.0.0,>=7.1.3; extra == 'testing'
|
|
65
|
+
Requires-Dist: pytz>=2022.6; extra == 'testing'
|
|
66
|
+
Requires-Dist: ravyn>=0.3.7; extra == 'testing'
|
|
67
|
+
Requires-Dist: redis<8.0.0,>=4.4.0; extra == 'testing'
|
|
68
|
+
Requires-Dist: sqlalchemy; extra == 'testing'
|
|
69
|
+
Requires-Dist: starlette; extra == 'testing'
|
|
70
|
+
Requires-Dist: ty; extra == 'testing'
|
|
71
|
+
Requires-Dist: tzlocal; extra == 'testing'
|
|
72
|
+
Description-Content-Type: text/markdown
|
|
73
|
+
|
|
74
|
+
# Asyncz
|
|
75
|
+
|
|
76
|
+
<p align="center">
|
|
77
|
+
<a href="https://asyncz.tarsil.io"><img src="https://res.cloudinary.com/tarsild/image/upload/v1687363326/packages/asyncz/asyncz-new_wiyih8.png" alt='Asyncz'></a>
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
<p align="center">
|
|
81
|
+
<em>🚀 The scheduler that simply works. 🚀</em>
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
<p align="center">
|
|
85
|
+
<a href="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" target="_blank">
|
|
86
|
+
<img src="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" alt="Test Suite">
|
|
87
|
+
</a>
|
|
88
|
+
|
|
89
|
+
<a href="https://pypi.org/project/asyncz" target="_blank">
|
|
90
|
+
<img src="https://img.shields.io/pypi/v/asyncz?color=%2334D058&label=pypi%20package" alt="Package version">
|
|
91
|
+
</a>
|
|
92
|
+
|
|
93
|
+
<a href="https://pypi.org/project/asyncz" target="_blank">
|
|
94
|
+
<img src="https://img.shields.io/pypi/pyversions/asyncz.svg?color=%2334D058" alt="Supported Python versions">
|
|
95
|
+
</a>
|
|
96
|
+
</p>
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
**Documentation**: [https://asyncz.dymmond.com](https://asyncz.dymmond.com) 📚
|
|
101
|
+
|
|
102
|
+
**Source Code**: [https://github.com/dymmond/asyncz](https://github.com/dymmond/asyncz)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
Asyncz is an async-first scheduler for Python applications and ASGI services. It wraps the core APScheduler model in a codebase that is focused on `asyncio`, explicit task objects, pluggable stores and executors, and framework-friendly lifecycle integration.
|
|
107
|
+
|
|
108
|
+
Documentation: [https://asyncz.dymmond.com](https://asyncz.dymmond.com)
|
|
109
|
+
|
|
110
|
+
## Highlights
|
|
111
|
+
|
|
112
|
+
- `AsyncIOScheduler` and `NativeAsyncIOScheduler` for async runtimes.
|
|
113
|
+
- Built-in triggers for `date`, `interval`, `cron`, `and`, `or`, and `shutdown`.
|
|
114
|
+
- Built-in stores for `memory`, `file`, `mongodb`, `redis`, and `sqlalchemy`.
|
|
115
|
+
- Executors for in-event-loop work, thread pools, process pools, and direct debug execution.
|
|
116
|
+
- CLI commands for starting schedulers and managing persisted tasks.
|
|
117
|
+
- Task inspection snapshots and filtered task queries for dashboards, CLIs, and admin tooling.
|
|
118
|
+
- Optional dashboard UI for browsing tasks, filtering by schedule metadata, and viewing captured logs.
|
|
119
|
+
- Standard-library logging throughout the project.
|
|
120
|
+
|
|
121
|
+
## Installation
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pip install asyncz
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Useful extras:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pip install "asyncz[dashboard]"
|
|
131
|
+
pip install "asyncz[localtime]"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Quick start
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import logging
|
|
138
|
+
|
|
139
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
140
|
+
|
|
141
|
+
logging.basicConfig(level=logging.INFO)
|
|
142
|
+
|
|
143
|
+
scheduler = AsyncIOScheduler()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def cleanup() -> None:
|
|
147
|
+
logging.getLogger(__name__).info("cleanup finished")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
scheduler.add_task(cleanup, "interval", minutes=5, id="cleanup-task")
|
|
151
|
+
scheduler.start()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Core concepts
|
|
155
|
+
|
|
156
|
+
Asyncz is built around four main component types:
|
|
157
|
+
|
|
158
|
+
- [Schedulers](https://asyncz.dymmond.com/schedulers/)
|
|
159
|
+
- [Triggers](https://asyncz.dymmond.com/triggers/)
|
|
160
|
+
- [Stores](https://asyncz.dymmond.com/stores/)
|
|
161
|
+
- [Executors](https://asyncz.dymmond.com/executors/)
|
|
162
|
+
|
|
163
|
+
Tasks are the public unit of scheduling. A task combines a callable, a trigger, an executor alias, and the metadata needed to persist and reschedule it correctly.
|
|
164
|
+
|
|
165
|
+
## Logging
|
|
166
|
+
|
|
167
|
+
Asyncz uses Python's built-in `logging` module. The default logger namespaces are:
|
|
168
|
+
|
|
169
|
+
- `asyncz.schedulers`
|
|
170
|
+
- `asyncz.executors.<alias>`
|
|
171
|
+
- `asyncz.stores.<alias>`
|
|
172
|
+
|
|
173
|
+
If you need custom logger creation, pass your own `loggers_class` to the scheduler. That class only needs to implement the same dictionary-like contract used by `ClassicLogging`.
|
|
174
|
+
|
|
175
|
+
## ASGI integration
|
|
176
|
+
|
|
177
|
+
Asyncz can wrap an ASGI app directly:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
181
|
+
|
|
182
|
+
scheduler = AsyncIOScheduler()
|
|
183
|
+
application = scheduler.asgi(application)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Or you can wire startup and shutdown hooks manually:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
190
|
+
|
|
191
|
+
scheduler = AsyncIOScheduler()
|
|
192
|
+
|
|
193
|
+
app = Lilya(
|
|
194
|
+
routes=[...],
|
|
195
|
+
on_startup=[scheduler.start],
|
|
196
|
+
on_shutdown=[scheduler.shutdown],
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The scheduler also supports synchronous and asynchronous context managers, which makes it easy to use inside lifespan handlers.
|
|
201
|
+
|
|
202
|
+
## Persistent stores and encryption
|
|
203
|
+
|
|
204
|
+
The default store is in-memory. For durable scheduling, configure a file, MongoDB, Redis, or SQLAlchemy store.
|
|
205
|
+
|
|
206
|
+
Persistent stores support the `ASYNCZ_STORE_ENCRYPTION_KEY` environment variable. When it is set, task payloads are encrypted before they are written to the backing store.
|
|
207
|
+
|
|
208
|
+
## CLI and dashboard
|
|
209
|
+
|
|
210
|
+
Asyncz ships with:
|
|
211
|
+
|
|
212
|
+
- a CLI for `start`, `add`, `list`, `run`, `pause`, `resume`, and `remove`
|
|
213
|
+
- a Lilya-based dashboard with task controls, task filters, and a log viewer
|
|
214
|
+
|
|
215
|
+
See the documentation for usage details:
|
|
216
|
+
|
|
217
|
+
- [CLI guide](https://asyncz.dymmond.com/cli/)
|
|
218
|
+
- [Dashboard guide](https://asyncz.dymmond.com/dashboard/dashboard/)
|
asyncz-0.15.0/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Asyncz
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://asyncz.tarsil.io"><img src="https://res.cloudinary.com/tarsild/image/upload/v1687363326/packages/asyncz/asyncz-new_wiyih8.png" alt='Asyncz'></a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<em>🚀 The scheduler that simply works. 🚀</em>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" target="_blank">
|
|
13
|
+
<img src="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" alt="Test Suite">
|
|
14
|
+
</a>
|
|
15
|
+
|
|
16
|
+
<a href="https://pypi.org/project/asyncz" target="_blank">
|
|
17
|
+
<img src="https://img.shields.io/pypi/v/asyncz?color=%2334D058&label=pypi%20package" alt="Package version">
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
<a href="https://pypi.org/project/asyncz" target="_blank">
|
|
21
|
+
<img src="https://img.shields.io/pypi/pyversions/asyncz.svg?color=%2334D058" alt="Supported Python versions">
|
|
22
|
+
</a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
**Documentation**: [https://asyncz.dymmond.com](https://asyncz.dymmond.com) 📚
|
|
28
|
+
|
|
29
|
+
**Source Code**: [https://github.com/dymmond/asyncz](https://github.com/dymmond/asyncz)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
Asyncz is an async-first scheduler for Python applications and ASGI services. It wraps the core APScheduler model in a codebase that is focused on `asyncio`, explicit task objects, pluggable stores and executors, and framework-friendly lifecycle integration.
|
|
34
|
+
|
|
35
|
+
Documentation: [https://asyncz.dymmond.com](https://asyncz.dymmond.com)
|
|
36
|
+
|
|
37
|
+
## Highlights
|
|
38
|
+
|
|
39
|
+
- `AsyncIOScheduler` and `NativeAsyncIOScheduler` for async runtimes.
|
|
40
|
+
- Built-in triggers for `date`, `interval`, `cron`, `and`, `or`, and `shutdown`.
|
|
41
|
+
- Built-in stores for `memory`, `file`, `mongodb`, `redis`, and `sqlalchemy`.
|
|
42
|
+
- Executors for in-event-loop work, thread pools, process pools, and direct debug execution.
|
|
43
|
+
- CLI commands for starting schedulers and managing persisted tasks.
|
|
44
|
+
- Task inspection snapshots and filtered task queries for dashboards, CLIs, and admin tooling.
|
|
45
|
+
- Optional dashboard UI for browsing tasks, filtering by schedule metadata, and viewing captured logs.
|
|
46
|
+
- Standard-library logging throughout the project.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install asyncz
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Useful extras:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "asyncz[dashboard]"
|
|
58
|
+
pip install "asyncz[localtime]"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import logging
|
|
65
|
+
|
|
66
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
67
|
+
|
|
68
|
+
logging.basicConfig(level=logging.INFO)
|
|
69
|
+
|
|
70
|
+
scheduler = AsyncIOScheduler()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def cleanup() -> None:
|
|
74
|
+
logging.getLogger(__name__).info("cleanup finished")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
scheduler.add_task(cleanup, "interval", minutes=5, id="cleanup-task")
|
|
78
|
+
scheduler.start()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Core concepts
|
|
82
|
+
|
|
83
|
+
Asyncz is built around four main component types:
|
|
84
|
+
|
|
85
|
+
- [Schedulers](https://asyncz.dymmond.com/schedulers/)
|
|
86
|
+
- [Triggers](https://asyncz.dymmond.com/triggers/)
|
|
87
|
+
- [Stores](https://asyncz.dymmond.com/stores/)
|
|
88
|
+
- [Executors](https://asyncz.dymmond.com/executors/)
|
|
89
|
+
|
|
90
|
+
Tasks are the public unit of scheduling. A task combines a callable, a trigger, an executor alias, and the metadata needed to persist and reschedule it correctly.
|
|
91
|
+
|
|
92
|
+
## Logging
|
|
93
|
+
|
|
94
|
+
Asyncz uses Python's built-in `logging` module. The default logger namespaces are:
|
|
95
|
+
|
|
96
|
+
- `asyncz.schedulers`
|
|
97
|
+
- `asyncz.executors.<alias>`
|
|
98
|
+
- `asyncz.stores.<alias>`
|
|
99
|
+
|
|
100
|
+
If you need custom logger creation, pass your own `loggers_class` to the scheduler. That class only needs to implement the same dictionary-like contract used by `ClassicLogging`.
|
|
101
|
+
|
|
102
|
+
## ASGI integration
|
|
103
|
+
|
|
104
|
+
Asyncz can wrap an ASGI app directly:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
108
|
+
|
|
109
|
+
scheduler = AsyncIOScheduler()
|
|
110
|
+
application = scheduler.asgi(application)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Or you can wire startup and shutdown hooks manually:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
117
|
+
|
|
118
|
+
scheduler = AsyncIOScheduler()
|
|
119
|
+
|
|
120
|
+
app = Lilya(
|
|
121
|
+
routes=[...],
|
|
122
|
+
on_startup=[scheduler.start],
|
|
123
|
+
on_shutdown=[scheduler.shutdown],
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The scheduler also supports synchronous and asynchronous context managers, which makes it easy to use inside lifespan handlers.
|
|
128
|
+
|
|
129
|
+
## Persistent stores and encryption
|
|
130
|
+
|
|
131
|
+
The default store is in-memory. For durable scheduling, configure a file, MongoDB, Redis, or SQLAlchemy store.
|
|
132
|
+
|
|
133
|
+
Persistent stores support the `ASYNCZ_STORE_ENCRYPTION_KEY` environment variable. When it is set, task payloads are encrypted before they are written to the backing store.
|
|
134
|
+
|
|
135
|
+
## CLI and dashboard
|
|
136
|
+
|
|
137
|
+
Asyncz ships with:
|
|
138
|
+
|
|
139
|
+
- a CLI for `start`, `add`, `list`, `run`, `pause`, `resume`, and `remove`
|
|
140
|
+
- a Lilya-based dashboard with task controls, task filters, and a log viewer
|
|
141
|
+
|
|
142
|
+
See the documentation for usage details:
|
|
143
|
+
|
|
144
|
+
- [CLI guide](https://asyncz.dymmond.com/cli/)
|
|
145
|
+
- [Dashboard guide](https://asyncz.dymmond.com/dashboard/dashboard/)
|
|
@@ -8,12 +8,41 @@ from sayer import Option, command, info
|
|
|
8
8
|
|
|
9
9
|
from asyncz.cli.bootstrap_loader import load_bootstrap_scheduler
|
|
10
10
|
from asyncz.cli.utils import build_stores_map, ensure_loop, maybe_await
|
|
11
|
+
from asyncz.enums import TaskScheduleState
|
|
11
12
|
from asyncz.schedulers import AsyncIOScheduler
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@command(name="list")
|
|
15
16
|
def list_jobs(
|
|
16
17
|
as_json: Annotated[bool, Option(False, "--json", help="Output as JSON")],
|
|
18
|
+
state: Annotated[
|
|
19
|
+
str | None,
|
|
20
|
+
Option(None, "--state", help="Filter by task state: pending, paused, or scheduled."),
|
|
21
|
+
],
|
|
22
|
+
executor: Annotated[
|
|
23
|
+
str | None,
|
|
24
|
+
Option(None, "--executor", help="Filter by executor alias."),
|
|
25
|
+
],
|
|
26
|
+
trigger: Annotated[
|
|
27
|
+
str | None,
|
|
28
|
+
Option(None, "--trigger", help="Filter by trigger alias or trigger class name."),
|
|
29
|
+
],
|
|
30
|
+
q: Annotated[
|
|
31
|
+
str | None,
|
|
32
|
+
Option(None, "--query", help="Case-insensitive free-text task search."),
|
|
33
|
+
],
|
|
34
|
+
sort_by: Annotated[
|
|
35
|
+
str,
|
|
36
|
+
Option(
|
|
37
|
+
"next_run_time",
|
|
38
|
+
"--sort-by",
|
|
39
|
+
help="Sort by id, name, next_run_time, schedule_state, executor, store, or trigger.",
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
descending: Annotated[
|
|
43
|
+
bool,
|
|
44
|
+
Option(False, "--desc", help="Reverse the selected sort order."),
|
|
45
|
+
],
|
|
17
46
|
bootstrap: Annotated[
|
|
18
47
|
str | None,
|
|
19
48
|
Option(
|
|
@@ -35,6 +64,7 @@ def list_jobs(
|
|
|
35
64
|
asyncz list
|
|
36
65
|
asyncz list --store durable=sqlite:///scheduler.db
|
|
37
66
|
asyncz list --json --store default=memory
|
|
67
|
+
asyncz list --state paused --sort-by name
|
|
38
68
|
asyncz list --bootstrap ravyn.contrib.asyncz:AsynczSpec
|
|
39
69
|
"""
|
|
40
70
|
loop = ensure_loop()
|
|
@@ -60,29 +90,50 @@ def list_jobs(
|
|
|
60
90
|
await maybe_await(scheduler.start())
|
|
61
91
|
|
|
62
92
|
try:
|
|
63
|
-
# 2) Fetch
|
|
64
|
-
|
|
93
|
+
# 2) Fetch task snapshots
|
|
94
|
+
schedule_state = (
|
|
95
|
+
TaskScheduleState(state.strip().lower()) if state is not None else None
|
|
96
|
+
)
|
|
97
|
+
infos = await maybe_await(
|
|
98
|
+
scheduler.get_task_infos(
|
|
99
|
+
schedule_state=schedule_state,
|
|
100
|
+
executor=executor,
|
|
101
|
+
trigger=trigger,
|
|
102
|
+
q=q,
|
|
103
|
+
sort_by=sort_by,
|
|
104
|
+
descending=descending,
|
|
105
|
+
)
|
|
106
|
+
)
|
|
65
107
|
|
|
66
108
|
# 3) Format payload
|
|
67
109
|
payload: list[dict[str, Any]] = [
|
|
68
110
|
{
|
|
69
|
-
"id":
|
|
70
|
-
"name":
|
|
71
|
-
"trigger":
|
|
72
|
-
"
|
|
111
|
+
"id": item.id,
|
|
112
|
+
"name": item.name,
|
|
113
|
+
"trigger": item.trigger_name,
|
|
114
|
+
"trigger_alias": item.trigger_alias,
|
|
115
|
+
"trigger_description": item.trigger_description,
|
|
116
|
+
"next_run_time": item.next_run_time,
|
|
117
|
+
"state": item.schedule_state.value,
|
|
118
|
+
"store": item.store_alias,
|
|
119
|
+
"executor": item.executor,
|
|
120
|
+
"callable_name": item.callable_name,
|
|
121
|
+
"callable_reference": item.callable_reference,
|
|
73
122
|
}
|
|
74
|
-
for
|
|
123
|
+
for item in infos
|
|
75
124
|
]
|
|
76
125
|
|
|
77
126
|
# 4) Output
|
|
78
127
|
if as_json:
|
|
79
|
-
|
|
128
|
+
print(json.dumps(payload, default=str))
|
|
80
129
|
return payload
|
|
81
130
|
else:
|
|
82
131
|
for r in payload:
|
|
83
132
|
info(
|
|
84
133
|
f"{r['id']:<32} {r['name'] or '-':<24} "
|
|
85
|
-
f"{r['
|
|
134
|
+
f"{r['state']:<10} {r['trigger'] or '-':<16} "
|
|
135
|
+
f"{(r['store'] or '-'): <12} {(r['executor'] or '-'): <12} "
|
|
136
|
+
f"{r['next_run_time']}"
|
|
86
137
|
)
|
|
87
138
|
return None
|
|
88
139
|
finally:
|
|
@@ -9,8 +9,6 @@ from sayer import Argument, Option, command, info, success
|
|
|
9
9
|
from asyncz.cli.bootstrap_loader import load_bootstrap_scheduler
|
|
10
10
|
from asyncz.cli.utils import build_stores_map, ensure_loop, maybe_await
|
|
11
11
|
from asyncz.schedulers import AsyncIOScheduler
|
|
12
|
-
from asyncz.stores.base import BaseStore
|
|
13
|
-
from asyncz.tasks import Task as AsynczTask
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
@command
|
|
@@ -28,11 +26,8 @@ def pause(
|
|
|
28
26
|
"""
|
|
29
27
|
Pause a job by ID.
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
This implementation achieves the pause by **directly setting the task's next run time to None**
|
|
35
|
-
in the persistent store, avoiding scheduler events that might interfere with CLI execution.
|
|
29
|
+
The CLI delegates to ``scheduler.pause_task(...)`` so command-line behavior
|
|
30
|
+
stays aligned with the scheduler's own pause semantics.
|
|
36
31
|
|
|
37
32
|
Examples:
|
|
38
33
|
asyncz pause <job_id>
|
|
@@ -59,19 +54,12 @@ def pause(
|
|
|
59
54
|
scheduler = AsyncIOScheduler(**cfg)
|
|
60
55
|
await maybe_await(scheduler.start())
|
|
61
56
|
|
|
62
|
-
# 2)
|
|
63
|
-
|
|
64
|
-
store_alias: str
|
|
65
|
-
task, store_alias = scheduler.lookup_task(job_id, None) # type: ignore
|
|
66
|
-
|
|
67
|
-
# 3) Mark paused: clear next_run_time and persist in the originating store
|
|
68
|
-
task.update_task(next_run_time=None)
|
|
69
|
-
store_obj: BaseStore = scheduler.lookup_store(store_alias) # type: ignore
|
|
70
|
-
store_obj.update_task(task)
|
|
57
|
+
# 2) Delegate to the scheduler API.
|
|
58
|
+
await maybe_await(scheduler.pause_task(job_id))
|
|
71
59
|
|
|
72
60
|
success(f"Paused job {job_id}")
|
|
73
61
|
|
|
74
|
-
#
|
|
62
|
+
# 3) Shutdown only if we created a temporary scheduler
|
|
75
63
|
if not bootstrap_mode:
|
|
76
64
|
await maybe_await(scheduler.shutdown())
|
|
77
65
|
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
-
from datetime import datetime
|
|
6
5
|
from typing import Annotated, Any
|
|
7
6
|
|
|
8
7
|
from sayer import Argument, Option, command, info, success
|
|
@@ -10,8 +9,6 @@ from sayer import Argument, Option, command, info, success
|
|
|
10
9
|
from asyncz.cli.bootstrap_loader import load_bootstrap_scheduler
|
|
11
10
|
from asyncz.cli.utils import build_stores_map, ensure_loop, maybe_await
|
|
12
11
|
from asyncz.schedulers import AsyncIOScheduler
|
|
13
|
-
from asyncz.stores.base import BaseStore
|
|
14
|
-
from asyncz.tasks import Task as AsynczTask
|
|
15
12
|
|
|
16
13
|
|
|
17
14
|
@command
|
|
@@ -29,8 +26,8 @@ def resume(
|
|
|
29
26
|
"""
|
|
30
27
|
Resume a paused job by ID.
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
The CLI delegates to ``scheduler.resume_task(...)`` so paused-job recovery
|
|
30
|
+
follows the same logic as programmatic callers and the dashboard.
|
|
34
31
|
|
|
35
32
|
Examples:
|
|
36
33
|
asyncz resume <job_id>
|
|
@@ -56,30 +53,14 @@ def resume(
|
|
|
56
53
|
scheduler = AsyncIOScheduler(**cfg)
|
|
57
54
|
await maybe_await(scheduler.start())
|
|
58
55
|
|
|
59
|
-
# 2)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
store_alias: str
|
|
63
|
-
task, store_alias = scheduler.lookup_task(job_id, None) # type: ignore
|
|
64
|
-
|
|
65
|
-
# 3) Compute the next run time from "now" (resume semantics)
|
|
66
|
-
now: datetime = datetime.now(scheduler.timezone)
|
|
67
|
-
next_run: datetime | None = task.trigger.get_next_trigger_time( # type: ignore[union-attr]
|
|
68
|
-
scheduler.timezone, None, now
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
# 4) Update or delete in the appropriate store
|
|
72
|
-
if next_run:
|
|
73
|
-
task.update_task(next_run_time=next_run)
|
|
74
|
-
store_obj: BaseStore = scheduler.lookup_store(store_alias) # type: ignore
|
|
75
|
-
store_obj.update_task(task)
|
|
76
|
-
success(f"Resumed job {job_id}")
|
|
77
|
-
else:
|
|
78
|
-
# No further runs: remove the task entirely
|
|
79
|
-
scheduler.delete_task(job_id, store_alias)
|
|
56
|
+
# 2) Delegate to the scheduler API and mirror the resulting state in the message.
|
|
57
|
+
resumed_task = await maybe_await(scheduler.resume_task(job_id))
|
|
58
|
+
if resumed_task is None:
|
|
80
59
|
success(f"Removed job {job_id} (schedule finished)")
|
|
60
|
+
else:
|
|
61
|
+
success(f"Resumed job {job_id}")
|
|
81
62
|
|
|
82
|
-
#
|
|
63
|
+
# 3) Shutdown only if we created a temporary scheduler
|
|
83
64
|
if not bootstrap_mode:
|
|
84
65
|
await maybe_await(scheduler.shutdown())
|
|
85
66
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
from typing import Annotated, Any
|
|
6
|
+
|
|
7
|
+
from sayer import Argument, Option, command, info
|
|
8
|
+
|
|
9
|
+
from asyncz.cli.bootstrap_loader import load_bootstrap_scheduler
|
|
10
|
+
from asyncz.cli.utils import build_stores_map, ensure_loop, maybe_await
|
|
11
|
+
from asyncz.schedulers import AsyncIOScheduler
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@command
|
|
15
|
+
def run(
|
|
16
|
+
job_id: Annotated[str, Argument(help="Job ID to run now")],
|
|
17
|
+
bootstrap: Annotated[
|
|
18
|
+
str | None,
|
|
19
|
+
Option(
|
|
20
|
+
None,
|
|
21
|
+
help="Dotted path to a class with get_scheduler(), e.g. 'ravyn.contrib.asyncz:AsynczSpec'",
|
|
22
|
+
),
|
|
23
|
+
],
|
|
24
|
+
store: Annotated[list[str], Option([], "--store", help="Store spec alias=value. Repeatable.")],
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Trigger a job to run immediately.
|
|
28
|
+
|
|
29
|
+
This command delegates to ``scheduler.run_task(...)`` so the CLI, dashboard,
|
|
30
|
+
and programmatic APIs all share the same "run now" semantics.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
|
|
34
|
+
asyncz run <job_id>
|
|
35
|
+
asyncz run <job_id> --store durable=sqlite:///scheduler.db
|
|
36
|
+
"""
|
|
37
|
+
loop: asyncio.AbstractEventLoop = ensure_loop()
|
|
38
|
+
|
|
39
|
+
async def main() -> None:
|
|
40
|
+
"""The core asynchronous logic for running the job immediately."""
|
|
41
|
+
bootstrap_mode = bool(bootstrap)
|
|
42
|
+
if bootstrap_mode:
|
|
43
|
+
if store:
|
|
44
|
+
info("Using --bootstrap; ignoring --store flag.")
|
|
45
|
+
|
|
46
|
+
scheduler: AsyncIOScheduler = load_bootstrap_scheduler(bootstrap) # type: ignore[arg-type]
|
|
47
|
+
# Try starting the scheduler if not already running
|
|
48
|
+
with contextlib.suppress(Exception):
|
|
49
|
+
await maybe_await(scheduler.start())
|
|
50
|
+
else:
|
|
51
|
+
# 1. Build configuration and initialize a temporary scheduler
|
|
52
|
+
cfg: dict[str, dict[str, Any]] = {"stores": build_stores_map(store)} if store else {}
|
|
53
|
+
scheduler = AsyncIOScheduler(**cfg)
|
|
54
|
+
await maybe_await(scheduler.start())
|
|
55
|
+
|
|
56
|
+
# 2. Delegate the "run now" flow to the scheduler itself.
|
|
57
|
+
await maybe_await(scheduler.run_task(job_id))
|
|
58
|
+
info(f"Triggered job {job_id}")
|
|
59
|
+
|
|
60
|
+
# 3. Shutdown the temporary scheduler
|
|
61
|
+
if not bootstrap_mode:
|
|
62
|
+
await maybe_await(scheduler.shutdown())
|
|
63
|
+
|
|
64
|
+
loop.run_until_complete(main())
|
|
@@ -75,4 +75,4 @@ def parse_trigger(*, cron: str | None, interval: str | None, at: str | None) ->
|
|
|
75
75
|
when: dt.datetime = dt.datetime.fromisoformat(at) # type: ignore[arg-type]
|
|
76
76
|
from asyncz.triggers.date import DateTrigger
|
|
77
77
|
|
|
78
|
-
return DateTrigger(
|
|
78
|
+
return DateTrigger(run_at=when)
|