jararaca 0.3.11a6__py3-none-any.whl → 0.3.11a8__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.

jararaca/cli.py CHANGED
@@ -17,6 +17,7 @@ from mako.template import Template
17
17
 
18
18
  from jararaca.messagebus import worker as worker_v1
19
19
  from jararaca.messagebus import worker_v2 as worker_v2_mod
20
+ from jararaca.messagebus.decorators import SCHEDULED_ACTION_DATA_SET
20
21
  from jararaca.microservice import Microservice
21
22
  from jararaca.presentation.http_microservice import HttpMicroservice
22
23
  from jararaca.presentation.server import create_http_server
@@ -75,6 +76,8 @@ async def declare_worker_infrastructure(
75
76
  exchange: str,
76
77
  app: Microservice,
77
78
  passive_declare: bool = False,
79
+ handler_names: set[str] | None = None,
80
+ force: bool = False,
78
81
  ) -> None:
79
82
  """
80
83
  Declare the infrastructure (exchanges and queues) for worker v1.
@@ -82,7 +85,12 @@ async def declare_worker_infrastructure(
82
85
  connection = await aio_pika.connect(url)
83
86
  channel = await connection.channel()
84
87
 
85
- await channel.set_qos(prefetch_count=1)
88
+ # Force delete infrastructure if requested
89
+ if force:
90
+ click.echo(f"→ Force deleting existing infrastructure for exchange: {exchange}")
91
+ await RabbitmqUtils.delete_exchange(channel, exchange)
92
+ await RabbitmqUtils.delete_exchange(channel, RabbitmqUtils.DEAD_LETTER_EXCHANGE)
93
+ await RabbitmqUtils.delete_queue(channel, RabbitmqUtils.DEAD_LETTER_QUEUE)
86
94
 
87
95
  # Declare main exchange
88
96
  main_ex = await RabbitmqUtils.declare_main_exchange(
@@ -112,17 +120,27 @@ async def declare_worker_infrastructure(
112
120
  handlers, _ = factory(instance)
113
121
 
114
122
  for handler in handlers:
123
+ # Filter handlers by name if specified
124
+ if handler_names is not None and handler.spec.name is not None:
125
+ if handler.spec.name not in handler_names:
126
+ continue
127
+ elif handler_names is not None and handler.spec.name is None:
128
+ # Skip handlers without names when filtering is requested
129
+ continue
130
+
115
131
  queue_name = f"{handler.message_type.MESSAGE_TOPIC}.{handler.instance_callable.__module__}.{handler.instance_callable.__qualname__}"
116
132
  routing_key = f"{handler.message_type.MESSAGE_TOPIC}.#"
117
133
 
118
- queue = await channel.declare_queue(
134
+ # Force delete queue if requested
135
+ if force:
136
+ await RabbitmqUtils.delete_queue(channel, queue_name)
137
+
138
+ queue = await RabbitmqUtils.declare_worker_v1_queue(
139
+ channel=channel,
140
+ queue_name=queue_name,
141
+ dlx_name=dlx.name,
142
+ dlq_name=dlq.name,
119
143
  passive=passive_declare,
120
- name=queue_name,
121
- arguments={
122
- "x-dead-letter-exchange": dlx.name,
123
- "x-dead-letter-routing-key": dlq.name,
124
- },
125
- durable=True,
126
144
  )
127
145
 
128
146
  await queue.bind(exchange=main_ex, routing_key=routing_key)
@@ -136,6 +154,8 @@ async def declare_worker_v2_infrastructure(
136
154
  broker_url: str,
137
155
  app: Microservice,
138
156
  passive_declare: bool = False,
157
+ handler_names: set[str] | None = None,
158
+ force: bool = False,
139
159
  ) -> None:
140
160
  """
141
161
  Declare the infrastructure (exchanges and queues) for worker v2.
@@ -159,7 +179,12 @@ async def declare_worker_v2_infrastructure(
159
179
  connection = await aio_pika.connect(broker_url)
160
180
  channel = await connection.channel()
161
181
 
162
- await channel.set_qos(prefetch_count=1)
182
+ # Force delete infrastructure if requested
183
+ if force:
184
+ click.echo(f"→ Force deleting existing infrastructure for exchange: {exchange}")
185
+ await RabbitmqUtils.delete_exchange(channel, exchange)
186
+ await RabbitmqUtils.delete_exchange(channel, RabbitmqUtils.DEAD_LETTER_EXCHANGE)
187
+ await RabbitmqUtils.delete_queue(channel, RabbitmqUtils.DEAD_LETTER_QUEUE)
163
188
 
164
189
  # Declare main exchange
165
190
  await RabbitmqUtils.declare_main_exchange(
@@ -192,9 +217,21 @@ async def declare_worker_v2_infrastructure(
192
217
 
193
218
  # Declare queues for message handlers
194
219
  for handler in handlers:
220
+ # Filter handlers by name if specified
221
+ if handler_names is not None and handler.spec.name is not None:
222
+ if handler.spec.name not in handler_names:
223
+ continue
224
+ elif handler_names is not None and handler.spec.name is None:
225
+ # Skip handlers without names when filtering is requested
226
+ continue
227
+
195
228
  queue_name = f"{handler.message_type.MESSAGE_TOPIC}.{handler.instance_callable.__module__}.{handler.instance_callable.__qualname__}"
196
229
  routing_key = f"{handler.message_type.MESSAGE_TOPIC}.#"
197
230
 
231
+ # Force delete queue if requested
232
+ if force:
233
+ await RabbitmqUtils.delete_queue(channel, queue_name)
234
+
198
235
  queue = await RabbitmqUtils.declare_queue(
199
236
  channel=channel, queue_name=queue_name, passive=passive_declare
200
237
  )
@@ -208,6 +245,10 @@ async def declare_worker_v2_infrastructure(
208
245
  queue_name = f"{scheduled_action.callable.__module__}.{scheduled_action.callable.__qualname__}"
209
246
  routing_key = queue_name
210
247
 
248
+ # Force delete queue if requested
249
+ if force:
250
+ await RabbitmqUtils.delete_queue(channel, queue_name)
251
+
211
252
  queue = await RabbitmqUtils.declare_queue(
212
253
  channel=channel, queue_name=queue_name, passive=passive_declare
213
254
  )
@@ -224,6 +265,8 @@ async def declare_scheduler_v2_infrastructure(
224
265
  broker_url: str,
225
266
  app: Microservice,
226
267
  passive_declare: bool = False,
268
+ scheduler_names: set[str] | None = None,
269
+ force: bool = False,
227
270
  ) -> None:
228
271
  """
229
272
  Declare the infrastructure (exchanges and queues) for scheduler v2.
@@ -249,7 +292,9 @@ async def declare_scheduler_v2_infrastructure(
249
292
  connection = await aio_pika.connect(broker_url)
250
293
  channel = await connection.channel()
251
294
 
252
- await channel.set_qos(prefetch_count=1)
295
+ # Force delete exchange if requested
296
+ if force:
297
+ await RabbitmqUtils.delete_exchange(channel, exchange)
253
298
 
254
299
  # Declare exchange for scheduler
255
300
  await channel.declare_exchange(
@@ -275,13 +320,29 @@ async def declare_scheduler_v2_infrastructure(
275
320
  instance: Any = container.get_by_type(instance_type)
276
321
  factory = controller.get_messagebus_factory()
277
322
  _, actions = factory(instance)
323
+
324
+ # Filter scheduled actions by name if specified
325
+ if scheduler_names is not None:
326
+ filtered_actions: SCHEDULED_ACTION_DATA_SET = set()
327
+ for action in actions:
328
+ # Include actions that have a name and it's in the provided set
329
+ if action.spec.name and action.spec.name in scheduler_names:
330
+ filtered_actions.add(action)
331
+ # Skip actions without names when filtering is active
332
+ actions = filtered_actions
333
+
278
334
  scheduled_actions.extend(actions)
279
335
 
280
336
  for scheduled_action in scheduled_actions:
281
337
  queue_name = ScheduledAction.get_function_id(scheduled_action.callable)
282
- queue = await channel.declare_queue(
283
- name=queue_name,
284
- durable=True,
338
+
339
+ # Force delete queue if requested
340
+ if force:
341
+ await RabbitmqUtils.delete_queue(channel, queue_name)
342
+
343
+ queue = await RabbitmqUtils.declare_scheduler_queue(
344
+ channel=channel,
345
+ queue_name=queue_name,
285
346
  passive=passive_declare,
286
347
  )
287
348
  await queue.bind(
@@ -334,6 +395,11 @@ def cli() -> None:
334
395
  is_flag=True,
335
396
  default=False,
336
397
  )
398
+ @click.option(
399
+ "--handlers",
400
+ type=str,
401
+ help="Comma-separated list of handler names to listen to. If not specified, all handlers will be used.",
402
+ )
337
403
  def worker(
338
404
  app_path: str,
339
405
  url: str,
@@ -342,6 +408,7 @@ def worker(
342
408
  exchange: str,
343
409
  prefetch_count: int,
344
410
  passive_declare: bool,
411
+ handlers: str | None,
345
412
  ) -> None:
346
413
 
347
414
  app = find_microservice_by_module_path(app_path)
@@ -368,6 +435,11 @@ def worker(
368
435
 
369
436
  url = parsed_url.geturl()
370
437
 
438
+ # Parse handler names if provided
439
+ handler_names: set[str] | None = None
440
+ if handlers:
441
+ handler_names = {name.strip() for name in handlers.split(",") if name.strip()}
442
+
371
443
  config = worker_v1.AioPikaWorkerConfig(
372
444
  url=url,
373
445
  exchange=exchange,
@@ -376,6 +448,7 @@ def worker(
376
448
 
377
449
  worker_v1.MessageBusWorker(app, config=config).start_sync(
378
450
  passive_declare=passive_declare,
451
+ handler_names=handler_names,
379
452
  )
380
453
 
381
454
 
@@ -395,14 +468,27 @@ def worker(
395
468
  type=str,
396
469
  envvar="BACKEND_URL",
397
470
  )
398
- def worker_v2(app_path: str, broker_url: str, backend_url: str) -> None:
471
+ @click.option(
472
+ "--handlers",
473
+ type=str,
474
+ help="Comma-separated list of handler names to listen to. If not specified, all handlers will be used.",
475
+ )
476
+ def worker_v2(
477
+ app_path: str, broker_url: str, backend_url: str, handlers: str | None
478
+ ) -> None:
399
479
 
400
480
  app = find_microservice_by_module_path(app_path)
401
481
 
482
+ # Parse handler names if provided
483
+ handler_names: set[str] | None = None
484
+ if handlers:
485
+ handler_names = {name.strip() for name in handlers.split(",") if name.strip()}
486
+
402
487
  worker_v2_mod.MessageBusWorker(
403
488
  app=app,
404
489
  broker_url=broker_url,
405
490
  backend_url=backend_url,
491
+ handler_names=handler_names,
406
492
  ).start_sync()
407
493
 
408
494
 
@@ -455,13 +541,26 @@ def server(app_path: str, host: str, port: int) -> None:
455
541
  type=int,
456
542
  default=1,
457
543
  )
544
+ @click.option(
545
+ "--schedulers",
546
+ type=str,
547
+ help="Comma-separated list of scheduler names to run (only run schedulers with these names)",
548
+ )
458
549
  def scheduler(
459
550
  app_path: str,
460
551
  interval: int,
552
+ schedulers: str | None = None,
461
553
  ) -> None:
462
554
  app = find_microservice_by_module_path(app_path)
463
555
 
464
- Scheduler(app, interval=interval).run()
556
+ # Parse scheduler names if provided
557
+ scheduler_names: set[str] | None = None
558
+ if schedulers:
559
+ scheduler_names = {
560
+ name.strip() for name in schedulers.split(",") if name.strip()
561
+ }
562
+
563
+ Scheduler(app, interval=interval, scheduler_names=scheduler_names).run()
465
564
 
466
565
 
467
566
  @cli.command()
@@ -485,19 +584,34 @@ def scheduler(
485
584
  type=str,
486
585
  required=True,
487
586
  )
587
+ @click.option(
588
+ "--schedulers",
589
+ type=str,
590
+ help="Comma-separated list of scheduler names to run (only run schedulers with these names)",
591
+ )
488
592
  def scheduler_v2(
489
593
  interval: int,
490
594
  broker_url: str,
491
595
  backend_url: str,
492
596
  app_path: str,
597
+ schedulers: str | None = None,
493
598
  ) -> None:
494
599
 
495
600
  app = find_microservice_by_module_path(app_path)
601
+
602
+ # Parse scheduler names if provided
603
+ scheduler_names: set[str] | None = None
604
+ if schedulers:
605
+ scheduler_names = {
606
+ name.strip() for name in schedulers.split(",") if name.strip()
607
+ }
608
+
496
609
  scheduler = SchedulerV2(
497
610
  app=app,
498
611
  interval=interval,
499
612
  backend_url=backend_url,
500
613
  broker_url=broker_url,
614
+ scheduler_names=scheduler_names,
501
615
  )
502
616
  scheduler.run()
503
617
 
@@ -746,11 +860,24 @@ def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
746
860
  default=False,
747
861
  help="Use passive declarations (check if infrastructure exists without creating it)",
748
862
  )
863
+ @click.option(
864
+ "--handlers",
865
+ type=str,
866
+ help="Comma-separated list of handler names to declare queues for. If not specified, all handlers will be declared.",
867
+ )
868
+ @click.option(
869
+ "--force",
870
+ is_flag=True,
871
+ default=False,
872
+ help="Force recreation by deleting existing exchanges and queues before declaring them",
873
+ )
749
874
  def declare_queues_v1(
750
875
  app_path: str,
751
876
  broker_url: str | None,
752
877
  exchange: str,
753
878
  passive_declare: bool,
879
+ handlers: str | None,
880
+ force: bool,
754
881
  ) -> None:
755
882
  """
756
883
  Declare RabbitMQ infrastructure (exchanges and queues) for worker v1.
@@ -785,13 +912,20 @@ def declare_queues_v1(
785
912
  )
786
913
  return
787
914
 
915
+ # Parse handler names if provided
916
+ handler_names: set[str] | None = None
917
+ if handlers:
918
+ handler_names = {
919
+ name.strip() for name in handlers.split(",") if name.strip()
920
+ }
921
+
788
922
  click.echo(
789
923
  f"→ Declaring worker v1 infrastructure (URL: {broker_url}, Exchange: {exchange})"
790
924
  )
791
925
 
792
926
  try:
793
927
  await declare_worker_infrastructure(
794
- broker_url, exchange, app, passive_declare
928
+ broker_url, exchange, app, passive_declare, handler_names, force
795
929
  )
796
930
  click.echo("✓ Worker v1 infrastructure declared successfully!")
797
931
  except Exception as e:
@@ -827,11 +961,30 @@ def declare_queues_v1(
827
961
  default=False,
828
962
  help="Use passive declarations (check if infrastructure exists without creating it)",
829
963
  )
964
+ @click.option(
965
+ "--handlers",
966
+ type=str,
967
+ help="Comma-separated list of handler names to declare queues for. If not specified, all handlers will be declared.",
968
+ )
969
+ @click.option(
970
+ "--schedulers",
971
+ type=str,
972
+ help="Comma-separated list of scheduler names to declare queues for. If not specified, all schedulers will be declared.",
973
+ )
974
+ @click.option(
975
+ "--force",
976
+ is_flag=True,
977
+ default=False,
978
+ help="Force recreation by deleting existing exchanges and queues before declaring them",
979
+ )
830
980
  def declare_queues_v2(
831
981
  app_path: str,
832
982
  broker_url: str | None,
833
983
  exchange: str,
834
984
  passive_declare: bool,
985
+ handlers: str | None,
986
+ schedulers: str | None,
987
+ force: bool,
835
988
  ) -> None:
836
989
  """
837
990
  Declare RabbitMQ infrastructure (exchanges and queues) for worker v2 and scheduler v2.
@@ -866,6 +1019,20 @@ def declare_queues_v2(
866
1019
  )
867
1020
  return
868
1021
 
1022
+ # Parse handler names if provided
1023
+ handler_names: set[str] | None = None
1024
+ if handlers:
1025
+ handler_names = {
1026
+ name.strip() for name in handlers.split(",") if name.strip()
1027
+ }
1028
+
1029
+ # Parse scheduler names if provided
1030
+ scheduler_names: set[str] | None = None
1031
+ if schedulers:
1032
+ scheduler_names = {
1033
+ name.strip() for name in schedulers.split(",") if name.strip()
1034
+ }
1035
+
869
1036
  # For v2, create the broker URL with exchange parameter
870
1037
  v2_broker_url = f"{broker_url}?exchange={exchange}"
871
1038
 
@@ -874,9 +1041,11 @@ def declare_queues_v2(
874
1041
 
875
1042
  try:
876
1043
  await asyncio.gather(
877
- declare_worker_v2_infrastructure(v2_broker_url, app, passive_declare),
1044
+ declare_worker_v2_infrastructure(
1045
+ v2_broker_url, app, passive_declare, handler_names, force
1046
+ ),
878
1047
  declare_scheduler_v2_infrastructure(
879
- v2_broker_url, app, passive_declare
1048
+ v2_broker_url, app, passive_declare, scheduler_names, force
880
1049
  ),
881
1050
  )
882
1051
  click.echo(
@@ -24,6 +24,7 @@ class MessageHandler(Generic[INHERITS_MESSAGE_CO]):
24
24
  exception_handler: Callable[[BaseException], None] | None = None,
25
25
  nack_on_exception: bool = False,
26
26
  auto_ack: bool = True,
27
+ name: str | None = None,
27
28
  ) -> None:
28
29
  self.message_type = message
29
30
 
@@ -31,6 +32,7 @@ class MessageHandler(Generic[INHERITS_MESSAGE_CO]):
31
32
  self.exception_handler = exception_handler
32
33
  self.requeue_on_exception = nack_on_exception
33
34
  self.auto_ack = auto_ack
35
+ self.name = name
34
36
 
35
37
  def __call__(
36
38
  self, func: Callable[[Any, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]]
@@ -118,13 +118,14 @@ class AioPikaMicroserviceConsumer:
118
118
 
119
119
  self.incoming_map[queue_name] = handler
120
120
 
121
- queue: aio_pika.abc.AbstractQueue = await channel.declare_queue(
122
- passive=passive_declare,
123
- name=queue_name,
124
- arguments={
125
- "x-dead-letter-exchange": dlx.name,
126
- "x-dead-letter-routing-key": dlq.name,
127
- },
121
+ queue: aio_pika.abc.AbstractQueue = (
122
+ await RabbitmqUtils.declare_worker_v1_queue(
123
+ channel=channel,
124
+ queue_name=queue_name,
125
+ dlx_name=dlx.name,
126
+ dlq_name=dlq.name,
127
+ passive=passive_declare,
128
+ )
128
129
  )
129
130
 
130
131
  await queue.bind(exchange=main_ex, routing_key=routing_key)
@@ -337,7 +338,9 @@ class MessageBusWorker:
337
338
  raise RuntimeError("Consumer not started")
338
339
  return self._consumer
339
340
 
340
- async def start_async(self, passive_declare: bool) -> None:
341
+ async def start_async(
342
+ self, passive_declare: bool, handler_names: set[str] | None = None
343
+ ) -> None:
341
344
  all_message_handlers_set: MESSAGE_HANDLER_DATA_SET = set()
342
345
  async with self.lifecycle():
343
346
  for instance_type in self.app.controllers:
@@ -356,6 +359,15 @@ class MessageBusWorker:
356
359
  for handler_data in handlers:
357
360
  message_type = handler_data.spec.message_type
358
361
  topic = message_type.MESSAGE_TOPIC
362
+
363
+ # Filter handlers by name if specified
364
+ if handler_names is not None and handler_data.spec.name is not None:
365
+ if handler_data.spec.name not in handler_names:
366
+ continue
367
+ elif handler_names is not None and handler_data.spec.name is None:
368
+ # Skip handlers without names when filtering is requested
369
+ continue
370
+
359
371
  if (
360
372
  topic in message_handler_data_map
361
373
  and message_type.MESSAGE_TYPE == "task"
@@ -376,7 +388,9 @@ class MessageBusWorker:
376
388
 
377
389
  await consumer.consume(passive_declare=passive_declare)
378
390
 
379
- def start_sync(self, passive_declare: bool) -> None:
391
+ def start_sync(
392
+ self, passive_declare: bool, handler_names: set[str] | None = None
393
+ ) -> None:
380
394
 
381
395
  def on_shutdown(loop: asyncio.AbstractEventLoop) -> None:
382
396
  logger.info("Shutting down")
@@ -386,7 +400,11 @@ class MessageBusWorker:
386
400
  runner.get_loop().add_signal_handler(
387
401
  signal.SIGINT, on_shutdown, runner.get_loop()
388
402
  )
389
- runner.run(self.start_async(passive_declare=passive_declare))
403
+ runner.run(
404
+ self.start_async(
405
+ passive_declare=passive_declare, handler_names=handler_names
406
+ )
407
+ )
390
408
 
391
409
 
392
410
  class AioPikaMessageBusController(BusMessageController):
@@ -515,10 +515,17 @@ async def none_context() -> AsyncGenerator[None, None]:
515
515
 
516
516
 
517
517
  class MessageBusWorker:
518
- def __init__(self, app: Microservice, broker_url: str, backend_url: str) -> None:
518
+ def __init__(
519
+ self,
520
+ app: Microservice,
521
+ broker_url: str,
522
+ backend_url: str,
523
+ handler_names: set[str] | None = None,
524
+ ) -> None:
519
525
  self.app = app
520
526
  self.backend_url = backend_url
521
527
  self.broker_url = broker_url
528
+ self.handler_names = handler_names
522
529
 
523
530
  self.container = Container(app)
524
531
  self.lifecycle = AppLifecycle(app, self.container)
@@ -555,6 +562,21 @@ class MessageBusWorker:
555
562
  for handler_data in handlers:
556
563
  message_type = handler_data.spec.message_type
557
564
  topic = message_type.MESSAGE_TOPIC
565
+
566
+ # Filter handlers by name if specified
567
+ if (
568
+ self.handler_names is not None
569
+ and handler_data.spec.name is not None
570
+ ):
571
+ if handler_data.spec.name not in self.handler_names:
572
+ continue
573
+ elif (
574
+ self.handler_names is not None
575
+ and handler_data.spec.name is None
576
+ ):
577
+ # Skip handlers without names when filtering is requested
578
+ continue
579
+
558
580
  if (
559
581
  topic in message_handler_data_map
560
582
  and message_type.MESSAGE_TYPE == "task"
@@ -20,6 +20,7 @@ class ScheduledAction:
20
20
  exclusive: bool = True,
21
21
  timeout: int | None = None,
22
22
  exception_handler: Callable[[BaseException], None] | None = None,
23
+ name: str | None = None,
23
24
  ) -> None:
24
25
  """
25
26
  :param cron: A string representing the cron expression for the scheduled action.
@@ -27,6 +28,7 @@ class ScheduledAction:
27
28
  :param exclusive: A boolean indicating if the scheduled action should be executed in one instance of the application. (Requires a distributed lock provided by a backend)
28
29
  :param exception_handler: A callable that will be called when an exception is raised during the execution of the scheduled action.
29
30
  :param timeout: An integer representing the timeout for the scheduled action in seconds. If the scheduled action takes longer than this time, it will be terminated.
31
+ :param name: An optional name for the scheduled action, used for filtering which actions to run.
30
32
  """
31
33
  self.cron = cron
32
34
  """
@@ -55,6 +57,11 @@ class ScheduledAction:
55
57
  If the scheduled action takes longer than this time, it will be terminated.
56
58
  """
57
59
 
60
+ self.name = name
61
+ """
62
+ An optional name for the scheduled action, used for filtering which actions to run.
63
+ """
64
+
58
65
  def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
59
66
  ScheduledAction.register(func, self)
60
67
  return func
@@ -34,7 +34,7 @@ class SchedulerConfig:
34
34
 
35
35
 
36
36
  def extract_scheduled_actions(
37
- app: Microservice, container: Container
37
+ app: Microservice, container: Container, scheduler_names: set[str] | None = None
38
38
  ) -> list[ScheduledActionData]:
39
39
  scheduled_actions: list[ScheduledActionData] = []
40
40
  for controllers in app.controllers:
@@ -42,6 +42,17 @@ def extract_scheduled_actions(
42
42
  controller_instance: Any = container.get_by_type(controllers)
43
43
 
44
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
+
45
56
  scheduled_actions.extend(controller_scheduled_actions)
46
57
 
47
58
  return scheduled_actions
@@ -59,10 +70,12 @@ class Scheduler:
59
70
  self,
60
71
  app: Microservice,
61
72
  interval: int,
73
+ scheduler_names: set[str] | None = None,
62
74
  ) -> None:
63
75
  self.app = app
64
76
 
65
77
  self.interval = interval
78
+ self.scheduler_names = scheduler_names
66
79
  self.container = Container(self.app)
67
80
  self.uow_provider = UnitOfWorkContextProvider(app, self.container)
68
81
 
@@ -146,7 +159,9 @@ class Scheduler:
146
159
  async def run_scheduled_actions() -> None:
147
160
 
148
161
  async with self.lifceycle():
149
- scheduled_actions = extract_scheduled_actions(self.app, self.container)
162
+ scheduled_actions = extract_scheduled_actions(
163
+ self.app, self.container, self.scheduler_names
164
+ )
150
165
 
151
166
  while True:
152
167
  for action in scheduled_actions:
@@ -31,12 +31,13 @@ from jararaca.scheduler.decorators import (
31
31
  get_type_scheduled_actions,
32
32
  )
33
33
  from jararaca.scheduler.types import DelayedMessageData
34
+ from jararaca.utils.rabbitmq_utils import RabbitmqUtils
34
35
 
35
36
  logger = logging.getLogger(__name__)
36
37
 
37
38
 
38
39
  def extract_scheduled_actions(
39
- app: Microservice, container: Container
40
+ app: Microservice, container: Container, scheduler_names: set[str] | None = None
40
41
  ) -> list[ScheduledActionData]:
41
42
  scheduled_actions: list[ScheduledActionData] = []
42
43
  for controllers in app.controllers:
@@ -44,6 +45,17 @@ def extract_scheduled_actions(
44
45
  controller_instance: Any = container.get_by_type(controllers)
45
46
 
46
47
  controller_scheduled_actions = get_type_scheduled_actions(controller_instance)
48
+
49
+ # Filter scheduled actions by name if scheduler_names is provided
50
+ if scheduler_names is not None:
51
+ filtered_actions = []
52
+ for action in controller_scheduled_actions:
53
+ # Include actions that have a name and it's in the provided set
54
+ if action.spec.name and action.spec.name in scheduler_names:
55
+ filtered_actions.append(action)
56
+ # Skip actions without names when filtering is active
57
+ controller_scheduled_actions = filtered_actions
58
+
47
59
  scheduled_actions.extend(controller_scheduled_actions)
48
60
 
49
61
  return scheduled_actions
@@ -179,8 +191,6 @@ class RabbitMQBrokerDispatcher(MessageBrokerDispatcher):
179
191
 
180
192
  async with self.channel_pool.acquire() as channel:
181
193
 
182
- await channel.set_qos(prefetch_count=1)
183
-
184
194
  await channel.declare_exchange(
185
195
  name=self.exchange,
186
196
  type="topic",
@@ -189,9 +199,10 @@ class RabbitMQBrokerDispatcher(MessageBrokerDispatcher):
189
199
  )
190
200
 
191
201
  for sched_act_data in scheduled_actions:
192
- queue = await channel.declare_queue(
193
- name=ScheduledAction.get_function_id(sched_act_data.callable),
194
- durable=True,
202
+ queue = await RabbitmqUtils.declare_scheduler_queue(
203
+ channel=channel,
204
+ queue_name=ScheduledAction.get_function_id(sched_act_data.callable),
205
+ passive=False,
195
206
  )
196
207
 
197
208
  await queue.bind(
@@ -228,6 +239,7 @@ class SchedulerV2:
228
239
  interval: int,
229
240
  broker_url: str,
230
241
  backend_url: str,
242
+ scheduler_names: set[str] | None = None,
231
243
  ) -> None:
232
244
  self.app = app
233
245
 
@@ -239,6 +251,7 @@ class SchedulerV2:
239
251
  )
240
252
 
241
253
  self.interval = interval
254
+ self.scheduler_names = scheduler_names
242
255
  self.container = Container(self.app)
243
256
  self.uow_provider = UnitOfWorkContextProvider(app, self.container)
244
257
 
@@ -264,7 +277,9 @@ class SchedulerV2:
264
277
  """
265
278
  async with self.lifecycle():
266
279
 
267
- scheduled_actions = extract_scheduled_actions(self.app, self.container)
280
+ scheduled_actions = extract_scheduled_actions(
281
+ self.app, self.container, self.scheduler_names
282
+ )
268
283
 
269
284
  await self.broker.initialize(scheduled_actions)
270
285
 
@@ -13,7 +13,7 @@ class RabbitmqUtils:
13
13
  """
14
14
  Declare a Dead Letter Exchange (DLX) for the given channel.
15
15
  """
16
- await channel.set_qos(prefetch_count=1)
16
+
17
17
  return await channel.declare_exchange(
18
18
  cls.DEAD_LETTER_EXCHANGE,
19
19
  passive=passive,
@@ -29,7 +29,7 @@ class RabbitmqUtils:
29
29
  """
30
30
  Declare a Dead Letter Queue (DLQ) for the given queue.
31
31
  """
32
- await channel.set_qos(prefetch_count=1)
32
+
33
33
  return await channel.declare_queue(
34
34
  cls.DEAD_LETTER_QUEUE,
35
35
  durable=True,
@@ -61,7 +61,7 @@ class RabbitmqUtils:
61
61
  """
62
62
  Declare a main exchange for the given channel.
63
63
  """
64
- await channel.set_qos(prefetch_count=1)
64
+
65
65
  return await channel.declare_exchange(
66
66
  exchange_name,
67
67
  passive=passive,
@@ -80,7 +80,7 @@ class RabbitmqUtils:
80
80
  """
81
81
  Declare a queue with the given name and properties.
82
82
  """
83
- await channel.set_qos(prefetch_count=1)
83
+
84
84
  return await channel.declare_queue(
85
85
  queue_name,
86
86
  passive=passive,
@@ -90,3 +90,81 @@ class RabbitmqUtils:
90
90
  "x-dead-letter-routing-key": cls.DEAD_LETTER_EXCHANGE,
91
91
  },
92
92
  )
93
+
94
+ @classmethod
95
+ async def declare_worker_v1_queue(
96
+ cls,
97
+ channel: AbstractChannel,
98
+ queue_name: str,
99
+ dlx_name: str,
100
+ dlq_name: str,
101
+ passive: bool = False,
102
+ ) -> AbstractQueue:
103
+ """
104
+ Declare a worker v1 queue with custom dead letter exchange and routing key.
105
+ """
106
+ return await channel.declare_queue(
107
+ passive=passive,
108
+ name=queue_name,
109
+ arguments={
110
+ "x-dead-letter-exchange": dlx_name,
111
+ "x-dead-letter-routing-key": dlq_name,
112
+ },
113
+ durable=True,
114
+ )
115
+
116
+ @classmethod
117
+ async def declare_scheduler_queue(
118
+ cls,
119
+ channel: AbstractChannel,
120
+ queue_name: str,
121
+ passive: bool = False,
122
+ ) -> AbstractQueue:
123
+ """
124
+ Declare a scheduler queue with simple durable configuration.
125
+ """
126
+ return await channel.declare_queue(
127
+ name=queue_name,
128
+ durable=True,
129
+ passive=passive,
130
+ )
131
+
132
+ @classmethod
133
+ async def delete_queue(
134
+ cls,
135
+ channel: AbstractChannel,
136
+ queue_name: str,
137
+ if_unused: bool = False,
138
+ if_empty: bool = False,
139
+ ) -> None:
140
+ """
141
+ Delete a queue.
142
+ """
143
+ try:
144
+ await channel.queue_delete(
145
+ queue_name=queue_name,
146
+ if_unused=if_unused,
147
+ if_empty=if_empty,
148
+ )
149
+ except Exception:
150
+ # Queue might not exist, which is fine
151
+ pass
152
+
153
+ @classmethod
154
+ async def delete_exchange(
155
+ cls,
156
+ channel: AbstractChannel,
157
+ exchange_name: str,
158
+ if_unused: bool = False,
159
+ ) -> None:
160
+ """
161
+ Delete an exchange.
162
+ """
163
+ try:
164
+ await channel.exchange_delete(
165
+ exchange_name=exchange_name,
166
+ if_unused=if_unused,
167
+ )
168
+ except Exception:
169
+ # Exchange might not exist, which is fine
170
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: jararaca
3
- Version: 0.3.11a6
3
+ Version: 0.3.11a8
4
4
  Summary: A simple and fast API framework for Python
5
5
  Author: Lucas S
6
6
  Author-email: me@luscasleo.dev
@@ -3,7 +3,7 @@ jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
3
3
  jararaca/broker_backend/__init__.py,sha256=GzEIuHR1xzgCJD4FE3harNjoaYzxHMHoEL0_clUaC-k,3528
4
4
  jararaca/broker_backend/mapper.py,sha256=vTsi7sWpNvlga1PWPFg0rCJ5joJ0cdzykkIc2Tuvenc,696
5
5
  jararaca/broker_backend/redis_broker_backend.py,sha256=a7DHchy3NAiD71Ix8SwmQOUnniu7uup-Woa4ON_4J7I,5786
6
- jararaca/cli.py,sha256=y3PhnhSdzwE-F1OTK-VJetZfxosQyDkrtiGgZgLQGD8,24707
6
+ jararaca/cli.py,sha256=-fFnf67N7DFwEg-cfEjWj5qrIEf2hzrzHbLm6x3xpTw,30992
7
7
  jararaca/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  jararaca/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  jararaca/core/providers.py,sha256=wktH84FK7c1s2wNq-fudf1uMfi3CQBR0neU2czJ_L0U,434
@@ -14,14 +14,14 @@ jararaca/lifecycle.py,sha256=qKlzLQQioS8QkxNJ_FC_5WbmT77cNbc_S7OcQeOoHkI,1895
14
14
  jararaca/messagebus/__init__.py,sha256=5jAqPqdcEMYBfQyfZDWPnplYdrfMyJLMcacf3qLyUhk,56
15
15
  jararaca/messagebus/bus_message_controller.py,sha256=Xd_qwnX5jUvgBTCarHR36fvtol9lPTsYp2IIGKyQQaE,1487
16
16
  jararaca/messagebus/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- jararaca/messagebus/decorators.py,sha256=pNefkrfTFuhRQQzmfL7MSKcVa55b0TUHqEtKPunxoAA,5847
17
+ jararaca/messagebus/decorators.py,sha256=oKFAavGWDTLMQCg6NyWF8U5zpWNvccSZ8H2UVXlSRGE,5905
18
18
  jararaca/messagebus/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=_DEHwIH9LYsA26Hu1mo9oHzLZuATgjilU9E3o-ecDjs,6520
20
20
  jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=ojy1bRhqMgrkQljcGGS8cd8-8pUjL8ZHjIUkdmaAnNM,1325
21
21
  jararaca/messagebus/message.py,sha256=U6cyd2XknX8mtm0333slz5fanky2PFLWCmokAO56vvU,819
22
22
  jararaca/messagebus/publisher.py,sha256=JTkxdKbvxvDWT8nK8PVEyyX061vYYbKQMxRHXrZtcEY,2173
23
- jararaca/messagebus/worker.py,sha256=IMMI5NCVYnXmETFgFymkFJj-dGUHM0WQUZXNItfW5cY,14018
24
- jararaca/messagebus/worker_v2.py,sha256=M8bN_9L1mo_uzJDut90WgKv3okwrzfKg4jD6jUFcBvA,20904
23
+ jararaca/messagebus/worker.py,sha256=cj2Tn2l0kyc-NO6zn6C8yvT-sSi1aFZluXSLTUqfaAI,14701
24
+ jararaca/messagebus/worker_v2.py,sha256=QMw40YfOm3kCuuHlyGKSkx50aVrwBvhJRt5L4Twgvqw,21653
25
25
  jararaca/microservice.py,sha256=jTGzkoJcco1nXwZZDeHRwEHAu_Hpr5gxA_d51P49C2c,7915
26
26
  jararaca/observability/decorators.py,sha256=MOIr2PttPYYvRwEdfQZEwD5RxKHOTv8UEy9n1YQVoKw,2281
27
27
  jararaca/observability/interceptor.py,sha256=U4ZLM0f8j6Q7gMUKKnA85bnvD-Qa0ii79Qa_X8KsXAQ,1498
@@ -58,18 +58,18 @@ jararaca/rpc/http/backends/otel.py,sha256=Uc6CjHSCZ5hvnK1fNFv3ota5xzUFnvIl1JOpG3
58
58
  jararaca/rpc/http/decorators.py,sha256=oUSzgMGI8w6SoKiz3GltDbd3BWAuyY60F23cdRRNeiw,11897
59
59
  jararaca/rpc/http/httpx.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  jararaca/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- jararaca/scheduler/decorators.py,sha256=ZvOMzQT1ypU7xRLuhu6YARek1YFQ69kje2NdGjlppY8,4325
62
- jararaca/scheduler/scheduler.py,sha256=PjX40P09tAeD77Z0f4U1XkW782aDk_1GlUXoLaEWXe8,5446
63
- jararaca/scheduler/scheduler_v2.py,sha256=Ipwgsoirate3xwsoKD-Hd6vxZplSpYLgRulA41Xl-sA,11313
61
+ jararaca/scheduler/decorators.py,sha256=iyWFvPLCRh9c0YReQRemI2mLuaUv7r929So-xuKIWUs,4605
62
+ jararaca/scheduler/scheduler.py,sha256=1T7qKIOiU9WD2p0aJsiUW-MOArJZyUSMwpv_14ziTQM,6188
63
+ jararaca/scheduler/scheduler_v2.py,sha256=9o7GZRqLJj8WkBv4JLomqsDaVnrT2jeHGjN8K9YM-uA,12110
64
64
  jararaca/scheduler/types.py,sha256=4HEQOmVIDp-BYLSzqmqSFIio1bd51WFmgFPIzPpVu04,135
65
65
  jararaca/tools/app_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
66
  jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaNXMp8fd1Ndk,941
67
67
  jararaca/tools/app_config/interceptor.py,sha256=HV8h4AxqUc_ACs5do4BSVlyxlRXzx7HqJtoVO9tfRnQ,2611
68
68
  jararaca/tools/typescript/interface_parser.py,sha256=35xbOrZDQDyTXdMrVZQ8nnFw79f28lJuLYNHAspIqi8,30492
69
69
  jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
- jararaca/utils/rabbitmq_utils.py,sha256=FPDP8ZVgvitZXV-oK73D7EIANsqUzXTW7HdpEKsIsyI,2811
71
- jararaca-0.3.11a6.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
72
- jararaca-0.3.11a6.dist-info/METADATA,sha256=o7ABpst9rZU3RW_jFGPOE6CsJViab3luBBIWvv0jFlA,4997
73
- jararaca-0.3.11a6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
74
- jararaca-0.3.11a6.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
75
- jararaca-0.3.11a6.dist-info/RECORD,,
70
+ jararaca/utils/rabbitmq_utils.py,sha256=IeKRc5K3Ml6yWFmdcL7r99c9tgyeyjlr3o_ZWtDBT9M,4651
71
+ jararaca-0.3.11a8.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
72
+ jararaca-0.3.11a8.dist-info/METADATA,sha256=Twd_9r877lTkrkAvB77ihCU2gKIiR84pzwGViRvG770,4997
73
+ jararaca-0.3.11a8.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
74
+ jararaca-0.3.11a8.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
75
+ jararaca-0.3.11a8.dist-info/RECORD,,