jararaca 0.3.9__py3-none-any.whl → 0.3.11__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.

Files changed (35) hide show
  1. jararaca/__init__.py +76 -5
  2. jararaca/cli.py +460 -116
  3. jararaca/core/uow.py +17 -12
  4. jararaca/messagebus/decorators.py +33 -30
  5. jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +30 -2
  6. jararaca/messagebus/interceptors/publisher_interceptor.py +7 -3
  7. jararaca/messagebus/publisher.py +14 -6
  8. jararaca/messagebus/worker.py +1102 -88
  9. jararaca/microservice.py +137 -34
  10. jararaca/observability/decorators.py +7 -3
  11. jararaca/observability/interceptor.py +4 -2
  12. jararaca/observability/providers/otel.py +14 -10
  13. jararaca/persistence/base.py +2 -1
  14. jararaca/persistence/interceptors/aiosqa_interceptor.py +167 -16
  15. jararaca/persistence/utilities.py +32 -20
  16. jararaca/presentation/decorators.py +96 -10
  17. jararaca/presentation/server.py +31 -4
  18. jararaca/presentation/websocket/context.py +30 -4
  19. jararaca/presentation/websocket/types.py +2 -2
  20. jararaca/presentation/websocket/websocket_interceptor.py +28 -4
  21. jararaca/reflect/__init__.py +0 -0
  22. jararaca/reflect/controller_inspect.py +75 -0
  23. jararaca/{tools → reflect}/metadata.py +25 -5
  24. jararaca/scheduler/{scheduler_v2.py → beat_worker.py} +49 -53
  25. jararaca/scheduler/decorators.py +55 -20
  26. jararaca/tools/app_config/interceptor.py +4 -2
  27. jararaca/utils/rabbitmq_utils.py +259 -5
  28. jararaca/utils/retry.py +141 -0
  29. {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/METADATA +2 -1
  30. {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/RECORD +33 -32
  31. {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/WHEEL +1 -1
  32. jararaca/messagebus/worker_v2.py +0 -617
  33. jararaca/scheduler/scheduler.py +0 -161
  34. {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/LICENSE +0 -0
  35. {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/entry_points.txt +0 -0
@@ -1,161 +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 Microservice, SchedulerAppContext
18
- from jararaca.scheduler.decorators import ScheduledAction
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- @dataclass
24
- class SchedulerConfig:
25
- interval: int
26
-
27
-
28
- def extract_scheduled_actions(
29
- app: Microservice, container: Container
30
- ) -> list[tuple[Callable[..., Any], "ScheduledAction"]]:
31
- scheduled_actions: list[tuple[Callable[..., Any], "ScheduledAction"]] = []
32
- for controllers in app.controllers:
33
-
34
- controller_instance: Any = container.get_by_type(controllers)
35
-
36
- controller_scheduled_actions = ScheduledAction.get_type_scheduled_actions(
37
- controller_instance
38
- )
39
- scheduled_actions.extend(controller_scheduled_actions)
40
-
41
- return scheduled_actions
42
-
43
-
44
- # TODO: Implement Backend for Distributed Lock
45
- # TODO: Improve error handling
46
- # TODO: Implement logging
47
- # TODO: Implement tests
48
- # TODO: Implement graceful shutdown
49
- # TODO: Implement ScheduletAction parameters configuration
50
- class Scheduler:
51
-
52
- def __init__(
53
- self,
54
- app: Microservice,
55
- interval: int,
56
- ) -> None:
57
- self.app = app
58
-
59
- self.interval = interval
60
- self.container = Container(self.app)
61
- self.uow_provider = UnitOfWorkContextProvider(app, self.container)
62
-
63
- self.tasks: set[asyncio.Task[Any]] = set()
64
- self.lock = asyncio.Lock()
65
- self.shutdown_event = asyncio.Event()
66
-
67
- self.last_checks: dict[Callable[..., Any], datetime] = {}
68
-
69
- self.lifceycle = AppLifecycle(app, self.container)
70
-
71
- async def process_task(
72
- self, func: Callable[..., Any], scheduled_action: ScheduledAction
73
- ) -> None:
74
-
75
- async with self.lock:
76
- task = asyncio.create_task(self.handle_task(func, scheduled_action))
77
- self.tasks.add(task)
78
- task.add_done_callback(self.tasks.discard)
79
-
80
- async def handle_task(
81
- self, func: Callable[..., Any], scheduled_action: ScheduledAction
82
- ) -> None:
83
-
84
- last_check = self.last_checks.setdefault(func, datetime.now(UTC))
85
-
86
- cron = croniter(scheduled_action.cron, last_check)
87
- next_run: datetime = cron.get_next(datetime)
88
- if next_run > datetime.now(UTC):
89
- logger.info(
90
- f"Skipping {func.__module__}.{func.__qualname__} until {next_run}"
91
- )
92
- return
93
-
94
- logger.info(f"Running {func.__module__}.{func.__qualname__}")
95
-
96
- action_specs = ScheduledAction.get_scheduled_action(func)
97
-
98
- assert action_specs is not None
99
-
100
- ctx: AsyncContextManager[Any]
101
- if action_specs.timeout:
102
- ctx = asyncio.timeout(action_specs.timeout)
103
- else:
104
- ctx = none_context()
105
-
106
- try:
107
- async with self.uow_provider(
108
- SchedulerAppContext(
109
- action=func,
110
- scheduled_to=next_run,
111
- cron_expression=scheduled_action.cron,
112
- triggered_at=datetime.now(UTC),
113
- )
114
- ):
115
- try:
116
- async with ctx:
117
- signature = inspect.signature(func)
118
- if len(signature.parameters) > 0:
119
- logging.warning(
120
- f"Scheduled action {func.__module__}.{func.__qualname__} has parameters, but no arguments were provided. Must be using scheduler-v2"
121
- )
122
- await func(ScheduleDispatchData(time.time()))
123
- else:
124
- await func()
125
-
126
- except BaseException as e:
127
- if action_specs.exception_handler:
128
- action_specs.exception_handler(e)
129
- else:
130
- logging.exception(
131
- f"Error in scheduled action {scheduled_action}: {e}"
132
- )
133
-
134
- except Exception as e:
135
- logging.exception(f"Error in scheduled action {scheduled_action}: {e}")
136
-
137
- self.last_checks[func] = datetime.now(UTC)
138
-
139
- def run(self) -> None:
140
-
141
- async def run_scheduled_actions() -> None:
142
-
143
- async with self.lifceycle():
144
- scheduled_actions = extract_scheduled_actions(self.app, self.container)
145
-
146
- while True:
147
- for func, scheduled_action in scheduled_actions:
148
- if self.shutdown_event.is_set():
149
- break
150
-
151
- await self.process_task(func, scheduled_action)
152
-
153
- await asyncio.sleep(self.interval)
154
-
155
- with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
156
- runner.run(run_scheduled_actions())
157
-
158
-
159
- @asynccontextmanager
160
- async def none_context() -> AsyncGenerator[None, None]:
161
- yield