asyncz 0.8.2__tar.gz → 0.9.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.
Files changed (65) hide show
  1. {asyncz-0.8.2 → asyncz-0.9.0}/.gitignore +1 -0
  2. {asyncz-0.8.2 → asyncz-0.9.0}/PKG-INFO +93 -6
  3. {asyncz-0.8.2 → asyncz-0.9.0}/README.md +85 -3
  4. asyncz-0.9.0/asyncz/__init__.py +1 -0
  5. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/datastructures.py +2 -2
  6. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/enums.py +3 -3
  7. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/events/base.py +2 -2
  8. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/exceptions.py +6 -6
  9. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/executors/asyncio.py +10 -8
  10. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/executors/base.py +28 -36
  11. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/executors/debug.py +6 -8
  12. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/executors/pool.py +4 -4
  13. asyncz-0.9.0/asyncz/executors/types.py +62 -0
  14. asyncz-0.9.0/asyncz/schedulers/asgi.py +69 -0
  15. asyncz-0.9.0/asyncz/schedulers/asyncio.py +69 -0
  16. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/schedulers/base.py +169 -112
  17. asyncz-0.9.0/asyncz/schedulers/datastructures.py +12 -0
  18. asyncz-0.9.0/asyncz/schedulers/defaults.py +25 -0
  19. asyncz-0.9.0/asyncz/schedulers/types.py +400 -0
  20. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/schedulers/utils.py +1 -1
  21. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/state.py +1 -1
  22. asyncz-0.9.0/asyncz/stores/base.py +72 -0
  23. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/stores/memory.py +14 -13
  24. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/stores/mongo.py +29 -20
  25. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/stores/redis.py +32 -31
  26. asyncz-0.9.0/asyncz/stores/sqlalchemy.py +173 -0
  27. asyncz-0.8.2/asyncz/stores/base.py → asyncz-0.9.0/asyncz/stores/types.py +20 -37
  28. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/tasks/base.py +46 -45
  29. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/base.py +19 -28
  30. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/combination.py +22 -18
  31. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/cron/expressions.py +36 -37
  32. asyncz-0.9.0/asyncz/triggers/cron/fields.py +121 -0
  33. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/cron/trigger.py +12 -9
  34. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/date.py +5 -8
  35. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/interval.py +18 -15
  36. asyncz-0.9.0/asyncz/triggers/types.py +36 -0
  37. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/typing.py +1 -0
  38. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/utils.py +98 -38
  39. {asyncz-0.8.2 → asyncz-0.9.0}/pyproject.toml +36 -15
  40. asyncz-0.8.2/asyncz/__init__.py +0 -1
  41. asyncz-0.8.2/asyncz/_mapping.py +0 -33
  42. asyncz-0.8.2/asyncz/contrib/esmerald/__init__.py +0 -0
  43. asyncz-0.8.2/asyncz/contrib/esmerald/decorator.py +0 -40
  44. asyncz-0.8.2/asyncz/contrib/esmerald/scheduler.py +0 -217
  45. asyncz-0.8.2/asyncz/executors/types.py +0 -7
  46. asyncz-0.8.2/asyncz/schedulers/asyncio.py +0 -64
  47. asyncz-0.8.2/asyncz/schedulers/datastructures.py +0 -49
  48. asyncz-0.8.2/asyncz/schedulers/types.py +0 -3
  49. asyncz-0.8.2/asyncz/stores/__init__.py +0 -0
  50. asyncz-0.8.2/asyncz/stores/types.py +0 -8
  51. asyncz-0.8.2/asyncz/triggers/cron/fields.py +0 -131
  52. asyncz-0.8.2/asyncz/triggers/types.py +0 -9
  53. {asyncz-0.8.2 → asyncz-0.9.0}/LICENSE +0 -0
  54. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/events/__init__.py +0 -0
  55. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/events/constants.py +0 -0
  56. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/executors/__init__.py +0 -0
  57. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/py.typed +0 -0
  58. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/schedulers/__init__.py +0 -0
  59. {asyncz-0.8.2/asyncz/contrib → asyncz-0.9.0/asyncz/stores}/__init__.py +0 -0
  60. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/tasks/__init__.py +0 -0
  61. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/tasks/types.py +0 -0
  62. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/__init__.py +0 -0
  63. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/cron/__init__.py +0 -0
  64. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/cron/constants.py +0 -0
  65. {asyncz-0.8.2 → asyncz-0.9.0}/asyncz/triggers/cron/types.py +0 -0
@@ -1,5 +1,6 @@
1
1
  # folders
2
2
  *.egg-info/
3
+ *.sqlite3
3
4
  .hypothesis/
4
5
  .idea/
5
6
  .mypy_cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: asyncz
3
- Version: 0.8.2
3
+ Version: 0.9.0
4
4
  Summary: The scheduler that nobody wants but every application needs.
5
5
  Project-URL: Homepage, https://github.com/dymmond/asyncz
6
6
  Project-URL: Documentation, https://asyncz.dymmond.com/
@@ -10,7 +10,7 @@ Project-URL: Source, https://github.com/dymmond/asyncz
10
10
  Author-email: Tiago Silva <tiago.arasilva@gmail.com>
11
11
  License-Expression: MIT
12
12
  License-File: LICENSE
13
- Keywords: api,apscheduler,asgi,asyncz,cron,fastapi,framework,http,machine learning,ml,openapi,pydantic,rest,scheduler,starlette,websocket
13
+ Keywords: apscheduler,asgi,asyncz,cron,fastapi,framework,pydantic,scheduler,starlette
14
14
  Classifier: Development Status :: 4 - Beta
15
15
  Classifier: Environment :: Web Environment
16
16
  Classifier: Framework :: AnyIO
@@ -38,19 +38,24 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
38
38
  Classifier: Typing :: Typed
39
39
  Requires-Python: >=3.8
40
40
  Requires-Dist: backports-zoneinfo; python_version < '3.9'
41
+ Requires-Dist: cryptography
41
42
  Requires-Dist: loguru<0.8.0,>=0.7.0
42
43
  Requires-Dist: pydantic<3.0.0,>=2.5.3
43
44
  Requires-Dist: tzlocal<6.0,>=4.0
44
45
  Provides-Extra: testing
45
46
  Requires-Dist: esmerald; extra == 'testing'
47
+ Requires-Dist: httpx; extra == 'testing'
48
+ Requires-Dist: lilya; extra == 'testing'
46
49
  Requires-Dist: mypy<2.0.0,>=0.982; extra == 'testing'
47
50
  Requires-Dist: pymongo<5.0.0,>=4.3.3; extra == 'testing'
48
- Requires-Dist: pytest-asyncio<1.0.0,>=0.19.0; extra == 'testing'
51
+ Requires-Dist: pytest-asyncio<1.0.0,>=0.23.0; extra == 'testing'
49
52
  Requires-Dist: pytest-cov<6.0.0,>=2.12.0; extra == 'testing'
50
53
  Requires-Dist: pytest-loguru<1,>=0.2.0; extra == 'testing'
51
54
  Requires-Dist: pytest<9.0.0,>=7.1.3; extra == 'testing'
52
55
  Requires-Dist: pytz>=2022.6; extra == 'testing'
53
56
  Requires-Dist: redis<6.0.0,>=4.4.0; extra == 'testing'
57
+ Requires-Dist: sqlalchemy; extra == 'testing'
58
+ Requires-Dist: starlette; extra == 'testing'
54
59
  Description-Content-Type: text/markdown
55
60
 
56
61
  # Asyncz
@@ -210,9 +215,91 @@ fun 😁🎉.
210
215
  Asyncz currently supports the [Esmerald framework](https://asyncz.dymmond.com/contrib/esmerald/index.md)
211
216
  and brings some batteries that are currently used by the framework and leveraging Asyncz.
212
217
 
213
- If you wish to have support to any other framework such as FastAPI, Starlite, Starlette or
214
- literally any other, check the [contributing](https://asyncz.dymmond.com/contributing.md) section and see how you can
215
- do it.
218
+
219
+ ```python
220
+ from asyncz.schedulers import AsyncIOScheduler
221
+ ...
222
+
223
+ # handle_lifespan is optional, set to True if you don't want to pass it down because the underlying app doesn't support it
224
+ # this is true for django
225
+ application = AsyncIOScheduler().asgi(application, handle_lifespan=False)
226
+ # or more simple (please do not use both together)
227
+ application = AsyncIOScheduler().asgi()(application)
228
+ ```
229
+
230
+ Using with lilya:
231
+
232
+ ```python
233
+ from asyncz.schedulers import AsyncIOScheduler
234
+
235
+ # Lilya middleware doesn't pass lifespan events
236
+
237
+ app = AsyncIOScheduler().asgi(Lilya(
238
+ routes=[...],
239
+ ))
240
+
241
+ ```
242
+
243
+ Or manually:
244
+
245
+ ```python
246
+ from asyncz.schedulers import AsyncIOScheduler
247
+
248
+ scheduler = AsyncIOScheduler()
249
+
250
+ app = Lilya(
251
+ routes=[...],
252
+ on_startup=[scheduler.start],
253
+ on_shutdown=[scheduler.shutdown],
254
+ )
255
+
256
+ ```
257
+
258
+
259
+ ## Contextmanager support
260
+
261
+ Use as sync contextmanager
262
+
263
+ ```python
264
+ from asyncz.schedulers import AsyncIOScheduler
265
+
266
+ with AsyncIOScheduler() as scheduler:
267
+ ...
268
+ ```
269
+
270
+ Use as async contextmanager
271
+
272
+ ```python
273
+ from asyncz.schedulers import AsyncIOScheduler
274
+
275
+ async with AsyncIOScheduler() as scheduler:
276
+ ...
277
+ ```
278
+
279
+ For using with lifespan of starlette:
280
+
281
+ ```python
282
+ from asyncz.schedulers import AsyncIOScheduler
283
+
284
+ async lifespan(app):
285
+ with AsyncIOScheduler() as scheduler:
286
+ yield
287
+ # or yield a state
288
+ app = Starlette(
289
+ lifespan=lifespan,
290
+ )
291
+
292
+ ```
293
+
294
+ ## Security
295
+
296
+ You should use store encryption for security reasons.
297
+
298
+ All standard stores except MemoryStore support the environment variable `ASYNCZ_STORE_ENCRYPTION_KEY`.
299
+ If set and non-empty the hash of the value is used for AESGCM encrypting the elements before sending them
300
+ to the store.
301
+ This way store entries are encrypted and authentificated so there is no security hole.
302
+ This is highly recommended! Because if someone can inject store entries he can execute code.
216
303
 
217
304
  ## Sponsors
218
305
 
@@ -155,9 +155,91 @@ fun 😁🎉.
155
155
  Asyncz currently supports the [Esmerald framework](https://asyncz.dymmond.com/contrib/esmerald/index.md)
156
156
  and brings some batteries that are currently used by the framework and leveraging Asyncz.
157
157
 
158
- If you wish to have support to any other framework such as FastAPI, Starlite, Starlette or
159
- literally any other, check the [contributing](https://asyncz.dymmond.com/contributing.md) section and see how you can
160
- do it.
158
+
159
+ ```python
160
+ from asyncz.schedulers import AsyncIOScheduler
161
+ ...
162
+
163
+ # handle_lifespan is optional, set to True if you don't want to pass it down because the underlying app doesn't support it
164
+ # this is true for django
165
+ application = AsyncIOScheduler().asgi(application, handle_lifespan=False)
166
+ # or more simple (please do not use both together)
167
+ application = AsyncIOScheduler().asgi()(application)
168
+ ```
169
+
170
+ Using with lilya:
171
+
172
+ ```python
173
+ from asyncz.schedulers import AsyncIOScheduler
174
+
175
+ # Lilya middleware doesn't pass lifespan events
176
+
177
+ app = AsyncIOScheduler().asgi(Lilya(
178
+ routes=[...],
179
+ ))
180
+
181
+ ```
182
+
183
+ Or manually:
184
+
185
+ ```python
186
+ from asyncz.schedulers import AsyncIOScheduler
187
+
188
+ scheduler = AsyncIOScheduler()
189
+
190
+ app = Lilya(
191
+ routes=[...],
192
+ on_startup=[scheduler.start],
193
+ on_shutdown=[scheduler.shutdown],
194
+ )
195
+
196
+ ```
197
+
198
+
199
+ ## Contextmanager support
200
+
201
+ Use as sync contextmanager
202
+
203
+ ```python
204
+ from asyncz.schedulers import AsyncIOScheduler
205
+
206
+ with AsyncIOScheduler() as scheduler:
207
+ ...
208
+ ```
209
+
210
+ Use as async contextmanager
211
+
212
+ ```python
213
+ from asyncz.schedulers import AsyncIOScheduler
214
+
215
+ async with AsyncIOScheduler() as scheduler:
216
+ ...
217
+ ```
218
+
219
+ For using with lifespan of starlette:
220
+
221
+ ```python
222
+ from asyncz.schedulers import AsyncIOScheduler
223
+
224
+ async lifespan(app):
225
+ with AsyncIOScheduler() as scheduler:
226
+ yield
227
+ # or yield a state
228
+ app = Starlette(
229
+ lifespan=lifespan,
230
+ )
231
+
232
+ ```
233
+
234
+ ## Security
235
+
236
+ You should use store encryption for security reasons.
237
+
238
+ All standard stores except MemoryStore support the environment variable `ASYNCZ_STORE_ENCRYPTION_KEY`.
239
+ If set and non-empty the hash of the value is used for AESGCM encrypting the elements before sending them
240
+ to the store.
241
+ This way store entries are encrypted and authentificated so there is no security hole.
242
+ This is highly recommended! Because if someone can inject store entries he can execute code.
161
243
 
162
244
  ## Sponsors
163
245
 
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -57,7 +57,7 @@ class CronState(BaseDatastructureState):
57
57
  jitter: Optional[int] = None
58
58
 
59
59
 
60
- class TaskState(BaseDatastructureState):
60
+ class TaskState(BaseDatastructureState): # type: ignore
61
61
  id: Optional[str] = None
62
62
  name: Optional[str] = None
63
63
  fn: Optional[Union[Callable[..., Any], str]] = None
@@ -69,7 +69,7 @@ class TaskState(BaseDatastructureState):
69
69
  executor: Optional[str] = None
70
70
  mistrigger_grace_time: Optional[int] = None
71
71
  max_instances: Optional[int] = None
72
- next_run_time: Optional[datetime]
72
+ next_run_time: Optional[datetime] = None
73
73
  scheduler: Optional[Any] = None
74
74
  store_alias: Optional[str] = None
75
75
  store: Optional[Union[str, StoreType]] = None
@@ -1,12 +1,12 @@
1
- from enum import Enum
1
+ from enum import Enum, IntEnum
2
2
 
3
3
 
4
- class SchedulerState(Enum):
4
+ class SchedulerState(IntEnum):
5
5
  STATE_STOPPED = 0
6
6
  STATE_RUNNING = 1
7
7
  STATE_PAUSED = 2
8
8
 
9
9
 
10
- class PluginInstance(Enum):
10
+ class PluginInstance(str, Enum):
11
11
  EXECUTOR = "executor"
12
12
  STORE = "store"
@@ -30,8 +30,8 @@ class TaskEvent(SchedulerEvent):
30
30
  store: The alias given to a store.
31
31
  """
32
32
 
33
- task_id: Union[str, int]
34
- store: Union[str, Any] = None
33
+ task_id: str
34
+ store: Optional[str] = None
35
35
 
36
36
 
37
37
  class TaskSubmissionEvent(TaskEvent):
@@ -30,7 +30,7 @@ class BaseLookupError(LookupError):
30
30
 
31
31
  detail = "Not found."
32
32
 
33
- def __init__(self, detail: Optional[str] = None):
33
+ def __init__(self, detail: Optional[str] = None) -> None:
34
34
  if not detail:
35
35
  detail = self.detail
36
36
  super().__init__(detail)
@@ -47,7 +47,7 @@ class AsynczLookupError(BaseLookupError):
47
47
  class SchedulerLookupError(BaseLookupError):
48
48
  detail = "No schedule with the id {schedule_id} has been found."
49
49
 
50
- def __init__(self, schedule_id: str):
50
+ def __init__(self, schedule_id: str) -> None:
51
51
  detail = self.detail.format(schedule_id=schedule_id)
52
52
  super().__init__(detail=detail)
53
53
 
@@ -55,7 +55,7 @@ class SchedulerLookupError(BaseLookupError):
55
55
  class TaskLookupError(BaseLookupError):
56
56
  detail = "No task with the id {task_id} has been found."
57
57
 
58
- def __init__(self, task_id: str):
58
+ def __init__(self, task_id: str) -> None:
59
59
  detail = self.detail.format(task_id=task_id)
60
60
  super().__init__(detail=detail)
61
61
 
@@ -63,7 +63,7 @@ class TaskLookupError(BaseLookupError):
63
63
  class ConflictError(KeyError):
64
64
  detail = "This data store already contains a schedule with the identifier {schedule_id}."
65
65
 
66
- def __init__(self, schedule_id: str):
66
+ def __init__(self, schedule_id: str) -> None:
67
67
  detail = self.detail.format(schedule_id=schedule_id)
68
68
  super().__init__(detail)
69
69
 
@@ -71,7 +71,7 @@ class ConflictError(KeyError):
71
71
  class ConflictIdError(KeyError):
72
72
  detail = "Task identifier ({task_id}) conflicts with an existing task."
73
73
 
74
- def __init__(self, task_id: str):
74
+ def __init__(self, task_id: str) -> None:
75
75
  detail = self.detail.format(task_id=task_id)
76
76
  super().__init__(detail)
77
77
 
@@ -83,7 +83,7 @@ class MaxInterationsReached(AsynczException):
83
83
  class MaximumInstancesError(AsynczException):
84
84
  detail = "The task by the id of {id} reached its maximum number of instances {total}."
85
85
 
86
- def __init__(self, _id: Union[UUID, str, int], total: int):
86
+ def __init__(self, _id: Union[UUID, str, int], total: int) -> None:
87
87
  detail = self.detail.format(id=_id, total=total)
88
88
  super().__init__(detail=detail)
89
89
 
@@ -1,4 +1,5 @@
1
- from typing import TYPE_CHECKING, Any, Union
1
+ from datetime import datetime
2
+ from typing import TYPE_CHECKING, Any, List, Set
2
3
 
3
4
  from asyncz.exceptions import AsynczException
4
5
  from asyncz.executors.base import BaseExecutor, run_coroutine_task, run_task
@@ -10,7 +11,8 @@ if TYPE_CHECKING:
10
11
 
11
12
  class AsyncIOExecutor(BaseExecutor):
12
13
  """
13
- Executor used for AsyncIO, typically can also be plugged into any ASGI framework as well, for example, Esmerald, Starlite, FastAPI...
14
+ Executor used for AsyncIO, typically can also be plugged into any ASGI framework as well,
15
+ for example, Esmerald, Starlite, FastAPI...
14
16
 
15
17
  Runs the task in the default executor event loop.
16
18
 
@@ -19,20 +21,20 @@ class AsyncIOExecutor(BaseExecutor):
19
21
  executor which is usually a thread pool.
20
22
  """
21
23
 
22
- def start(self, scheduler: Any, alias: str):
24
+ def start(self, scheduler: Any, alias: str) -> None:
23
25
  super().start(scheduler, alias)
24
26
  self.event_loop = scheduler.event_loop
25
- self.pending_futures = set()
27
+ self.pending_futures: Set[Any] = set()
26
28
 
27
- def shutdown(self, wait: bool = True):
29
+ def shutdown(self, wait: bool = True) -> None:
28
30
  for f in self.pending_futures:
29
31
  if not f.done():
30
32
  f.cancel()
31
33
 
32
34
  self.pending_futures.clear()
33
35
 
34
- def do_send_task(self, task: "TaskType", run_times: Union[int, str]):
35
- def callback(fn):
36
+ def do_send_task(self, task: "TaskType", run_times: List[datetime]) -> None:
37
+ def callback(fn: Any) -> None:
36
38
  self.pending_futures.discard(fn)
37
39
  try:
38
40
  events = fn.result()
@@ -43,7 +45,7 @@ class AsyncIOExecutor(BaseExecutor):
43
45
 
44
46
  if iscoroutinefunction_partial(task.fn):
45
47
  if run_coroutine_task is not None:
46
- coroutine = run_coroutine_task(task, task.store_alias, run_times, self.logger)
48
+ coroutine = run_coroutine_task(task, task.store_alias, run_times, self.logger) # type: ignore
47
49
  fn = self.event_loop.create_task(coroutine)
48
50
  else:
49
51
  raise AsynczException(
@@ -1,11 +1,11 @@
1
1
  import sys
2
2
  import traceback
3
- from abc import ABC, abstractmethod
4
3
  from collections import defaultdict
5
4
  from datetime import datetime, timedelta
6
5
  from datetime import timezone as tz
6
+ from threading import RLock
7
7
  from traceback import format_tb
8
- from typing import TYPE_CHECKING, Any, List, Optional, Union
8
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, cast
9
9
 
10
10
  from loguru import logger
11
11
  from loguru._logger import Logger
@@ -13,24 +13,25 @@ from loguru._logger import Logger
13
13
  from asyncz.events import TaskExecutionEvent
14
14
  from asyncz.events.constants import TASK_ERROR, TASK_EXECUTED, TASK_MISSED
15
15
  from asyncz.exceptions import MaximumInstancesError
16
+ from asyncz.executors.types import ExecutorType
16
17
  from asyncz.state import BaseStateExtra
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from asyncz.tasks.types import TaskType
20
21
 
21
22
 
22
- class BaseExecutor(BaseStateExtra, ABC):
23
+ class BaseExecutor(BaseStateExtra, ExecutorType):
23
24
  """
24
25
  Base model for the executors. It defines the interface for all the executors used by the Asyncz.
25
26
 
26
27
  Asyncz uses loguru for its logging as it is more descriptive and intuitive.
27
28
  """
28
29
 
29
- def __init__(self, **kwargs):
30
+ def __init__(self, **kwargs: Any) -> None:
30
31
  super().__init__(**kwargs)
31
- self.instances = defaultdict(lambda: 0)
32
+ self.instances: Dict[str, int] = defaultdict(lambda: 0)
32
33
 
33
- def start(self, scheduler: Any, alias: str):
34
+ def start(self, scheduler: Any, alias: str) -> None:
34
35
  """
35
36
  Called by the scheduler when the scheduler is being started or when the executor is being
36
37
  added to an already running scheduler.
@@ -40,11 +41,11 @@ class BaseExecutor(BaseStateExtra, ABC):
40
41
  alias - The alias of this executor as it was assigned to the scheduler.
41
42
  """
42
43
  self.scheduler = scheduler
43
- self.lock = scheduler.create_lock()
44
- self.logger = logger
44
+ self.lock: RLock = scheduler.create_lock()
45
+ self.logger: Any = logger
45
46
  self.logger.bind(logger_name=f"asyncz.executors.{alias}")
46
47
 
47
- def shutdown(self, wait: bool = True):
48
+ def shutdown(self, wait: bool = True) -> None:
48
49
  """
49
50
  Shuts down the executor.
50
51
 
@@ -52,12 +53,12 @@ class BaseExecutor(BaseStateExtra, ABC):
52
53
  wait - Boolean indicating to wait until all submitted tasks have been executed.
53
54
  """
54
55
 
55
- def send_task(self, task: "TaskType", run_times: List[datetime]):
56
+ def send_task(self, task: "TaskType", run_times: List[datetime]) -> None:
56
57
  """
57
58
  Sends the task for execution.
58
59
 
59
60
  Args:
60
- task: A Task instanceyo execute.
61
+ task: A Task instance to execute.
61
62
  run_times: A list of datetimes specifying when the task should have been run.
62
63
  """
63
64
  assert self.lock is not None, "This executor has not been started yet."
@@ -68,14 +69,7 @@ class BaseExecutor(BaseStateExtra, ABC):
68
69
  self.do_send_task(task, run_times)
69
70
  self.instances[task.id] += 1
70
71
 
71
- @abstractmethod
72
- def do_send_task(self, task: "TaskType", run_times: List[datetime]) -> Any:
73
- """
74
- Executes the actual task of scheduling `run_task` to be called.
75
- """
76
- ...
77
-
78
- def run_task_success(self, task_id: Union[str, int], events: List[int]) -> Any:
72
+ def run_task_success(self, task_id: str, events: List[TaskExecutionEvent]) -> None:
79
73
  """
80
74
  Called by the executor with the list of generated events when the function run_task has
81
75
  been successfully executed.
@@ -88,7 +82,7 @@ class BaseExecutor(BaseStateExtra, ABC):
88
82
  for event in events or []:
89
83
  self.scheduler.dispatch_event(event)
90
84
 
91
- def run_task_error(self, task_id: Union[str, int]) -> Any:
85
+ def run_task_error(self, task_id: str) -> None:
92
86
  """
93
87
  Called by the executor with the exception if there is an error calling the run_task.
94
88
  """
@@ -97,17 +91,15 @@ class BaseExecutor(BaseStateExtra, ABC):
97
91
  if self.instances[task_id] == 0:
98
92
  del self.instances[task_id]
99
93
 
100
- self.logger.opt(exception=True).error(
101
- f"Error running task {task_id}", exc_info=True
102
- )
94
+ self.logger.opt(exception=True).error(f"Error running task {task_id}", exc_info=True)
103
95
 
104
96
 
105
97
  def run_task(
106
98
  task: "TaskType",
107
99
  store_alias: str,
108
100
  run_times: List[datetime],
109
- _logger: Optional["Logger"] = None,
110
- ):
101
+ _logger: Optional[Any] = None,
102
+ ) -> List[TaskExecutionEvent]:
111
103
  """
112
104
  Called by executors to run the task. Returns a list of scheduler events to be dispatched by the
113
105
  scheduler.
@@ -136,9 +128,9 @@ def run_task(
136
128
 
137
129
  _logger.info(f'Running task "{task}" (scheduled at {run_time})')
138
130
  try:
139
- return_value = task.fn(*task.args, **task.kwargs)
140
- except BaseException:
141
- exc, trace_back = sys.exc_info()[1:]
131
+ return_value = cast(Callable[..., Any], task.fn)(*task.args, **task.kwargs)
132
+ except Exception as exc:
133
+ trace_back = sys.exc_info()[2]
142
134
  formatted_trace_back = "".join(format_tb(trace_back))
143
135
  events.append(
144
136
  TaskExecutionEvent(
@@ -171,7 +163,7 @@ async def run_coroutine_task(
171
163
  store_alias: str,
172
164
  run_times: List[datetime],
173
165
  _logger: Optional["Logger"] = None,
174
- ):
166
+ ) -> List[TaskExecutionEvent]:
175
167
  """
176
168
  Called by executors to run the task. Returns a list of scheduler events to be dispatched by the
177
169
  scheduler.
@@ -179,7 +171,7 @@ async def run_coroutine_task(
179
171
  The run task is made to run in async mode.
180
172
  """
181
173
  if not _logger:
182
- _logger = logger
174
+ _logger = logger # type: ignore
183
175
 
184
176
  events = []
185
177
  for run_time in run_times:
@@ -195,14 +187,14 @@ async def run_coroutine_task(
195
187
  scheduled_run_time=run_time,
196
188
  )
197
189
  )
198
- _logger.warning(f"Run time of task '{task}' was missed by {difference}")
190
+ _logger.warning(f"Run time of task '{task}' was missed by {difference}") # type: ignore
199
191
  continue
200
192
 
201
- _logger.info(f'Running task "{task}" (scheduled at {run_time})')
193
+ _logger.info(f'Running task "{task}" (scheduled at {run_time})') # type: ignore
202
194
  try:
203
- return_value = await task.fn(*task.args, **task.kwargs)
204
- except BaseException:
205
- exc, trace_back = sys.exc_info()[1:]
195
+ return_value = await cast(Callable[..., Any], task.fn)(*task.args, **task.kwargs)
196
+ except Exception as exc:
197
+ trace_back = sys.exc_info()[2]
206
198
  formatted_trace_back = "".join(format_tb(trace_back))
207
199
  events.append(
208
200
  TaskExecutionEvent(
@@ -226,6 +218,6 @@ async def run_coroutine_task(
226
218
  return_value=return_value,
227
219
  )
228
220
  )
229
- _logger.info(f"Task '{task}' executed successfully")
221
+ _logger.info(f"Task '{task}' executed successfully") # type: ignore
230
222
 
231
223
  return events
@@ -1,4 +1,5 @@
1
- from typing import TYPE_CHECKING, Union
1
+ from datetime import datetime
2
+ from typing import TYPE_CHECKING, List
2
3
 
3
4
  from asyncz.executors.base import BaseExecutor, run_task
4
5
 
@@ -15,14 +16,11 @@ class DebugExecutor(BaseExecutor):
15
16
  def do_send_task(
16
17
  self,
17
18
  task: "TaskType",
18
- run_times: Union[
19
- int,
20
- str,
21
- ],
22
- ):
19
+ run_times: List[datetime],
20
+ ) -> None:
23
21
  try:
24
- events = run_task(task, task.store_alias, run_times, self.logger)
25
- except BaseException:
22
+ events = run_task(task, task.store_alias, run_times, self.logger) # type: ignore
23
+ except Exception:
26
24
  self.run_task_error(task.id)
27
25
  else:
28
26
  self.run_task_success(task.id, events)
@@ -16,12 +16,12 @@ class BasePoolExecutor(BaseExecutor):
16
16
  model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True, populate_by_name=True)
17
17
 
18
18
  @abstractmethod
19
- def __init__(self, pool: Any, **kwargs: Any):
19
+ def __init__(self, pool: Any, **kwargs: Any) -> None:
20
20
  super().__init__(**kwargs)
21
21
  self.pool = pool
22
22
 
23
23
  def do_send_task(self, task: "TaskType", run_times: List[datetime]) -> Any:
24
- def callback(fn):
24
+ def callback(fn: Any) -> None:
25
25
  exc, _ = (
26
26
  fn.exception_info()
27
27
  if hasattr(fn, "exception_info")
@@ -41,7 +41,7 @@ class BasePoolExecutor(BaseExecutor):
41
41
 
42
42
  fn.add_done_callback(callback)
43
43
 
44
- def shutdown(self, wait=True):
44
+ def shutdown(self, wait: bool = True) -> None:
45
45
  self.pool.shutdown(wait)
46
46
 
47
47
 
@@ -70,7 +70,7 @@ class ProcessPoolExecutor(BasePoolExecutor):
70
70
  ProcessPoolExecutor constructor.
71
71
  """
72
72
 
73
- def __init__(self, max_workers: int = 10, pool_kwargs: Optional[Any] = None):
73
+ def __init__(self, max_workers: int = 10, pool_kwargs: Optional[Any] = None) -> None:
74
74
  pool_kwargs = pool_kwargs or {}
75
75
  pool = concurrent.futures.ProcessPoolExecutor(int(max_workers), **pool_kwargs)
76
76
  super().__init__(pool)