jararaca 0.2.37a12__py3-none-any.whl → 0.3.0__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,5 +1,7 @@
1
1
  import asyncio
2
+ import inspect
2
3
  import logging
4
+ import time
3
5
  from contextlib import asynccontextmanager
4
6
  from dataclasses import dataclass
5
7
  from datetime import UTC, datetime
@@ -11,6 +13,7 @@ from croniter import croniter
11
13
  from jararaca.core.uow import UnitOfWorkContextProvider
12
14
  from jararaca.di import Container
13
15
  from jararaca.lifecycle import AppLifecycle
16
+ from jararaca.messagebus.decorators import ScheduleDispatchData
14
17
  from jararaca.microservice import Microservice, SchedulerAppContext
15
18
  from jararaca.scheduler.decorators import ScheduledAction
16
19
 
@@ -22,9 +25,6 @@ class SchedulerConfig:
22
25
  interval: int
23
26
 
24
27
 
25
- class SchedulerBackend: ...
26
-
27
-
28
28
  def extract_scheduled_actions(
29
29
  app: Microservice, container: Container
30
30
  ) -> list[tuple[Callable[..., Any], "ScheduledAction"]]:
@@ -52,12 +52,11 @@ class Scheduler:
52
52
  def __init__(
53
53
  self,
54
54
  app: Microservice,
55
- backend: SchedulerBackend,
56
- config: SchedulerConfig,
55
+ interval: int,
57
56
  ) -> None:
58
57
  self.app = app
59
- self.backend = backend
60
- self.config = config
58
+
59
+ self.interval = interval
61
60
  self.container = Container(self.app)
62
61
  self.uow_provider = UnitOfWorkContextProvider(app, self.container)
63
62
 
@@ -115,7 +114,15 @@ class Scheduler:
115
114
  ):
116
115
  try:
117
116
  async with ctx:
118
- await func()
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
+
119
126
  except BaseException as e:
120
127
  if action_specs.exception_handler:
121
128
  action_specs.exception_handler(e)
@@ -143,7 +150,7 @@ class Scheduler:
143
150
 
144
151
  await self.process_task(func, scheduled_action)
145
152
 
146
- await asyncio.sleep(self.config.interval)
153
+ await asyncio.sleep(self.interval)
147
154
 
148
155
  with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
149
156
  runner.run(run_scheduled_actions())
@@ -0,0 +1,346 @@
1
+ import asyncio
2
+ import contextlib
3
+ import logging
4
+ import signal
5
+ import time
6
+ from abc import ABC, abstractmethod
7
+ from contextlib import asynccontextmanager
8
+ from datetime import UTC, datetime
9
+ from types import FrameType
10
+ from typing import Any, AsyncGenerator, Callable
11
+ from urllib.parse import parse_qs
12
+
13
+ import aio_pika
14
+ import croniter
15
+ import urllib3
16
+ import urllib3.util
17
+ import uvloop
18
+ from aio_pika import connect_robust
19
+ from aio_pika.abc import AbstractChannel, AbstractRobustConnection
20
+ from aio_pika.pool import Pool
21
+
22
+ from jararaca.broker_backend import MessageBrokerBackend
23
+ from jararaca.broker_backend.mapper import get_message_broker_backend_from_url
24
+ from jararaca.core.uow import UnitOfWorkContextProvider
25
+ from jararaca.di import Container
26
+ from jararaca.lifecycle import AppLifecycle
27
+ from jararaca.microservice import Microservice
28
+ from jararaca.scheduler.decorators import ScheduledAction
29
+ from jararaca.scheduler.types import DelayedMessageData
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ SCHEDULED_ACTION_LIST = list[tuple[Callable[..., Any], "ScheduledAction"]]
34
+
35
+
36
+ def extract_scheduled_actions(
37
+ app: Microservice, container: Container
38
+ ) -> SCHEDULED_ACTION_LIST:
39
+ scheduled_actions: SCHEDULED_ACTION_LIST = []
40
+ for controllers in app.controllers:
41
+
42
+ controller_instance: Any = container.get_by_type(controllers)
43
+
44
+ controller_scheduled_actions = ScheduledAction.get_type_scheduled_actions(
45
+ controller_instance
46
+ )
47
+ scheduled_actions.extend(controller_scheduled_actions)
48
+
49
+ return scheduled_actions
50
+
51
+
52
+ # region Message Broker Dispatcher
53
+
54
+
55
+ class MessageBrokerDispatcher(ABC):
56
+
57
+ @abstractmethod
58
+ async def dispatch_scheduled_action(
59
+ self,
60
+ action_id: str,
61
+ timestamp: int,
62
+ ) -> None:
63
+ """
64
+ Dispatch a message to the message broker.
65
+ This is used to send a message to the message broker
66
+ to trigger the scheduled action.
67
+ """
68
+ raise NotImplementedError("dispatch() is not implemented yet.")
69
+
70
+ @abstractmethod
71
+ async def dispatch_delayed_message(
72
+ self,
73
+ delayed_message: DelayedMessageData,
74
+ ) -> None:
75
+ """
76
+ Dispatch a delayed message to the message broker.
77
+ This is used to send a message to the message broker
78
+ to trigger the scheduled action.
79
+ """
80
+
81
+ raise NotImplementedError("dispatch_delayed_message() is not implemented yet.")
82
+
83
+ @abstractmethod
84
+ async def initialize(self, scheduled_actions: SCHEDULED_ACTION_LIST) -> None:
85
+ raise NotImplementedError("initialize() is not implemented yet.")
86
+
87
+ async def dispose(self) -> None:
88
+ pass
89
+
90
+
91
+ class RabbitMQBrokerDispatcher(MessageBrokerDispatcher):
92
+
93
+ def __init__(self, url: str) -> None:
94
+ self.url = url
95
+
96
+ self.conn_pool: "Pool[AbstractRobustConnection]" = Pool(
97
+ self._create_connection,
98
+ max_size=10,
99
+ )
100
+
101
+ self.channel_pool: "Pool[AbstractChannel]" = Pool(
102
+ self._create_channel,
103
+ max_size=10,
104
+ )
105
+
106
+ splitted = urllib3.util.parse_url(url)
107
+
108
+ assert splitted.scheme in ["amqp", "amqps"], "Invalid URL scheme"
109
+
110
+ assert splitted.host, "Invalid URL host"
111
+
112
+ assert splitted.query, "Invalid URL query"
113
+
114
+ query_params: dict[str, list[str]] = parse_qs(splitted.query)
115
+
116
+ assert "exchange" in query_params, "Missing exchange parameter"
117
+
118
+ assert query_params["exchange"], "Empty exchange parameter"
119
+
120
+ self.exchange = str(query_params["exchange"][0])
121
+
122
+ async def _create_connection(self) -> AbstractRobustConnection:
123
+ """
124
+ Create a connection to the RabbitMQ server.
125
+ This is used to send messages to the RabbitMQ server.
126
+ """
127
+ connection = await connect_robust(self.url)
128
+ return connection
129
+
130
+ async def _create_channel(self) -> AbstractChannel:
131
+ """
132
+ Create a channel to the RabbitMQ server.
133
+ This is used to send messages to the RabbitMQ server.
134
+ """
135
+ async with self.conn_pool.acquire() as connection:
136
+ channel = await connection.channel()
137
+ return channel
138
+
139
+ async def dispatch_scheduled_action(self, action_id: str, timestamp: int) -> None:
140
+ """
141
+ Dispatch a message to the RabbitMQ server.
142
+ This is used to send a message to the RabbitMQ server
143
+ to trigger the scheduled action.
144
+ """
145
+
146
+ logger.info(f"Dispatching message to {action_id} at {timestamp}")
147
+ async with self.channel_pool.acquire() as channel:
148
+ exchange = await channel.get_exchange(self.exchange)
149
+
150
+ await exchange.publish(
151
+ aio_pika.Message(body=str(timestamp).encode()),
152
+ routing_key=action_id,
153
+ )
154
+ logger.info(f"Dispatched message to {action_id} at {timestamp}")
155
+
156
+ async def dispatch_delayed_message(
157
+ self, delayed_message: DelayedMessageData
158
+ ) -> None:
159
+ """
160
+ Dispatch a delayed message to the RabbitMQ server.
161
+ This is used to send a message to the RabbitMQ server
162
+ to trigger the scheduled action.
163
+ """
164
+ async with self.channel_pool.acquire() as channel:
165
+
166
+ exchange = await channel.get_exchange(self.exchange)
167
+ await exchange.publish(
168
+ aio_pika.Message(
169
+ body=delayed_message.payload,
170
+ ),
171
+ routing_key=f"{delayed_message.message_topic}.",
172
+ )
173
+
174
+ async def initialize(self, scheduled_actions: SCHEDULED_ACTION_LIST) -> None:
175
+ """
176
+ Initialize the RabbitMQ server.
177
+ This is used to create the exchange and queues for the scheduled actions.
178
+ """
179
+
180
+ async with self.channel_pool.acquire() as channel:
181
+
182
+ await channel.set_qos(prefetch_count=1)
183
+
184
+ await channel.declare_exchange(
185
+ name=self.exchange,
186
+ type="topic",
187
+ durable=True,
188
+ auto_delete=False,
189
+ )
190
+
191
+ for func, _ in scheduled_actions:
192
+ queue = await channel.declare_queue(
193
+ name=ScheduledAction.get_function_id(func),
194
+ durable=True,
195
+ )
196
+
197
+ await queue.bind(
198
+ exchange=self.exchange,
199
+ routing_key=ScheduledAction.get_function_id(func),
200
+ )
201
+
202
+ async def dispose(self) -> None:
203
+ await self.channel_pool.close()
204
+ await self.conn_pool.close()
205
+
206
+
207
+ def get_message_broker_dispatcher_from_url(url: str) -> MessageBrokerDispatcher:
208
+ """
209
+ Factory function to create a message broker instance from a URL.
210
+ Currently, only RabbitMQ is supported.
211
+ """
212
+ if url.startswith("amqp://") or url.startswith("amqps://"):
213
+ return RabbitMQBrokerDispatcher(url=url)
214
+ else:
215
+ raise ValueError(f"Unsupported message broker URL: {url}")
216
+
217
+
218
+ # endregion
219
+
220
+
221
+ class SchedulerV2:
222
+
223
+ def __init__(
224
+ self,
225
+ app: Microservice,
226
+ interval: int,
227
+ broker_url: str,
228
+ backend_url: str,
229
+ ) -> None:
230
+ self.app = app
231
+
232
+ self.broker: MessageBrokerDispatcher = get_message_broker_dispatcher_from_url(
233
+ broker_url
234
+ )
235
+ self.backend: MessageBrokerBackend = get_message_broker_backend_from_url(
236
+ backend_url
237
+ )
238
+
239
+ self.interval = interval
240
+ self.container = Container(self.app)
241
+ self.uow_provider = UnitOfWorkContextProvider(app, self.container)
242
+
243
+ self.shutdown_event = asyncio.Event()
244
+
245
+ self.lifecycle = AppLifecycle(app, self.container)
246
+
247
+ def run(self) -> None:
248
+
249
+ def on_signal_received(signal: int, frame_type: FrameType | None) -> None:
250
+ logger.info("Received shutdown signal")
251
+ self.shutdown_event.set()
252
+
253
+ signal.signal(signal.SIGINT, on_signal_received)
254
+
255
+ with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
256
+ runner.run(self.start_scheduler())
257
+
258
+ async def start_scheduler(self) -> None:
259
+ """
260
+ Declares the scheduled actions and starts the scheduler.
261
+ This is the main entry point for the scheduler.
262
+ """
263
+ async with self.lifecycle():
264
+
265
+ scheduled_actions = extract_scheduled_actions(self.app, self.container)
266
+
267
+ await self.broker.initialize(scheduled_actions)
268
+
269
+ await self.run_scheduled_actions(scheduled_actions)
270
+
271
+ async def run_scheduled_actions(
272
+ self, scheduled_actions: SCHEDULED_ACTION_LIST
273
+ ) -> None:
274
+
275
+ while not self.shutdown_event.is_set():
276
+ now = int(time.time())
277
+ for func, scheduled_action in scheduled_actions:
278
+ if self.shutdown_event.is_set():
279
+ break
280
+
281
+ async with self.backend.lock():
282
+
283
+ last_dispatch_time: int | None = (
284
+ await self.backend.get_last_dispatch_time(
285
+ ScheduledAction.get_function_id(func)
286
+ )
287
+ )
288
+
289
+ if last_dispatch_time is not None:
290
+ cron = croniter.croniter(
291
+ scheduled_action.cron, last_dispatch_time
292
+ )
293
+ next_run: datetime = cron.get_next(datetime).replace(tzinfo=UTC)
294
+ if next_run > datetime.now(UTC):
295
+ logger.info(
296
+ f"Skipping {func.__module__}.{func.__qualname__} until {next_run}"
297
+ )
298
+ continue
299
+
300
+ if not scheduled_action.allow_overlap:
301
+ if (
302
+ await self.backend.get_in_execution_count(
303
+ ScheduledAction.get_function_id(func)
304
+ )
305
+ > 0
306
+ ):
307
+ continue
308
+
309
+ await self.broker.dispatch_scheduled_action(
310
+ ScheduledAction.get_function_id(func),
311
+ now,
312
+ )
313
+
314
+ await self.backend.set_last_dispatch_time(
315
+ ScheduledAction.get_function_id(func), now
316
+ )
317
+
318
+ logger.info(
319
+ f"Scheduled {func.__module__}.{func.__qualname__} at {now}"
320
+ )
321
+
322
+ for (
323
+ delayed_message_data
324
+ ) in await self.backend.dequeue_next_delayed_messages(now):
325
+ await self.broker.dispatch_delayed_message(delayed_message_data)
326
+
327
+ with contextlib.suppress(asyncio.TimeoutError):
328
+ await asyncio.wait_for(self.shutdown_event.wait(), self.interval)
329
+
330
+ # await self.shutdown_event.wait(self.interval)
331
+
332
+ logger.info("Scheduler stopped")
333
+
334
+ await self.backend.dispose()
335
+ await self.broker.dispose()
336
+
337
+
338
+ @asynccontextmanager
339
+ async def none_context() -> AsyncGenerator[None, None]:
340
+ yield
341
+
342
+
343
+ logging.basicConfig(
344
+ level=logging.INFO,
345
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
346
+ )
@@ -0,0 +1,7 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class DelayedMessageData(BaseModel):
5
+ message_topic: str
6
+ dispatch_time: int
7
+ payload: bytes
File without changes
@@ -0,0 +1,84 @@
1
+ from aio_pika.abc import AbstractChannel, AbstractExchange, AbstractQueue
2
+
3
+
4
+ class RabbitmqUtils:
5
+
6
+ DEAD_LETTER_EXCHANGE = "dlx"
7
+ DEAD_LETTER_QUEUE = "dlq"
8
+
9
+ @classmethod
10
+ async def declare_dl_exchange(cls, channel: AbstractChannel) -> AbstractExchange:
11
+ """
12
+ Declare a Dead Letter Exchange (DLX) for the given channel.
13
+ """
14
+ await channel.set_qos(prefetch_count=1)
15
+ return await channel.declare_exchange(
16
+ cls.DEAD_LETTER_EXCHANGE,
17
+ type="direct",
18
+ durable=True,
19
+ auto_delete=False,
20
+ )
21
+
22
+ @classmethod
23
+ async def declare_dl_queue(cls, channel: AbstractChannel) -> AbstractQueue:
24
+ """
25
+ Declare a Dead Letter Queue (DLQ) for the given queue.
26
+ """
27
+ await channel.set_qos(prefetch_count=1)
28
+ return await channel.declare_queue(
29
+ cls.DEAD_LETTER_QUEUE,
30
+ durable=True,
31
+ arguments={
32
+ "x-dead-letter-exchange": "",
33
+ "x-dead-letter-routing-key": cls.DEAD_LETTER_EXCHANGE,
34
+ },
35
+ )
36
+
37
+ @classmethod
38
+ async def delcare_dl_kit(
39
+ cls,
40
+ channel: AbstractChannel,
41
+ ) -> tuple[AbstractExchange, AbstractQueue]:
42
+ """
43
+ Declare a Dead Letter Exchange and Queue (DLX and DLQ) for the given channel.
44
+ """
45
+ dlx = await cls.declare_dl_exchange(channel)
46
+ dlq = await cls.declare_dl_queue(channel)
47
+ await dlq.bind(dlx, routing_key=cls.DEAD_LETTER_EXCHANGE)
48
+ return dlx, dlq
49
+
50
+ @classmethod
51
+ async def declare_main_exchange(
52
+ cls,
53
+ channel: AbstractChannel,
54
+ exchange_name: str,
55
+ ) -> AbstractExchange:
56
+ """
57
+ Declare a main exchange for the given channel.
58
+ """
59
+ await channel.set_qos(prefetch_count=1)
60
+ return await channel.declare_exchange(
61
+ exchange_name,
62
+ type="topic",
63
+ durable=True,
64
+ auto_delete=False,
65
+ )
66
+
67
+ @classmethod
68
+ async def declare_queue(
69
+ cls,
70
+ channel: AbstractChannel,
71
+ queue_name: str,
72
+ ) -> AbstractQueue:
73
+ """
74
+ Declare a queue with the given name and properties.
75
+ """
76
+ await channel.set_qos(prefetch_count=1)
77
+ return await channel.declare_queue(
78
+ queue_name,
79
+ durable=True,
80
+ arguments={
81
+ "x-dead-letter-exchange": cls.DEAD_LETTER_EXCHANGE,
82
+ "x-dead-letter-routing-key": cls.DEAD_LETTER_EXCHANGE,
83
+ },
84
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: jararaca
3
- Version: 0.2.37a12
3
+ Version: 0.3.0
4
4
  Summary: A simple and fast API framework for Python
5
5
  Author: Lucas S
6
6
  Author-email: me@luscasleo.dev
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.13
12
12
  Provides-Extra: docs
13
13
  Provides-Extra: http
14
14
  Provides-Extra: opentelemetry
15
+ Provides-Extra: watch
15
16
  Requires-Dist: aio-pika (>=9.4.3,<10.0.0)
16
17
  Requires-Dist: croniter (>=3.0.3,<4.0.0)
17
18
  Requires-Dist: fastapi (>=0.113.0,<0.114.0)
@@ -27,6 +28,7 @@ Requires-Dist: types-croniter (>=3.0.3.20240731,<4.0.0.0)
27
28
  Requires-Dist: types-redis (>=4.6.0.20240903,<5.0.0.0)
28
29
  Requires-Dist: uvicorn (>=0.30.6,<0.31.0)
29
30
  Requires-Dist: uvloop (>=0.20.0,<0.21.0)
31
+ Requires-Dist: watchdog (>=3.0.0,<4.0.0) ; extra == "watch"
30
32
  Requires-Dist: websockets (>=13.0.1,<14.0.0)
31
33
  Project-URL: Repository, https://github.com/LuscasLeo/jararaca
32
34
  Description-Content-Type: text/markdown
@@ -1,6 +1,9 @@
1
- jararaca/__init__.py,sha256=VBrN25GHJ3gDG95CcJWe3dmGcA-X2agzOCIBbjzc1Iw,15312
1
+ jararaca/__init__.py,sha256=4WO-Th3vY1_0SKdLyviZN2rftiX7R5yPVNPGJ9Mf5Y0,15633
2
2
  jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
3
- jararaca/cli.py,sha256=K6df8fqEc4Qj-yGz5hmO3pSgFWxcLP4iciOP4LHida4,7979
3
+ jararaca/broker_backend/__init__.py,sha256=GzEIuHR1xzgCJD4FE3harNjoaYzxHMHoEL0_clUaC-k,3528
4
+ jararaca/broker_backend/mapper.py,sha256=vTsi7sWpNvlga1PWPFg0rCJ5joJ0cdzykkIc2Tuvenc,696
5
+ jararaca/broker_backend/redis_broker_backend.py,sha256=a7DHchy3NAiD71Ix8SwmQOUnniu7uup-Woa4ON_4J7I,5786
6
+ jararaca/cli.py,sha256=8opY0_YOKGaglDxIg9ijTWKcTvgHlBY9x3xiJGEo1xk,8964
4
7
  jararaca/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
8
  jararaca/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
9
  jararaca/core/providers.py,sha256=wktH84FK7c1s2wNq-fudf1uMfi3CQBR0neU2czJ_L0U,434
@@ -8,15 +11,18 @@ jararaca/core/uow.py,sha256=WrA50VWzfJIyZHeUhSs8IOpSA4T-D8VV6YPLlFTF5V4,2026
8
11
  jararaca/di.py,sha256=h3IsXdYZjJj8PJfnEDn0ZAwdd4EBfh8jU-wWO8ko_t4,76
9
12
  jararaca/files/entity.py.mako,sha256=tjQ-1udAMvVqgRokhsrR4uy3P_OVnbk3XZ8X69ixWhE,3098
10
13
  jararaca/lifecycle.py,sha256=qKlzLQQioS8QkxNJ_FC_5WbmT77cNbc_S7OcQeOoHkI,1895
11
- jararaca/messagebus/__init__.py,sha256=Zdl74HcS9K0FW6XUt7bVvaHEyxL8pWsqqakeRENIn0w,54
14
+ jararaca/messagebus/__init__.py,sha256=5jAqPqdcEMYBfQyfZDWPnplYdrfMyJLMcacf3qLyUhk,56
12
15
  jararaca/messagebus/bus_message_controller.py,sha256=Xd_qwnX5jUvgBTCarHR36fvtol9lPTsYp2IIGKyQQaE,1487
13
- jararaca/messagebus/decorators.py,sha256=GHlaXRuHtrz6R0HgcG2gJybpGYtdts9meDVSRPwN74I,4245
16
+ jararaca/messagebus/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ jararaca/messagebus/decorators.py,sha256=y-w4dWbP9ZW3ZJ4mE9iIaxw01ZC5snEbOuBY5NC-Bn0,5626
14
18
  jararaca/messagebus/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=BPH5wOlj_CyHtJ7W4NWF2h0gYMwzOPNzFhGADk618N4,4373
16
- jararaca/messagebus/publisher.py,sha256=5ay9Znwybqt981OOykdWkFisSvGiTeTpPXDFLMnaiqg,1109
17
- jararaca/messagebus/types.py,sha256=iYLyLxWqOHkDadxyMqQPWy3itLNQfvD6oQe8jcq9nzo,887
18
- jararaca/messagebus/worker.py,sha256=_X2Ctj7bI9XjCMvQMy8jM3dEE3CGnqgrU_B3lTGfmmQ,13605
19
- jararaca/microservice.py,sha256=1TvDKVMMREH27Ly8eTEheMmSfro4_Az_JKM_NdDvrgc,6636
19
+ jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=1pSaifCT6TnuKWH7u80llRcUvyFlS-G9p1AU83CxGuA,5315
20
+ jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=fQFFW9hH6ZU3UOyR7kMPrNp9wA71qEy5XlgrBQdBMS4,1230
21
+ jararaca/messagebus/message.py,sha256=U6cyd2XknX8mtm0333slz5fanky2PFLWCmokAO56vvU,819
22
+ jararaca/messagebus/publisher.py,sha256=K7WsOMVTyLmdms3ZKEshqrQc_DhNreiFK-HnmOT9Ce0,1965
23
+ jararaca/messagebus/worker.py,sha256=SGd7Mng33ivcaAP7cwmF_2loQPuHIfTwkCZtCNJ5G-I,13523
24
+ jararaca/messagebus/worker_v2.py,sha256=F7Hl92h5PezxNUY3_lfv9jNhboyQouuk6hXocPGeyXY,20341
25
+ jararaca/microservice.py,sha256=C_Txqm3xSmdHIghJigKk6TOycL5eTJv9PF6XP7TA8j4,6638
20
26
  jararaca/observability/decorators.py,sha256=XffBinFXdiNkY6eo8_1nkr_GapM0RUGBg0aicBIelag,2220
21
27
  jararaca/observability/interceptor.py,sha256=GHkuGKFWftN7MDjvYeGFGEPnuJETNhtxRK6yuPrCrpU,1462
22
28
  jararaca/observability/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -49,15 +55,19 @@ jararaca/rpc/http/backends/otel.py,sha256=Uc6CjHSCZ5hvnK1fNFv3ota5xzUFnvIl1JOpG3
49
55
  jararaca/rpc/http/decorators.py,sha256=oUSzgMGI8w6SoKiz3GltDbd3BWAuyY60F23cdRRNeiw,11897
50
56
  jararaca/rpc/http/httpx.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
57
  jararaca/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- jararaca/scheduler/decorators.py,sha256=V6VtLA44HPEbevEcEGiHDHM3HZvka1P5kpvIm9nCMkc,2268
53
- jararaca/scheduler/scheduler.py,sha256=r1_OPTOsL9WkxTLBaxLiq1csujUqepvPg-8I4m28amU,4845
58
+ jararaca/scheduler/decorators.py,sha256=cRwnQHtlo9iC-2c30t7GGTEURFmdtDPkkVrr5cRVblM,3645
59
+ jararaca/scheduler/scheduler.py,sha256=ll3ifOZ9QZcyCX1jfuNagHMVAbhRoE9VAl0ox3MWnwo,5295
60
+ jararaca/scheduler/scheduler_v2.py,sha256=5-_O6EzG95y_3aIw5SA4W_ppbPIIszBrCYM7cFvXgBo,11166
61
+ jararaca/scheduler/types.py,sha256=4HEQOmVIDp-BYLSzqmqSFIio1bd51WFmgFPIzPpVu04,135
54
62
  jararaca/tools/app_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
63
  jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaNXMp8fd1Ndk,941
56
64
  jararaca/tools/app_config/interceptor.py,sha256=nfFZiS80hrbnL7-XEYrwmp2rwaVYBqxvqu3Y-6o_ov4,2575
57
65
  jararaca/tools/metadata.py,sha256=7nlCDYgItNybentPSSCc2MLqN7IpBd0VyQzfjfQycVI,1402
58
66
  jararaca/tools/typescript/interface_parser.py,sha256=4SHt094P-QawMFHSyMCiujQf8Niw7xACIO1RHBM8-w4,29192
59
- jararaca-0.2.37a12.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
- jararaca-0.2.37a12.dist-info/METADATA,sha256=W8aHUpqo5Q5iGBwX0BE4CS9MfEEMN97vM-YRuCJ2URM,4873
61
- jararaca-0.2.37a12.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
62
- jararaca-0.2.37a12.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
63
- jararaca-0.2.37a12.dist-info/RECORD,,
67
+ jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
+ jararaca/utils/rabbitmq_utils.py,sha256=zFqZE-j6TSWFOEPbkIaB2hy2sqsXup-5421jIiPLfXY,2543
69
+ jararaca-0.3.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
70
+ jararaca-0.3.0.dist-info/METADATA,sha256=szUO6pqKIKDGAmJC1ppYnRXm-xVr2hoT49yCqLmf4o0,4951
71
+ jararaca-0.3.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
72
+ jararaca-0.3.0.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
73
+ jararaca-0.3.0.dist-info/RECORD,,
@@ -1,30 +0,0 @@
1
- from typing import ClassVar, Generic, Literal, Protocol, TypeVar
2
-
3
- from pydantic import BaseModel
4
-
5
- from jararaca.messagebus.publisher import use_publisher
6
-
7
-
8
- class Message(BaseModel):
9
- """
10
- Base class for messages representing tasks.
11
- A Task is a message that represents a unit of work to be done.
12
- It is published to a TaskPublisher and consumed by a TaskHandler, wrapped in TaskData.
13
- Note: A Task is not an Event.
14
- """
15
-
16
- MESSAGE_TOPIC: ClassVar[str] = "__unset__"
17
-
18
- MESSAGE_TYPE: ClassVar[Literal["task", "event"]] = "task"
19
-
20
- async def publish(self) -> None:
21
- task_publisher = use_publisher()
22
- await task_publisher.publish(self, self.MESSAGE_TOPIC)
23
-
24
-
25
- INHERITS_MESSAGE_CO = TypeVar("INHERITS_MESSAGE_CO", bound=Message, covariant=True)
26
-
27
-
28
- class MessageOf(Protocol, Generic[INHERITS_MESSAGE_CO]):
29
-
30
- def payload(self) -> INHERITS_MESSAGE_CO: ...