jararaca 0.3.11a7__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,6 +85,13 @@ async def declare_worker_infrastructure(
82
85
  connection = await aio_pika.connect(url)
83
86
  channel = await connection.channel()
84
87
 
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)
94
+
85
95
  # Declare main exchange
86
96
  main_ex = await RabbitmqUtils.declare_main_exchange(
87
97
  channel=channel,
@@ -110,17 +120,27 @@ async def declare_worker_infrastructure(
110
120
  handlers, _ = factory(instance)
111
121
 
112
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
+
113
131
  queue_name = f"{handler.message_type.MESSAGE_TOPIC}.{handler.instance_callable.__module__}.{handler.instance_callable.__qualname__}"
114
132
  routing_key = f"{handler.message_type.MESSAGE_TOPIC}.#"
115
133
 
116
- 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,
117
143
  passive=passive_declare,
118
- name=queue_name,
119
- arguments={
120
- "x-dead-letter-exchange": dlx.name,
121
- "x-dead-letter-routing-key": dlq.name,
122
- },
123
- durable=True,
124
144
  )
125
145
 
126
146
  await queue.bind(exchange=main_ex, routing_key=routing_key)
@@ -134,6 +154,8 @@ async def declare_worker_v2_infrastructure(
134
154
  broker_url: str,
135
155
  app: Microservice,
136
156
  passive_declare: bool = False,
157
+ handler_names: set[str] | None = None,
158
+ force: bool = False,
137
159
  ) -> None:
138
160
  """
139
161
  Declare the infrastructure (exchanges and queues) for worker v2.
@@ -157,6 +179,13 @@ async def declare_worker_v2_infrastructure(
157
179
  connection = await aio_pika.connect(broker_url)
158
180
  channel = await connection.channel()
159
181
 
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)
188
+
160
189
  # Declare main exchange
161
190
  await RabbitmqUtils.declare_main_exchange(
162
191
  channel=channel,
@@ -188,9 +217,21 @@ async def declare_worker_v2_infrastructure(
188
217
 
189
218
  # Declare queues for message handlers
190
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
+
191
228
  queue_name = f"{handler.message_type.MESSAGE_TOPIC}.{handler.instance_callable.__module__}.{handler.instance_callable.__qualname__}"
192
229
  routing_key = f"{handler.message_type.MESSAGE_TOPIC}.#"
193
230
 
231
+ # Force delete queue if requested
232
+ if force:
233
+ await RabbitmqUtils.delete_queue(channel, queue_name)
234
+
194
235
  queue = await RabbitmqUtils.declare_queue(
195
236
  channel=channel, queue_name=queue_name, passive=passive_declare
196
237
  )
@@ -204,6 +245,10 @@ async def declare_worker_v2_infrastructure(
204
245
  queue_name = f"{scheduled_action.callable.__module__}.{scheduled_action.callable.__qualname__}"
205
246
  routing_key = queue_name
206
247
 
248
+ # Force delete queue if requested
249
+ if force:
250
+ await RabbitmqUtils.delete_queue(channel, queue_name)
251
+
207
252
  queue = await RabbitmqUtils.declare_queue(
208
253
  channel=channel, queue_name=queue_name, passive=passive_declare
209
254
  )
@@ -220,6 +265,8 @@ async def declare_scheduler_v2_infrastructure(
220
265
  broker_url: str,
221
266
  app: Microservice,
222
267
  passive_declare: bool = False,
268
+ scheduler_names: set[str] | None = None,
269
+ force: bool = False,
223
270
  ) -> None:
224
271
  """
225
272
  Declare the infrastructure (exchanges and queues) for scheduler v2.
@@ -245,6 +292,10 @@ async def declare_scheduler_v2_infrastructure(
245
292
  connection = await aio_pika.connect(broker_url)
246
293
  channel = await connection.channel()
247
294
 
295
+ # Force delete exchange if requested
296
+ if force:
297
+ await RabbitmqUtils.delete_exchange(channel, exchange)
298
+
248
299
  # Declare exchange for scheduler
249
300
  await channel.declare_exchange(
250
301
  name=exchange,
@@ -269,13 +320,29 @@ async def declare_scheduler_v2_infrastructure(
269
320
  instance: Any = container.get_by_type(instance_type)
270
321
  factory = controller.get_messagebus_factory()
271
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
+
272
334
  scheduled_actions.extend(actions)
273
335
 
274
336
  for scheduled_action in scheduled_actions:
275
337
  queue_name = ScheduledAction.get_function_id(scheduled_action.callable)
276
- queue = await channel.declare_queue(
277
- name=queue_name,
278
- 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,
279
346
  passive=passive_declare,
280
347
  )
281
348
  await queue.bind(
@@ -328,6 +395,11 @@ def cli() -> None:
328
395
  is_flag=True,
329
396
  default=False,
330
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
+ )
331
403
  def worker(
332
404
  app_path: str,
333
405
  url: str,
@@ -336,6 +408,7 @@ def worker(
336
408
  exchange: str,
337
409
  prefetch_count: int,
338
410
  passive_declare: bool,
411
+ handlers: str | None,
339
412
  ) -> None:
340
413
 
341
414
  app = find_microservice_by_module_path(app_path)
@@ -362,6 +435,11 @@ def worker(
362
435
 
363
436
  url = parsed_url.geturl()
364
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
+
365
443
  config = worker_v1.AioPikaWorkerConfig(
366
444
  url=url,
367
445
  exchange=exchange,
@@ -370,6 +448,7 @@ def worker(
370
448
 
371
449
  worker_v1.MessageBusWorker(app, config=config).start_sync(
372
450
  passive_declare=passive_declare,
451
+ handler_names=handler_names,
373
452
  )
374
453
 
375
454
 
@@ -389,14 +468,27 @@ def worker(
389
468
  type=str,
390
469
  envvar="BACKEND_URL",
391
470
  )
392
- 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:
393
479
 
394
480
  app = find_microservice_by_module_path(app_path)
395
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
+
396
487
  worker_v2_mod.MessageBusWorker(
397
488
  app=app,
398
489
  broker_url=broker_url,
399
490
  backend_url=backend_url,
491
+ handler_names=handler_names,
400
492
  ).start_sync()
401
493
 
402
494
 
@@ -449,13 +541,26 @@ def server(app_path: str, host: str, port: int) -> None:
449
541
  type=int,
450
542
  default=1,
451
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
+ )
452
549
  def scheduler(
453
550
  app_path: str,
454
551
  interval: int,
552
+ schedulers: str | None = None,
455
553
  ) -> None:
456
554
  app = find_microservice_by_module_path(app_path)
457
555
 
458
- 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()
459
564
 
460
565
 
461
566
  @cli.command()
@@ -479,19 +584,34 @@ def scheduler(
479
584
  type=str,
480
585
  required=True,
481
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
+ )
482
592
  def scheduler_v2(
483
593
  interval: int,
484
594
  broker_url: str,
485
595
  backend_url: str,
486
596
  app_path: str,
597
+ schedulers: str | None = None,
487
598
  ) -> None:
488
599
 
489
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
+
490
609
  scheduler = SchedulerV2(
491
610
  app=app,
492
611
  interval=interval,
493
612
  backend_url=backend_url,
494
613
  broker_url=broker_url,
614
+ scheduler_names=scheduler_names,
495
615
  )
496
616
  scheduler.run()
497
617
 
@@ -740,11 +860,24 @@ def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
740
860
  default=False,
741
861
  help="Use passive declarations (check if infrastructure exists without creating it)",
742
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
+ )
743
874
  def declare_queues_v1(
744
875
  app_path: str,
745
876
  broker_url: str | None,
746
877
  exchange: str,
747
878
  passive_declare: bool,
879
+ handlers: str | None,
880
+ force: bool,
748
881
  ) -> None:
749
882
  """
750
883
  Declare RabbitMQ infrastructure (exchanges and queues) for worker v1.
@@ -779,13 +912,20 @@ def declare_queues_v1(
779
912
  )
780
913
  return
781
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
+
782
922
  click.echo(
783
923
  f"→ Declaring worker v1 infrastructure (URL: {broker_url}, Exchange: {exchange})"
784
924
  )
785
925
 
786
926
  try:
787
927
  await declare_worker_infrastructure(
788
- broker_url, exchange, app, passive_declare
928
+ broker_url, exchange, app, passive_declare, handler_names, force
789
929
  )
790
930
  click.echo("✓ Worker v1 infrastructure declared successfully!")
791
931
  except Exception as e:
@@ -821,11 +961,30 @@ def declare_queues_v1(
821
961
  default=False,
822
962
  help="Use passive declarations (check if infrastructure exists without creating it)",
823
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
+ )
824
980
  def declare_queues_v2(
825
981
  app_path: str,
826
982
  broker_url: str | None,
827
983
  exchange: str,
828
984
  passive_declare: bool,
985
+ handlers: str | None,
986
+ schedulers: str | None,
987
+ force: bool,
829
988
  ) -> None:
830
989
  """
831
990
  Declare RabbitMQ infrastructure (exchanges and queues) for worker v2 and scheduler v2.
@@ -860,6 +1019,20 @@ def declare_queues_v2(
860
1019
  )
861
1020
  return
862
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
+
863
1036
  # For v2, create the broker URL with exchange parameter
864
1037
  v2_broker_url = f"{broker_url}?exchange={exchange}"
865
1038
 
@@ -868,9 +1041,11 @@ def declare_queues_v2(
868
1041
 
869
1042
  try:
870
1043
  await asyncio.gather(
871
- 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
+ ),
872
1047
  declare_scheduler_v2_infrastructure(
873
- v2_broker_url, app, passive_declare
1048
+ v2_broker_url, app, passive_declare, scheduler_names, force
874
1049
  ),
875
1050
  )
876
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
@@ -187,9 +199,10 @@ class RabbitMQBrokerDispatcher(MessageBrokerDispatcher):
187
199
  )
188
200
 
189
201
  for sched_act_data in scheduled_actions:
190
- queue = await channel.declare_queue(
191
- name=ScheduledAction.get_function_id(sched_act_data.callable),
192
- 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,
193
206
  )
194
207
 
195
208
  await queue.bind(
@@ -226,6 +239,7 @@ class SchedulerV2:
226
239
  interval: int,
227
240
  broker_url: str,
228
241
  backend_url: str,
242
+ scheduler_names: set[str] | None = None,
229
243
  ) -> None:
230
244
  self.app = app
231
245
 
@@ -237,6 +251,7 @@ class SchedulerV2:
237
251
  )
238
252
 
239
253
  self.interval = interval
254
+ self.scheduler_names = scheduler_names
240
255
  self.container = Container(self.app)
241
256
  self.uow_provider = UnitOfWorkContextProvider(app, self.container)
242
257
 
@@ -262,7 +277,9 @@ class SchedulerV2:
262
277
  """
263
278
  async with self.lifecycle():
264
279
 
265
- scheduled_actions = extract_scheduled_actions(self.app, self.container)
280
+ scheduled_actions = extract_scheduled_actions(
281
+ self.app, self.container, self.scheduler_names
282
+ )
266
283
 
267
284
  await self.broker.initialize(scheduled_actions)
268
285
 
@@ -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.11a7
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=YdIot8zcnRoW5_Qm4rn0Ow1kfjlgPCJwndG63vDzpaA,24572
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=x1snb4eaCmBqjAkAvAcqSNvgpQolXqf_zTfKRBK3bIM,11260
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=MFbXpScRxUyeZMfhB-o22PTFFF_SrU8Y09APaGYUtbo,2623
71
- jararaca-0.3.11a7.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
72
- jararaca-0.3.11a7.dist-info/METADATA,sha256=tNPHTsrnA-qsO31QGN1GzH5z8522sLFU2vlCrMDAe9o,4997
73
- jararaca-0.3.11a7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
74
- jararaca-0.3.11a7.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
75
- jararaca-0.3.11a7.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,,