jararaca 0.3.11a9__py3-none-any.whl → 0.3.11a11__py3-none-any.whl

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.

Potentially problematic release.


This version of jararaca might be problematic. Click here for more details.

@@ -1,181 +0,0 @@
1
- import asyncio
2
- import inspect
3
- import logging
4
- import time
5
- from contextlib import asynccontextmanager
6
- from dataclasses import dataclass
7
- from datetime import UTC, datetime
8
- from typing import Any, AsyncContextManager, AsyncGenerator, Callable
9
-
10
- import uvloop
11
- from croniter import croniter
12
-
13
- from jararaca.core.uow import UnitOfWorkContextProvider
14
- from jararaca.di import Container
15
- from jararaca.lifecycle import AppLifecycle
16
- from jararaca.messagebus.decorators import ScheduleDispatchData
17
- from jararaca.microservice import (
18
- AppTransactionContext,
19
- Microservice,
20
- SchedulerTransactionData,
21
- )
22
- from jararaca.scheduler.decorators import (
23
- ScheduledAction,
24
- ScheduledActionData,
25
- get_type_scheduled_actions,
26
- )
27
-
28
- logger = logging.getLogger(__name__)
29
-
30
-
31
- @dataclass
32
- class SchedulerConfig:
33
- interval: int
34
-
35
-
36
- def extract_scheduled_actions(
37
- app: Microservice, container: Container, scheduler_names: set[str] | None = None
38
- ) -> list[ScheduledActionData]:
39
- scheduled_actions: list[ScheduledActionData] = []
40
- for controllers in app.controllers:
41
-
42
- controller_instance: Any = container.get_by_type(controllers)
43
-
44
- controller_scheduled_actions = get_type_scheduled_actions(controller_instance)
45
-
46
- # Filter scheduled actions by name if scheduler_names is provided
47
- if scheduler_names is not None:
48
- filtered_actions = []
49
- for action in controller_scheduled_actions:
50
- # Include actions that have a name and it's in the provided set
51
- if action.spec.name and action.spec.name in scheduler_names:
52
- filtered_actions.append(action)
53
- # Skip actions without names when filtering is active
54
- controller_scheduled_actions = filtered_actions
55
-
56
- scheduled_actions.extend(controller_scheduled_actions)
57
-
58
- return scheduled_actions
59
-
60
-
61
- # TODO: Implement Backend for Distributed Lock
62
- # TODO: Improve error handling
63
- # TODO: Implement logging
64
- # TODO: Implement tests
65
- # TODO: Implement graceful shutdown
66
- # TODO: Implement ScheduletAction parameters configuration
67
- class Scheduler:
68
-
69
- def __init__(
70
- self,
71
- app: Microservice,
72
- interval: int,
73
- scheduler_names: set[str] | None = None,
74
- ) -> None:
75
- self.app = app
76
-
77
- self.interval = interval
78
- self.scheduler_names = scheduler_names
79
- self.container = Container(self.app)
80
- self.uow_provider = UnitOfWorkContextProvider(app, self.container)
81
-
82
- self.tasks: set[asyncio.Task[Any]] = set()
83
- self.lock = asyncio.Lock()
84
- self.shutdown_event = asyncio.Event()
85
-
86
- self.last_checks: dict[Callable[..., Any], datetime] = {}
87
-
88
- self.lifceycle = AppLifecycle(app, self.container)
89
-
90
- async def process_task(self, sched_act_data: ScheduledActionData) -> None:
91
-
92
- async with self.lock:
93
- task = asyncio.create_task(self.handle_task(sched_act_data))
94
- self.tasks.add(task)
95
- task.add_done_callback(self.tasks.discard)
96
-
97
- async def handle_task(self, sched_act_data: ScheduledActionData) -> None:
98
- func = sched_act_data.callable
99
- scheduled_action = sched_act_data.spec
100
- last_check = self.last_checks.setdefault(func, datetime.now(UTC))
101
-
102
- cron = croniter(scheduled_action.cron, last_check)
103
- next_run: datetime = cron.get_next(datetime)
104
- if next_run > datetime.now(UTC):
105
- logger.info(
106
- f"Skipping {func.__module__}.{func.__qualname__} until {next_run}"
107
- )
108
- return
109
-
110
- logger.info(f"Running {func.__module__}.{func.__qualname__}")
111
-
112
- action_specs = ScheduledAction.get_scheduled_action(func)
113
-
114
- assert action_specs is not None
115
-
116
- ctx: AsyncContextManager[Any]
117
- if action_specs.timeout:
118
- ctx = asyncio.timeout(action_specs.timeout)
119
- else:
120
- ctx = none_context()
121
-
122
- try:
123
- async with self.uow_provider(
124
- app_context=AppTransactionContext(
125
- controller_member_reflect=sched_act_data.controller_member,
126
- transaction_data=SchedulerTransactionData(
127
- scheduled_to=next_run,
128
- cron_expression=scheduled_action.cron,
129
- triggered_at=datetime.now(UTC),
130
- ),
131
- )
132
- ):
133
- try:
134
- async with ctx:
135
- signature = inspect.signature(func)
136
- if len(signature.parameters) > 0:
137
- logging.warning(
138
- f"Scheduled action {func.__module__}.{func.__qualname__} has parameters, but no arguments were provided. Must be using scheduler-v2"
139
- )
140
- await func(ScheduleDispatchData(time.time()))
141
- else:
142
- await func()
143
-
144
- except BaseException as e:
145
- if action_specs.exception_handler:
146
- action_specs.exception_handler(e)
147
- else:
148
- logging.exception(
149
- f"Error in scheduled action {scheduled_action}: {e}"
150
- )
151
-
152
- except Exception as e:
153
- logging.exception(f"Error in scheduled action {scheduled_action}: {e}")
154
-
155
- self.last_checks[func] = datetime.now(UTC)
156
-
157
- def run(self) -> None:
158
-
159
- async def run_scheduled_actions() -> None:
160
-
161
- async with self.lifceycle():
162
- scheduled_actions = extract_scheduled_actions(
163
- self.app, self.container, self.scheduler_names
164
- )
165
-
166
- while True:
167
- for action in scheduled_actions:
168
- if self.shutdown_event.is_set():
169
- break
170
-
171
- await self.process_task(action)
172
-
173
- await asyncio.sleep(self.interval)
174
-
175
- with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
176
- runner.run(run_scheduled_actions())
177
-
178
-
179
- @asynccontextmanager
180
- async def none_context() -> AsyncGenerator[None, None]:
181
- yield