jararaca 0.3.0__py3-none-any.whl → 0.3.2__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
@@ -1,9 +1,11 @@
1
1
  import importlib
2
2
  import importlib.resources
3
+ import multiprocessing
3
4
  import os
4
5
  import sys
5
6
  import time
6
7
  from codecs import StreamWriter
8
+ from pathlib import Path
7
9
  from typing import Any
8
10
  from urllib.parse import urlparse, urlunsplit
9
11
 
@@ -265,6 +267,30 @@ def scheduler_v2(
265
267
  scheduler.run()
266
268
 
267
269
 
270
+ def generate_interfaces(app_path: str, file_path: str) -> None:
271
+ try:
272
+ app = find_microservice_by_module_path(app_path)
273
+ content = write_microservice_to_typescript_interface(app)
274
+
275
+ with open(file_path, "w", encoding="utf-8") as file:
276
+ # Save current position
277
+ file.tell()
278
+
279
+ # Reset file to beginning
280
+ file.seek(0)
281
+ file.truncate()
282
+
283
+ # Write new content
284
+ file.write(content)
285
+ file.flush()
286
+
287
+ print(
288
+ f"Generated TypeScript interfaces at {time.strftime('%H:%M:%S')} at {str(Path(file_path).absolute())}"
289
+ )
290
+ except Exception as e:
291
+ print(f"Error generating TypeScript interfaces: {e}", file=sys.stderr)
292
+
293
+
268
294
  @cli.command()
269
295
  @click.argument(
270
296
  "app_path",
@@ -272,7 +298,7 @@ def scheduler_v2(
272
298
  )
273
299
  @click.argument(
274
300
  "file_path",
275
- type=click.File("w"),
301
+ type=click.Path(file_okay=True, dir_okay=False),
276
302
  )
277
303
  @click.option(
278
304
  "--watch",
@@ -283,32 +309,11 @@ def scheduler_v2(
283
309
  type=click.Path(exists=True, file_okay=False, dir_okay=True),
284
310
  default="src",
285
311
  )
286
- def gen_tsi(app_path: str, file_path: StreamWriter, watch: bool, src_dir: str) -> None:
312
+ def gen_tsi(app_path: str, file_path: str, watch: bool, src_dir: str) -> None:
287
313
  """Generate TypeScript interfaces from a Python microservice."""
288
314
 
289
- # Generate typescript interfaces
290
- def generate_interfaces() -> None:
291
- try:
292
- app = find_microservice_by_module_path(app_path)
293
- content = write_microservice_to_typescript_interface(app)
294
-
295
- # Save current position
296
- file_path.tell()
297
-
298
- # Reset file to beginning
299
- file_path.seek(0)
300
- file_path.truncate()
301
-
302
- # Write new content
303
- file_path.write(content)
304
- file_path.flush()
305
-
306
- print(f"Generated TypeScript interfaces at {time.strftime('%H:%M:%S')}")
307
- except Exception as e:
308
- print(f"Error generating TypeScript interfaces: {e}", file=sys.stderr)
309
-
310
315
  # Initial generation
311
- generate_interfaces()
316
+ generate_interfaces(app_path, file_path)
312
317
 
313
318
  # If watch mode is not enabled, exit
314
319
  if not watch:
@@ -334,7 +339,33 @@ def gen_tsi(app_path: str, file_path: StreamWriter, watch: bool, src_dir: str) -
334
339
  )
335
340
  if not event.is_directory and src_path.endswith(".py"):
336
341
  print(f"File changed: {src_path}")
337
- generate_interfaces()
342
+ # Create a completely detached process to ensure classes are reloaded
343
+ process = multiprocessing.get_context("spawn").Process(
344
+ target=generate_interfaces,
345
+ args=(app_path, file_path),
346
+ daemon=False, # Non-daemon to ensure it completes
347
+ )
348
+ process.start()
349
+ # Don't join to keep it detached from main process
350
+
351
+ def _run_generator_in_separate_process(
352
+ self, app_path: str, file_path: str
353
+ ) -> None:
354
+ # Using Python executable to start a completely new process
355
+ # This ensures all modules are freshly imported
356
+ generate_interfaces(app_path, file_path)
357
+ # cmd = [
358
+ # sys.executable,
359
+ # "-c",
360
+ # (
361
+ # f"import sys; sys.path.extend({sys.path}); "
362
+ # f"from jararaca.cli import generate_interfaces; "
363
+ # f"generate_interfaces('{app_path}', '{file_path}')"
364
+ # ),
365
+ # ]
366
+ # import subprocess
367
+
368
+ # subprocess.run(cmd, check=False)
338
369
 
339
370
  # Set up observer
340
371
  observer = Observer()
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from contextlib import asynccontextmanager
2
3
  from datetime import datetime, timedelta
3
4
  from datetime import tzinfo as _TzInfo
@@ -29,10 +30,10 @@ class AIOPikaMessagePublisher(MessagePublisher):
29
30
  self.message_broker_backend = message_broker_backend
30
31
 
31
32
  async def publish(self, message: IMessage, topic: str) -> None:
32
- exchange = await self.channel.declare_exchange(
33
- self.exchange_name,
34
- type=aio_pika.ExchangeType.TOPIC,
35
- )
33
+ exchange = await self.channel.get_exchange(self.exchange_name, ensure=False)
34
+ if not exchange:
35
+ logging.warning(f"Exchange {self.exchange_name} not found")
36
+ return
36
37
  routing_key = f"{topic}."
37
38
  await exchange.publish(
38
39
  aio_pika.Message(body=message.model_dump_json().encode()),
@@ -193,10 +193,11 @@ def parse_type_to_typescript_interface(
193
193
  mapped_types.update(inherited_classes)
194
194
 
195
195
  if Enum in inherited_classes:
196
+ enum_values = sorted([(x._name_, x.value) for x in basemodel_type])
196
197
  return (
197
198
  set(),
198
199
  f"export enum {basemodel_type.__name__} {{\n"
199
- + "\n ".join([f'\t{x._name_} = "{x.value}",' for x in basemodel_type])
200
+ + "\n ".join([f'\t{name} = "{value}",' for name, value in enum_values])
200
201
  + "\n}\n",
201
202
  )
202
203
 
@@ -222,31 +223,39 @@ def parse_type_to_typescript_interface(
222
223
  extends_expression = (
223
224
  " extends %s"
224
225
  % ", ".join(
225
- [
226
- (
227
- "%s" % get_field_type_for_ts(inherited_class)
228
- if not inherited_classes_consts_conflict[inherited_class]
229
- else "Omit<%s, %s>"
230
- % (
231
- get_field_type_for_ts(inherited_class),
232
- " | ".join(
233
- '"%s"' % field_name
234
- for field_name in inherited_classes_consts_conflict[
235
- inherited_class
236
- ]
237
- ),
226
+ sorted(
227
+ [
228
+ (
229
+ "%s" % get_field_type_for_ts(inherited_class)
230
+ if not inherited_classes_consts_conflict[inherited_class]
231
+ else "Omit<%s, %s>"
232
+ % (
233
+ get_field_type_for_ts(inherited_class),
234
+ " | ".join(
235
+ sorted(
236
+ [
237
+ '"%s"' % field_name
238
+ for field_name in inherited_classes_consts_conflict[
239
+ inherited_class
240
+ ]
241
+ ]
242
+ )
243
+ ),
244
+ )
238
245
  )
239
- )
240
- for inherited_class in valid_inherited_classes
241
- ]
246
+ for inherited_class in valid_inherited_classes
247
+ ],
248
+ key=lambda x: str(x),
249
+ )
242
250
  )
243
251
  if len(valid_inherited_classes) > 0
244
252
  else ""
245
253
  )
246
254
 
247
255
  if is_generic_type(basemodel_type):
256
+ generic_args = get_generic_args(basemodel_type)
248
257
  string_builder.write(
249
- f"export interface {basemodel_type.__name__}<{', '.join([arg.__name__ for arg in get_generic_args(basemodel_type)])}>{extends_expression} {{\n"
258
+ f"export interface {basemodel_type.__name__}<{', '.join(sorted([arg.__name__ for arg in generic_args]))}>{extends_expression} {{\n"
250
259
  )
251
260
  else:
252
261
  string_builder.write(
@@ -254,12 +263,12 @@ def parse_type_to_typescript_interface(
254
263
  )
255
264
 
256
265
  if hasattr(basemodel_type, "__annotations__"):
257
- # for field_name in (f for f in dir(basemodel_type) if is_constant(f)):
258
- # field = getattr(basemodel_type, field_name)
259
- # if field is None:
260
- # continue
261
- # string_builder.write(f" {field_name}: {parse_literal_value(field)};\n")
262
- for field_name, field in basemodel_type.__annotations__.items():
266
+ # Sort fields for consistent output
267
+ annotation_items = sorted(
268
+ basemodel_type.__annotations__.items(), key=lambda x: x[0]
269
+ )
270
+
271
+ for field_name, field in annotation_items:
263
272
  if field_name in cls_consts:
264
273
  continue
265
274
 
@@ -277,8 +286,11 @@ def parse_type_to_typescript_interface(
277
286
  mapped_types.update(extract_all_envolved_types(field))
278
287
  mapped_types.add(field)
279
288
 
280
- ## Loop over computed fields
281
- members = inspect.getmembers(basemodel_type, lambda a: isinstance(a, property))
289
+ ## Loop over computed fields - sort them for consistent output
290
+ members = sorted(
291
+ inspect.getmembers(basemodel_type, lambda a: isinstance(a, property)),
292
+ key=lambda x: x[0],
293
+ )
282
294
  for field_name, field in members:
283
295
  if hasattr(field, "fget"):
284
296
  module_func_name = field.fget.__module__ + "." + field.fget.__qualname__
@@ -359,11 +371,13 @@ export type WebSocketMessageMap = {
359
371
  }
360
372
  """
361
373
  % "\n".join(
362
- [
363
- f'\t"{message.MESSAGE_ID}": {message.__name__};'
364
- for registers in websocket_registries
365
- for message in registers.message_types
366
- ]
374
+ sorted(
375
+ [
376
+ f'\t"{message.MESSAGE_ID}": {message.__name__};'
377
+ for registers in websocket_registries
378
+ for message in registers.message_types
379
+ ]
380
+ )
367
381
  )
368
382
  )
369
383
 
@@ -477,7 +491,12 @@ def write_rest_controller_to_typescript_interface(
477
491
 
478
492
  mapped_types: set[Any] = set()
479
493
 
480
- for name, member in inspect.getmembers(controller, predicate=inspect.isfunction):
494
+ # Sort members for consistent output
495
+ member_items = sorted(
496
+ inspect.getmembers(controller, predicate=inspect.isfunction), key=lambda x: x[0]
497
+ )
498
+
499
+ for name, member in member_items:
481
500
  if (mapping := HttpMapping.get_http_mapping(member)) is not None:
482
501
  return_type = member.__annotations__.get("return")
483
502
 
@@ -514,17 +533,24 @@ def write_rest_controller_to_typescript_interface(
514
533
  )
515
534
  class_buffer.write(f"\t\t\tpath: `/{final_path}`,\n")
516
535
 
536
+ # Sort headers
537
+ header_params = sorted(
538
+ [param for param in arg_params_spec if param.type_ == "header"],
539
+ key=lambda x: x.name,
540
+ )
517
541
  class_buffer.write("\t\t\theaders: {\n")
518
- for param in arg_params_spec:
519
- if param.type_ == "header":
520
- class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
521
-
542
+ for param in header_params:
543
+ class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
522
544
  class_buffer.write("\t\t\t},\n")
523
- class_buffer.write("\t\t\tquery: {\n")
524
545
 
525
- for param in arg_params_spec:
526
- if param.type_ == "query":
527
- class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
546
+ # Sort query params
547
+ query_params = sorted(
548
+ [param for param in arg_params_spec if param.type_ == "query"],
549
+ key=lambda x: x.name,
550
+ )
551
+ class_buffer.write("\t\t\tquery: {\n")
552
+ for param in query_params:
553
+ class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
528
554
  class_buffer.write("\t\t\t},\n")
529
555
 
530
556
  if (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: jararaca
3
- Version: 0.3.0
3
+ Version: 0.3.2
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=8opY0_YOKGaglDxIg9ijTWKcTvgHlBY9x3xiJGEo1xk,8964
6
+ jararaca/cli.py,sha256=oaLqJlb-GAbhYpQHzl-P8ajPdo-G_ddIuSu2SsZNDh8,10244
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
@@ -16,7 +16,7 @@ jararaca/messagebus/bus_message_controller.py,sha256=Xd_qwnX5jUvgBTCarHR36fvtol9
16
16
  jararaca/messagebus/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  jararaca/messagebus/decorators.py,sha256=y-w4dWbP9ZW3ZJ4mE9iIaxw01ZC5snEbOuBY5NC-Bn0,5626
18
18
  jararaca/messagebus/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=1pSaifCT6TnuKWH7u80llRcUvyFlS-G9p1AU83CxGuA,5315
19
+ jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=Aex87wopB34sMNzXBgi4KucVHjL2wYog3-IH75DAfDk,5387
20
20
  jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=fQFFW9hH6ZU3UOyR7kMPrNp9wA71qEy5XlgrBQdBMS4,1230
21
21
  jararaca/messagebus/message.py,sha256=U6cyd2XknX8mtm0333slz5fanky2PFLWCmokAO56vvU,819
22
22
  jararaca/messagebus/publisher.py,sha256=K7WsOMVTyLmdms3ZKEshqrQc_DhNreiFK-HnmOT9Ce0,1965
@@ -63,11 +63,11 @@ jararaca/tools/app_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
63
63
  jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaNXMp8fd1Ndk,941
64
64
  jararaca/tools/app_config/interceptor.py,sha256=nfFZiS80hrbnL7-XEYrwmp2rwaVYBqxvqu3Y-6o_ov4,2575
65
65
  jararaca/tools/metadata.py,sha256=7nlCDYgItNybentPSSCc2MLqN7IpBd0VyQzfjfQycVI,1402
66
- jararaca/tools/typescript/interface_parser.py,sha256=4SHt094P-QawMFHSyMCiujQf8Niw7xACIO1RHBM8-w4,29192
66
+ jararaca/tools/typescript/interface_parser.py,sha256=_4cqte3OMAiHP-dPqUCWzWar4Ur04F-0x4ZUEnSTN-c,30040
67
67
  jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
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,,
69
+ jararaca-0.3.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
70
+ jararaca-0.3.2.dist-info/METADATA,sha256=S8sFyV7thyv0X-v-xiN-rK8JobRXvxpJzHef3wmW8Kc,4951
71
+ jararaca-0.3.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
72
+ jararaca-0.3.2.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
73
+ jararaca-0.3.2.dist-info/RECORD,,