jararaca 0.3.11a13__py3-none-any.whl → 0.3.11a15__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
@@ -20,7 +20,10 @@ from jararaca.messagebus.decorators import MessageBusController, MessageHandler
20
20
  from jararaca.microservice import Microservice
21
21
  from jararaca.presentation.http_microservice import HttpMicroservice
22
22
  from jararaca.presentation.server import create_http_server
23
- from jararaca.reflect.controller_inspect import inspect_controller
23
+ from jararaca.reflect.controller_inspect import (
24
+ ControllerMemberReflect,
25
+ inspect_controller,
26
+ )
24
27
  from jararaca.scheduler.beat_worker import BeatWorker
25
28
  from jararaca.scheduler.decorators import ScheduledAction
26
29
  from jararaca.tools.typescript.interface_parser import (
@@ -83,7 +86,6 @@ async def declare_worker_infrastructure(
83
86
  """
84
87
  Declare the infrastructure (exchanges and queues) for worker.
85
88
  """
86
-
87
89
  parsed_url = urlparse(broker_url)
88
90
  if parsed_url.scheme not in ["amqp", "amqps"]:
89
91
  raise ValueError(f"Unsupported broker URL scheme: {parsed_url.scheme}")
@@ -98,35 +100,161 @@ async def declare_worker_infrastructure(
98
100
 
99
101
  exchange = query_params["exchange"][0]
100
102
 
103
+ # Create a connection that will be used to create channels for each operation
101
104
  connection = await aio_pika.connect(broker_url)
102
- channel = await connection.channel()
103
105
 
104
- # Only force delete infrastructure if requested at the beginning
105
- if force or (interactive_mode and click.confirm(f"Delete existing infrastructure for exchange: {exchange}?")):
106
+ try:
107
+ # Step 1: Setup infrastructure (exchanges and dead letter queues)
108
+ # Creating a dedicated channel for infrastructure setup
109
+ await setup_infrastructure(connection, exchange, force, interactive_mode)
110
+
111
+ # Step 2: Declare all message handlers and scheduled actions queues
112
+ # Creating a dedicated channel for controller queues
113
+ await declare_controller_queues(
114
+ connection, app, exchange, force, interactive_mode
115
+ )
116
+ finally:
117
+ # Always close the connection in the finally block
118
+ await connection.close()
119
+
120
+
121
+ async def setup_infrastructure(
122
+ connection: aio_pika.abc.AbstractConnection,
123
+ exchange: str,
124
+ force: bool = False,
125
+ interactive_mode: bool = False,
126
+ ) -> None:
127
+ """
128
+ Setup the basic infrastructure (exchanges and dead letter queues).
129
+ """
130
+ # Check if infrastructure exists
131
+ infrastructure_exists = await check_infrastructure_exists(connection, exchange)
132
+
133
+ # If it exists and force or user confirms, delete it
134
+ if not infrastructure_exists and should_recreate_infrastructure(
135
+ force,
136
+ interactive_mode,
137
+ f"Existing infrastructure found for exchange '{exchange}'. Delete and recreate?",
138
+ ):
139
+ await delete_infrastructure(connection, exchange)
140
+
141
+ # Try to declare required infrastructure
142
+ await declare_infrastructure(connection, exchange, force, interactive_mode)
143
+
144
+
145
+ async def check_infrastructure_exists(
146
+ connection: aio_pika.abc.AbstractConnection, exchange: str
147
+ ) -> bool:
148
+ """
149
+ Check if the infrastructure exists by trying passive declarations.
150
+ """
151
+ # Create a dedicated channel for checking infrastructure existence using async context manager
152
+ async with connection.channel() as channel:
153
+ try:
154
+ await RabbitmqUtils.declare_main_exchange(
155
+ channel=channel,
156
+ exchange_name=exchange,
157
+ passive=True,
158
+ )
159
+ await RabbitmqUtils.declare_dl_exchange(channel=channel, passive=True)
160
+ await RabbitmqUtils.declare_dl_queue(channel=channel, passive=True)
161
+ return True
162
+ except Exception:
163
+ # Infrastructure doesn't exist, which is fine for fresh setup
164
+ return False
165
+
166
+
167
+ def should_recreate_infrastructure(
168
+ force: bool, interactive_mode: bool, confirmation_message: str
169
+ ) -> bool:
170
+ """
171
+ Determine if infrastructure should be recreated based on force flag and user input.
172
+ """
173
+ return force or (interactive_mode and click.confirm(confirmation_message))
174
+
175
+
176
+ async def delete_infrastructure(
177
+ connection: aio_pika.abc.AbstractConnection, exchange: str
178
+ ) -> None:
179
+ """
180
+ Delete existing infrastructure (exchanges and queues).
181
+ """
182
+ # Create a dedicated channel for deleting infrastructure using async context manager
183
+ async with connection.channel() as channel:
106
184
  click.echo(f"→ Deleting existing infrastructure for exchange: {exchange}")
107
185
  await RabbitmqUtils.delete_exchange(channel, exchange)
108
186
  await RabbitmqUtils.delete_exchange(channel, RabbitmqUtils.DEAD_LETTER_EXCHANGE)
109
187
  await RabbitmqUtils.delete_queue(channel, RabbitmqUtils.DEAD_LETTER_QUEUE)
110
188
 
111
- try:
112
- await RabbitmqUtils.declare_main_exchange(
113
- channel=channel,
114
- exchange_name=exchange,
115
- passive=False,
116
- )
117
189
 
118
- dlx = await RabbitmqUtils.declare_dl_exchange(channel=channel, passive=False)
119
- dlq = await RabbitmqUtils.declare_dl_queue(channel=channel, passive=False)
120
- await dlq.bind(dlx, routing_key=RabbitmqUtils.DEAD_LETTER_EXCHANGE)
121
- except Exception as e:
122
- click.echo(f"Error during exchange declaration: {e}")
123
- if force or (interactive_mode and click.confirm("Error occurred. Recreate infrastructure?")):
124
- await channel.close()
125
- await connection.close()
126
- raise
127
- click.echo("Skipping main exchange declaration due to error")
190
+ async def declare_infrastructure(
191
+ connection: aio_pika.abc.AbstractConnection,
192
+ exchange: str,
193
+ force: bool,
194
+ interactive_mode: bool,
195
+ ) -> None:
196
+ """
197
+ Declare the required infrastructure (exchanges and dead letter queues).
198
+ """
199
+ # Using async context manager for channel creation
200
+ async with connection.channel() as channel:
201
+ try:
202
+ # Declare main exchange
203
+ await RabbitmqUtils.declare_main_exchange(
204
+ channel=channel,
205
+ exchange_name=exchange,
206
+ passive=False,
207
+ )
128
208
 
129
- # Find all message handlers and scheduled actions
209
+ # Declare dead letter exchange and queue
210
+ dlx = await RabbitmqUtils.declare_dl_exchange(
211
+ channel=channel, passive=False
212
+ )
213
+ dlq = await RabbitmqUtils.declare_dl_queue(channel=channel, passive=False)
214
+ await dlq.bind(dlx, routing_key=RabbitmqUtils.DEAD_LETTER_EXCHANGE)
215
+
216
+ except Exception as e:
217
+ click.echo(f"Error during exchange declaration: {e}")
218
+
219
+ # If interactive mode and user confirms, or if forced, try again after deletion
220
+ if should_recreate_infrastructure(
221
+ force, interactive_mode, "Delete existing infrastructure and recreate?"
222
+ ):
223
+ # Delete infrastructure with a new channel
224
+ await delete_infrastructure(connection, exchange)
225
+
226
+ # Try again with a new channel
227
+ async with connection.channel() as new_channel:
228
+ # Try again after deletion
229
+ await RabbitmqUtils.declare_main_exchange(
230
+ channel=new_channel,
231
+ exchange_name=exchange,
232
+ passive=False,
233
+ )
234
+ dlx = await RabbitmqUtils.declare_dl_exchange(
235
+ channel=new_channel, passive=False
236
+ )
237
+ dlq = await RabbitmqUtils.declare_dl_queue(
238
+ channel=new_channel, passive=False
239
+ )
240
+ await dlq.bind(dlx, routing_key=RabbitmqUtils.DEAD_LETTER_EXCHANGE)
241
+ elif force:
242
+ # If force is true but recreation failed, propagate the error
243
+ raise
244
+ else:
245
+ click.echo("Skipping main exchange declaration due to error")
246
+
247
+
248
+ async def declare_controller_queues(
249
+ connection: aio_pika.abc.AbstractConnection,
250
+ app: Microservice,
251
+ exchange: str,
252
+ force: bool,
253
+ interactive_mode: bool,
254
+ ) -> None:
255
+ """
256
+ Declare all message handler and scheduled action queues for controllers.
257
+ """
130
258
  for instance_type in app.controllers:
131
259
  controller_spec = MessageBusController.get_messagebus(instance_type)
132
260
  if controller_spec is None:
@@ -134,50 +262,135 @@ async def declare_worker_infrastructure(
134
262
 
135
263
  _, members = inspect_controller(instance_type)
136
264
 
137
- # Declare queues for message handlers
265
+ # Process each member (method) in the controller
138
266
  for _, member in members.items():
139
- message_handler = MessageHandler.get_message_incoming(
140
- member.member_function
267
+ # Check if it's a message handler
268
+ await declare_message_handler_queue(
269
+ connection, member, exchange, force, interactive_mode
141
270
  )
142
- if message_handler is not None:
143
- queue_name = f"{message_handler.message_type.MESSAGE_TOPIC}.{member.member_function.__module__}.{member.member_function.__qualname__}"
144
- routing_key = f"{message_handler.message_type.MESSAGE_TOPIC}.#"
145
-
146
- try:
147
- # Try to declare queue
148
- queue = await RabbitmqUtils.declare_worker_queue(
149
- channel=channel, queue_name=queue_name, passive=False
150
- )
151
- await queue.bind(exchange=exchange, routing_key=routing_key)
152
- click.echo(
153
- f"✓ Declared message handler queue: {queue_name} (routing key: {routing_key})"
154
- )
155
- except Exception as e:
156
- click.echo(f"⚠ Skipping message handler queue {queue_name} due to error: {e}")
157
- continue
158
271
 
159
- scheduled_action = ScheduledAction.get_scheduled_action(
160
- member.member_function
272
+ # Check if it's a scheduled action
273
+ await declare_scheduled_action_queue(
274
+ connection, member, exchange, force, interactive_mode
161
275
  )
162
- if scheduled_action is not None:
163
- queue_name = f"{member.member_function.__module__}.{member.member_function.__qualname__}"
164
- routing_key = queue_name
165
-
166
- try:
167
- # Try to declare queue
168
- queue = await RabbitmqUtils.declare_scheduled_action_queue(
169
- channel=channel, queue_name=queue_name, passive=False
170
- )
276
+
277
+
278
+ async def declare_message_handler_queue(
279
+ connection: aio_pika.abc.AbstractConnection,
280
+ member: ControllerMemberReflect,
281
+ exchange: str,
282
+ force: bool,
283
+ interactive_mode: bool,
284
+ ) -> None:
285
+ """
286
+ Declare a queue for a message handler if the member is one.
287
+ """
288
+ message_handler = MessageHandler.get_message_incoming(member.member_function)
289
+ if message_handler is not None:
290
+ queue_name = f"{message_handler.message_type.MESSAGE_TOPIC}.{member.member_function.__module__}.{member.member_function.__qualname__}"
291
+ routing_key = f"{message_handler.message_type.MESSAGE_TOPIC}.#"
292
+
293
+ await declare_and_bind_queue(
294
+ connection,
295
+ queue_name,
296
+ routing_key,
297
+ exchange,
298
+ force,
299
+ interactive_mode,
300
+ is_scheduled_action=False,
301
+ )
302
+
303
+
304
+ async def declare_scheduled_action_queue(
305
+ connection: aio_pika.abc.AbstractConnection,
306
+ member: ControllerMemberReflect,
307
+ exchange: str,
308
+ force: bool,
309
+ interactive_mode: bool,
310
+ ) -> None:
311
+ """
312
+ Declare a queue for a scheduled action if the member is one.
313
+ """
314
+ scheduled_action = ScheduledAction.get_scheduled_action(member.member_function)
315
+ if scheduled_action is not None:
316
+ queue_name = (
317
+ f"{member.member_function.__module__}.{member.member_function.__qualname__}"
318
+ )
319
+ routing_key = queue_name
320
+
321
+ await declare_and_bind_queue(
322
+ connection,
323
+ queue_name,
324
+ routing_key,
325
+ exchange,
326
+ force,
327
+ interactive_mode,
328
+ is_scheduled_action=True,
329
+ )
330
+
331
+
332
+ async def declare_and_bind_queue(
333
+ connection: aio_pika.abc.AbstractConnection,
334
+ queue_name: str,
335
+ routing_key: str,
336
+ exchange: str,
337
+ force: bool,
338
+ interactive_mode: bool,
339
+ is_scheduled_action: bool,
340
+ ) -> None:
341
+ """
342
+ Declare and bind a queue to the exchange with the given routing key.
343
+ """
344
+ queue_type = "scheduled action" if is_scheduled_action else "message handler"
345
+
346
+ # Using async context manager for channel creation
347
+ async with connection.channel() as channel:
348
+ try:
349
+ # Try to declare queue using the appropriate method
350
+ if is_scheduled_action:
351
+ queue = await RabbitmqUtils.declare_scheduled_action_queue(
352
+ channel=channel, queue_name=queue_name, passive=False
353
+ )
354
+ else:
355
+ queue = await RabbitmqUtils.declare_worker_queue(
356
+ channel=channel, queue_name=queue_name, passive=False
357
+ )
358
+
359
+ # Bind the queue to the exchange
360
+ await queue.bind(exchange=exchange, routing_key=routing_key)
361
+ click.echo(
362
+ f"✓ Declared {queue_type} queue: {queue_name} (routing key: {routing_key})"
363
+ )
364
+ except Exception as e:
365
+ click.echo(f"⚠ Error declaring {queue_type} queue {queue_name}: {e}")
366
+
367
+ # If interactive mode and user confirms, or if forced, recreate the queue
368
+ if force or (
369
+ interactive_mode
370
+ and click.confirm(f"Delete and recreate queue {queue_name}?")
371
+ ):
372
+ # Create a new channel for deletion and recreation
373
+ async with connection.channel() as new_channel:
374
+ # Delete the queue
375
+ await RabbitmqUtils.delete_queue(new_channel, queue_name)
376
+
377
+ # Try to declare queue again using the appropriate method
378
+ if is_scheduled_action:
379
+ queue = await RabbitmqUtils.declare_scheduled_action_queue(
380
+ channel=new_channel, queue_name=queue_name, passive=False
381
+ )
382
+ else:
383
+ queue = await RabbitmqUtils.declare_worker_queue(
384
+ channel=new_channel, queue_name=queue_name, passive=False
385
+ )
386
+
387
+ # Bind the queue to the exchange
171
388
  await queue.bind(exchange=exchange, routing_key=routing_key)
172
389
  click.echo(
173
- f"✓ Declared scheduled action queue: {queue_name} (routing key: {routing_key})"
390
+ f"✓ Recreated {queue_type} queue: {queue_name} (routing key: {routing_key})"
174
391
  )
175
- except Exception as e:
176
- click.echo(f"⚠ Skipping scheduled action queue {queue_name} due to error: {e}")
177
- continue
178
-
179
- await channel.close()
180
- await connection.close()
392
+ else:
393
+ click.echo(f"⚠ Skipping {queue_type} queue {queue_name}")
181
394
 
182
395
 
183
396
  @click.group()
@@ -552,29 +765,14 @@ def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
552
765
  help="Broker URL (e.g., amqp://guest:guest@localhost/) [env: BROKER_URL]",
553
766
  )
554
767
  @click.option(
555
- "--exchange",
556
- type=str,
557
- default="jararaca_ex",
558
- envvar="EXCHANGE",
559
- help="Exchange name [env: EXCHANGE]",
560
- )
561
- @click.option(
562
- "--passive-declare",
768
+ "-i",
769
+ "--interactive-mode",
563
770
  is_flag=True,
564
771
  default=False,
565
- help="Use passive declarations (check if infrastructure exists without creating it)",
566
- )
567
- @click.option(
568
- "--handlers",
569
- type=str,
570
- help="Comma-separated list of handler names to declare queues for. If not specified, all handlers will be declared.",
571
- )
572
- @click.option(
573
- "--schedulers",
574
- type=str,
575
- help="Comma-separated list of scheduler names to declare queues for. If not specified, all schedulers will be declared.",
772
+ help="Enable interactive mode for queue declaration (confirm before deleting existing queues)",
576
773
  )
577
774
  @click.option(
775
+ "-f",
578
776
  "--force",
579
777
  is_flag=True,
580
778
  default=False,
@@ -582,12 +780,9 @@ def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
582
780
  )
583
781
  def declare(
584
782
  app_path: str,
585
- broker_url: str | None,
586
- exchange: str,
587
- passive_declare: bool,
588
- handlers: str | None,
589
- schedulers: str | None,
783
+ broker_url: str,
590
784
  force: bool,
785
+ interactive_mode: bool,
591
786
  ) -> None:
592
787
  """
593
788
  Declare RabbitMQ infrastructure (exchanges and queues) for message handlers and schedulers.
@@ -626,28 +821,13 @@ def declare(
626
821
  )
627
822
  return
628
823
 
629
- # Parse handler names if provided
630
- handler_names: set[str] | None = None
631
- if handlers:
632
- handler_names = {
633
- name.strip() for name in handlers.split(",") if name.strip()
634
- }
635
-
636
- # Parse scheduler names if provided
637
- scheduler_names: set[str] | None = None
638
- if schedulers:
639
- scheduler_names = {
640
- name.strip() for name in schedulers.split(",") if name.strip()
641
- }
642
-
643
824
  try:
644
825
  # Create the broker URL with exchange parameter
645
- broker_url_with_exchange = f"{broker_url}?exchange={exchange}"
646
826
 
647
- click.echo(
648
- f"→ Declaring worker infrastructure (URL: {broker_url_with_exchange})"
827
+ click.echo(f"→ Declaring worker infrastructure (URL: {broker_url})")
828
+ await declare_worker_infrastructure(
829
+ broker_url, app, force, interactive_mode
649
830
  )
650
- await declare_worker_infrastructure(broker_url_with_exchange, app, force)
651
831
 
652
832
  click.echo("✓ Workers infrastructure declared successfully!")
653
833
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: jararaca
3
- Version: 0.3.11a13
3
+ Version: 0.3.11a15
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=pmwWDk7muP_BsDgWTcyiCOIhSbEJuuEoNFSiH2g27b4,19753
6
+ jararaca/cli.py,sha256=zkWRcqllY_C0sIR7h_crlptq2cA6sxRM4nvMMLBaANs,25946
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
@@ -66,8 +66,8 @@ jararaca/tools/app_config/interceptor.py,sha256=HV8h4AxqUc_ACs5do4BSVlyxlRXzx7Hq
66
66
  jararaca/tools/typescript/interface_parser.py,sha256=35xbOrZDQDyTXdMrVZQ8nnFw79f28lJuLYNHAspIqi8,30492
67
67
  jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  jararaca/utils/rabbitmq_utils.py,sha256=ytdAFUyv-OBkaVnxezuJaJoLrmN7giZgtKeet_IsMBs,10918
69
- jararaca-0.3.11a13.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
70
- jararaca-0.3.11a13.dist-info/METADATA,sha256=akkZ-YQwyNhyiFNRgvBCooSVevh2SlRmNH5I_tdeJCE,4998
71
- jararaca-0.3.11a13.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
72
- jararaca-0.3.11a13.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
73
- jararaca-0.3.11a13.dist-info/RECORD,,
69
+ jararaca-0.3.11a15.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
70
+ jararaca-0.3.11a15.dist-info/METADATA,sha256=7F5Rf37ynVQCj-5hACmHpsmjK11436KyQV6V3uqGdeI,4998
71
+ jararaca-0.3.11a15.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
72
+ jararaca-0.3.11a15.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
73
+ jararaca-0.3.11a15.dist-info/RECORD,,