sera-2 1.1.0__py3-none-any.whl → 1.2.1__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.
@@ -4,7 +4,7 @@ from typing import Sequence
4
4
 
5
5
  from codegen.models import DeferredVar, PredefinedFn, Program, expr, stmt
6
6
  from loguru import logger
7
- from sera.misc import to_snake_case
7
+ from sera.misc import assert_not_null, to_snake_case
8
8
  from sera.models import App, DataCollection, Module, Package
9
9
 
10
10
 
@@ -13,17 +13,25 @@ def make_python_api(app: App, collections: Sequence[DataCollection]):
13
13
  app.api.ensure_exists()
14
14
  app.api.pkg("routes").ensure_exists()
15
15
 
16
+ # make routes & depdnencies
16
17
  dep_pkg = app.api.pkg("dependencies")
18
+ routes: list[Module] = []
17
19
  for collection in collections:
18
20
  make_dependency(collection, dep_pkg)
19
21
  route = app.api.pkg("routes").pkg(collection.get_pymodule_name())
20
22
 
21
- get_route, get_route_fn = make_python_get_api(collection, route)
23
+ controllers = []
24
+ controllers.append(make_python_get_api(collection, route))
25
+ controllers.append(make_python_get_by_id_api(collection, route))
26
+ controllers.append(make_python_has_api(collection, route))
27
+ controllers.append(make_python_create_api(collection, route))
28
+ controllers.append(make_python_update_api(collection, route))
22
29
 
23
30
  program = Program()
24
31
  program.import_("__future__.annotations", True)
25
32
  program.import_("litestar.Router", True)
26
- program.import_(get_route.path + "." + get_route_fn, True)
33
+ for get_route, get_route_fn in controllers:
34
+ program.import_(get_route.path + "." + get_route_fn, True)
27
35
 
28
36
  program.root(
29
37
  stmt.LineBreak(),
@@ -42,7 +50,8 @@ def make_python_api(app: App, collections: Sequence[DataCollection]):
42
50
  "route_handlers",
43
51
  PredefinedFn.list(
44
52
  [
45
- expr.ExprIdent(get_route_fn),
53
+ expr.ExprIdent(get_route_fn)
54
+ for get_route, get_route_fn in controllers
46
55
  ]
47
56
  ),
48
57
  ),
@@ -52,6 +61,43 @@ def make_python_api(app: App, collections: Sequence[DataCollection]):
52
61
  )
53
62
 
54
63
  route.module("route").write(program)
64
+ routes.append(route.module("route"))
65
+
66
+ # make the main entry point
67
+ make_main(app.api, routes)
68
+
69
+
70
+ def make_main(target_pkg: Package, routes: Sequence[Module]):
71
+ outmod = target_pkg.module("app")
72
+ if outmod.exists():
73
+ logger.info("`{}` already exists. Skip generation.", outmod.path)
74
+ return
75
+
76
+ program = Program()
77
+ program.import_("__future__.annotations", True)
78
+ program.import_("litestar.Litestar", True)
79
+ for route in routes:
80
+ program.import_(route.path, False)
81
+
82
+ program.root(
83
+ stmt.LineBreak(),
84
+ lambda ast: ast.assign(
85
+ DeferredVar.simple("app"),
86
+ expr.ExprFuncCall(
87
+ expr.ExprIdent("Litestar"),
88
+ [
89
+ PredefinedFn.keyword_assignment(
90
+ "route_handlers",
91
+ PredefinedFn.list(
92
+ [expr.ExprIdent(route.path + ".router") for route in routes]
93
+ ),
94
+ )
95
+ ],
96
+ ),
97
+ ),
98
+ )
99
+
100
+ outmod.write(program)
55
101
 
56
102
 
57
103
  def make_dependency(collection: DataCollection, target_pkg: Package):
@@ -76,7 +122,10 @@ def make_dependency(collection: DataCollection, target_pkg: Package):
76
122
  program.root(
77
123
  stmt.LineBreak(),
78
124
  lambda ast: ast.func(
79
- ServiceNameDep, [], expr.ExprIdent(collection.get_service_name())
125
+ ServiceNameDep,
126
+ [],
127
+ expr.ExprIdent(collection.get_service_name()),
128
+ is_async=True,
80
129
  )(
81
130
  lambda ast01: ast01.return_(
82
131
  expr.ExprFuncCall(expr.ExprIdent(collection.get_service_name()), [])
@@ -97,10 +146,13 @@ def make_python_get_api(
97
146
  program = Program()
98
147
  program.import_("__future__.annotations", True)
99
148
  program.import_("typing.Annotated", True)
149
+ program.import_("typing.Sequence", True)
100
150
  program.import_("litestar.get", True)
101
151
  program.import_("litestar.Request", True)
102
152
  program.import_("litestar.params.Parameter", True)
153
+ program.import_(app.models.db.path + ".base.get_session", True)
103
154
  program.import_("litestar.di.Provide", True)
155
+ program.import_("sqlalchemy.orm.Session", True)
104
156
  program.import_(app.config.path + ".ROUTER_DEBUG", True)
105
157
  program.import_(
106
158
  f"{app.api.path}.dependencies.{collection.get_pymodule_name()}.{ServiceNameDep}",
@@ -111,6 +163,10 @@ def make_python_get_api(
111
163
  + f".{collection.get_pymodule_name()}.{collection.get_service_name()}",
112
164
  True,
113
165
  )
166
+ program.import_(
167
+ app.models.data.path + f".{collection.get_pymodule_name()}.{collection.name}",
168
+ True,
169
+ )
114
170
  program.import_("sera.libs.api_helper.parse_query", True)
115
171
 
116
172
  func_name = "get_"
@@ -133,7 +189,11 @@ def make_python_get_api(
133
189
  (
134
190
  expr.ExprConstant("service"),
135
191
  expr.ExprIdent(f"Provide({ServiceNameDep})"),
136
- )
192
+ ),
193
+ (
194
+ expr.ExprConstant("session"),
195
+ expr.ExprIdent(f"Provide(get_session)"),
196
+ ),
137
197
  ]
138
198
  ),
139
199
  ),
@@ -187,7 +247,12 @@ def make_python_get_api(
187
247
  "service",
188
248
  expr.ExprIdent(collection.get_service_name()),
189
249
  ),
250
+ DeferredVar.simple(
251
+ "session",
252
+ expr.ExprIdent("Session"),
253
+ ),
190
254
  ],
255
+ return_type=expr.ExprIdent(f"Sequence[{collection.name}]"),
191
256
  is_async=True,
192
257
  )(
193
258
  stmt.SingleExprStatement(
@@ -207,7 +272,8 @@ def make_python_get_api(
207
272
  ],
208
273
  ),
209
274
  ),
210
- lambda ast12: ast12.return_(
275
+ lambda ast12: ast12.assign(
276
+ DeferredVar.simple("result"),
211
277
  expr.ExprFuncCall(
212
278
  expr.ExprIdent("service.get"),
213
279
  [
@@ -230,7 +296,21 @@ def make_python_get_api(
230
296
  PredefinedFn.keyword_assignment(
231
297
  "fields", expr.ExprIdent("fields")
232
298
  ),
299
+ PredefinedFn.keyword_assignment(
300
+ "session", expr.ExprIdent("session")
301
+ ),
233
302
  ],
303
+ ),
304
+ ),
305
+ lambda ast13: ast13.return_(
306
+ PredefinedFn.map_list(
307
+ expr.ExprIdent("result"),
308
+ lambda item: expr.ExprFuncCall(
309
+ PredefinedFn.attr_getter(
310
+ expr.ExprIdent(collection.name), expr.ExprIdent("from_db")
311
+ ),
312
+ [item],
313
+ ),
234
314
  )
235
315
  ),
236
316
  ),
@@ -240,3 +320,478 @@ def make_python_get_api(
240
320
  outmod.write(program)
241
321
 
242
322
  return outmod, func_name
323
+
324
+
325
+ def make_python_get_by_id_api(
326
+ collection: DataCollection, target_pkg: Package
327
+ ) -> tuple[Module, str]:
328
+ """Make an endpoint for querying resource by id"""
329
+ app = target_pkg.app
330
+
331
+ ServiceNameDep = to_snake_case(f"{collection.name}ServiceDependency")
332
+
333
+ program = Program()
334
+ program.import_("__future__.annotations", True)
335
+ program.import_("litestar.get", True)
336
+ program.import_("litestar.status_codes", True)
337
+ program.import_("litestar.exceptions.HTTPException", True)
338
+ program.import_("litestar.di.Provide", True)
339
+ program.import_("sqlalchemy.orm.Session", True)
340
+ program.import_(app.models.db.path + ".base.get_session", True)
341
+ program.import_(
342
+ f"{app.api.path}.dependencies.{collection.get_pymodule_name()}.{ServiceNameDep}",
343
+ True,
344
+ )
345
+ program.import_(
346
+ app.services.path
347
+ + f".{collection.get_pymodule_name()}.{collection.get_service_name()}",
348
+ True,
349
+ )
350
+ program.import_(
351
+ app.models.data.path + f".{collection.get_pymodule_name()}.{collection.name}",
352
+ True,
353
+ )
354
+
355
+ # assuming the collection has only one class
356
+ cls = collection.cls
357
+ id_type = assert_not_null(cls.get_id_property()).datatype.get_python_type().type
358
+
359
+ func_name = "get_by_id"
360
+ program.root(
361
+ stmt.LineBreak(),
362
+ stmt.PythonDecoratorStatement(
363
+ expr.ExprFuncCall(
364
+ expr.ExprIdent("get"),
365
+ [
366
+ expr.ExprConstant("/{id:%s}" % id_type),
367
+ PredefinedFn.keyword_assignment(
368
+ "dependencies",
369
+ PredefinedFn.dict(
370
+ [
371
+ (
372
+ expr.ExprConstant("service"),
373
+ expr.ExprIdent(f"Provide({ServiceNameDep})"),
374
+ ),
375
+ (
376
+ expr.ExprConstant("session"),
377
+ expr.ExprIdent(f"Provide(get_session)"),
378
+ ),
379
+ ]
380
+ ),
381
+ ),
382
+ ],
383
+ )
384
+ ),
385
+ lambda ast10: ast10.func(
386
+ func_name,
387
+ [
388
+ DeferredVar.simple(
389
+ "id",
390
+ expr.ExprIdent(id_type),
391
+ ),
392
+ DeferredVar.simple(
393
+ "service",
394
+ expr.ExprIdent(collection.get_service_name()),
395
+ ),
396
+ DeferredVar.simple(
397
+ "session",
398
+ expr.ExprIdent("Session"),
399
+ ),
400
+ ],
401
+ return_type=expr.ExprIdent(f"{cls.name}"),
402
+ is_async=True,
403
+ )(
404
+ stmt.SingleExprStatement(expr.ExprConstant("Retrieving record by id")),
405
+ lambda ast11: ast11.assign(
406
+ DeferredVar.simple("record"),
407
+ expr.ExprFuncCall(
408
+ expr.ExprIdent("service.get_by_id"),
409
+ [
410
+ expr.ExprIdent("id"),
411
+ expr.ExprIdent("session"),
412
+ ],
413
+ ),
414
+ ),
415
+ lambda ast12: ast12.if_(PredefinedFn.is_null(expr.ExprIdent("record")))(
416
+ lambda ast23: ast23.raise_exception(
417
+ expr.StandardExceptionExpr(
418
+ expr.ExprIdent("HTTPException"),
419
+ [
420
+ PredefinedFn.keyword_assignment(
421
+ "status_code",
422
+ expr.ExprIdent("status_codes.HTTP_404_NOT_FOUND"),
423
+ ),
424
+ PredefinedFn.keyword_assignment(
425
+ "detail",
426
+ expr.ExprIdent('f"Record with id {id} not found"'),
427
+ ),
428
+ ],
429
+ )
430
+ )
431
+ ),
432
+ lambda ast13: ast13.return_(
433
+ expr.ExprFuncCall(
434
+ PredefinedFn.attr_getter(
435
+ expr.ExprIdent(cls.name), expr.ExprIdent("from_db")
436
+ ),
437
+ [expr.ExprIdent("record")],
438
+ )
439
+ ),
440
+ ),
441
+ )
442
+
443
+ outmod = target_pkg.module("get_by_id")
444
+ outmod.write(program)
445
+
446
+ return outmod, func_name
447
+
448
+
449
+ def make_python_has_api(
450
+ collection: DataCollection, target_pkg: Package
451
+ ) -> tuple[Module, str]:
452
+ """Make an endpoint for querying resource by id"""
453
+ app = target_pkg.app
454
+
455
+ ServiceNameDep = to_snake_case(f"{collection.name}ServiceDependency")
456
+
457
+ program = Program()
458
+ program.import_("__future__.annotations", True)
459
+ program.import_("litestar.head", True)
460
+ program.import_("litestar.status_codes", True)
461
+ program.import_("litestar.exceptions.HTTPException", True)
462
+ program.import_("litestar.di.Provide", True)
463
+ program.import_("sqlalchemy.orm.Session", True)
464
+ program.import_(app.models.db.path + ".base.get_session", True)
465
+ program.import_(
466
+ f"{app.api.path}.dependencies.{collection.get_pymodule_name()}.{ServiceNameDep}",
467
+ True,
468
+ )
469
+ program.import_(
470
+ app.services.path
471
+ + f".{collection.get_pymodule_name()}.{collection.get_service_name()}",
472
+ True,
473
+ )
474
+ program.import_(
475
+ app.models.data.path + f".{collection.get_pymodule_name()}.{collection.name}",
476
+ True,
477
+ )
478
+
479
+ # assuming the collection has only one class
480
+ cls = collection.cls
481
+ id_type = assert_not_null(cls.get_id_property()).datatype.get_python_type().type
482
+
483
+ func_name = "has"
484
+ program.root(
485
+ stmt.LineBreak(),
486
+ stmt.PythonDecoratorStatement(
487
+ expr.ExprFuncCall(
488
+ expr.ExprIdent("head"),
489
+ [
490
+ expr.ExprConstant("/{id:%s}" % id_type),
491
+ PredefinedFn.keyword_assignment(
492
+ "status_code",
493
+ expr.ExprIdent("status_codes.HTTP_204_NO_CONTENT"),
494
+ ),
495
+ PredefinedFn.keyword_assignment(
496
+ "dependencies",
497
+ PredefinedFn.dict(
498
+ [
499
+ (
500
+ expr.ExprConstant("service"),
501
+ expr.ExprIdent(f"Provide({ServiceNameDep})"),
502
+ ),
503
+ (
504
+ expr.ExprConstant("session"),
505
+ expr.ExprIdent(f"Provide(get_session)"),
506
+ ),
507
+ ]
508
+ ),
509
+ ),
510
+ ],
511
+ )
512
+ ),
513
+ lambda ast10: ast10.func(
514
+ func_name,
515
+ [
516
+ DeferredVar.simple(
517
+ "id",
518
+ expr.ExprIdent(id_type),
519
+ ),
520
+ DeferredVar.simple(
521
+ "service",
522
+ expr.ExprIdent(collection.get_service_name()),
523
+ ),
524
+ DeferredVar.simple(
525
+ "session",
526
+ expr.ExprIdent("Session"),
527
+ ),
528
+ ],
529
+ return_type=expr.ExprConstant(None),
530
+ is_async=True,
531
+ )(
532
+ stmt.SingleExprStatement(expr.ExprConstant("Retrieving record by id")),
533
+ lambda ast11: ast11.assign(
534
+ DeferredVar.simple("record_exist"),
535
+ expr.ExprFuncCall(
536
+ expr.ExprIdent("service.has_id"),
537
+ [
538
+ expr.ExprIdent("id"),
539
+ expr.ExprIdent("session"),
540
+ ],
541
+ ),
542
+ ),
543
+ lambda ast12: ast12.if_(expr.ExprNegation(expr.ExprIdent("record_exist")))(
544
+ lambda ast23: ast23.raise_exception(
545
+ expr.StandardExceptionExpr(
546
+ expr.ExprIdent("HTTPException"),
547
+ [
548
+ PredefinedFn.keyword_assignment(
549
+ "status_code",
550
+ expr.ExprIdent("status_codes.HTTP_404_NOT_FOUND"),
551
+ ),
552
+ PredefinedFn.keyword_assignment(
553
+ "detail",
554
+ expr.ExprIdent('f"Record with id {id} not found"'),
555
+ ),
556
+ ],
557
+ )
558
+ )
559
+ ),
560
+ lambda ast13: ast13.return_(expr.ExprConstant(None)),
561
+ ),
562
+ )
563
+
564
+ outmod = target_pkg.module("has")
565
+ outmod.write(program)
566
+
567
+ return outmod, func_name
568
+
569
+
570
+ def make_python_create_api(collection: DataCollection, target_pkg: Package):
571
+ """Make an endpoint for creating a resource"""
572
+ app = target_pkg.app
573
+
574
+ ServiceNameDep = to_snake_case(f"{collection.name}ServiceDependency")
575
+
576
+ program = Program()
577
+ program.import_("__future__.annotations", True)
578
+ program.import_("litestar.post", True)
579
+ program.import_("litestar.di.Provide", True)
580
+ program.import_("sqlalchemy.orm.Session", True)
581
+ program.import_(app.models.db.path + ".base.get_session", True)
582
+ program.import_(
583
+ f"{app.api.path}.dependencies.{collection.get_pymodule_name()}.{ServiceNameDep}",
584
+ True,
585
+ )
586
+ program.import_(
587
+ app.services.path
588
+ + f".{collection.get_pymodule_name()}.{collection.get_service_name()}",
589
+ True,
590
+ )
591
+ program.import_(
592
+ app.models.data.path + f".{collection.get_pymodule_name()}.{collection.name}",
593
+ True,
594
+ )
595
+ program.import_(
596
+ app.models.data.path
597
+ + f".{collection.get_pymodule_name()}.Upsert{collection.name}",
598
+ True,
599
+ )
600
+
601
+ # assuming the collection has only one class
602
+ cls = collection.cls
603
+ id_type = assert_not_null(cls.get_id_property()).datatype.get_python_type().type
604
+
605
+ func_name = "create"
606
+
607
+ program.root(
608
+ stmt.LineBreak(),
609
+ stmt.PythonDecoratorStatement(
610
+ expr.ExprFuncCall(
611
+ expr.ExprIdent("post"),
612
+ [
613
+ expr.ExprConstant("/"),
614
+ PredefinedFn.keyword_assignment(
615
+ "dependencies",
616
+ PredefinedFn.dict(
617
+ [
618
+ (
619
+ expr.ExprConstant("service"),
620
+ expr.ExprIdent(f"Provide({ServiceNameDep})"),
621
+ ),
622
+ (
623
+ expr.ExprConstant("session"),
624
+ expr.ExprIdent(f"Provide(get_session)"),
625
+ ),
626
+ ]
627
+ ),
628
+ ),
629
+ ],
630
+ )
631
+ ),
632
+ lambda ast10: ast10.func(
633
+ func_name,
634
+ [
635
+ DeferredVar.simple(
636
+ "record",
637
+ expr.ExprIdent(f"Upsert{cls.name}"),
638
+ ),
639
+ DeferredVar.simple(
640
+ "service",
641
+ expr.ExprIdent(collection.get_service_name()),
642
+ ),
643
+ DeferredVar.simple(
644
+ "session",
645
+ expr.ExprIdent("Session"),
646
+ ),
647
+ ],
648
+ return_type=expr.ExprIdent(cls.name),
649
+ is_async=True,
650
+ )(
651
+ stmt.SingleExprStatement(expr.ExprConstant("Creating new record")),
652
+ lambda ast13: ast13.return_(
653
+ expr.ExprMethodCall(
654
+ expr.ExprIdent(cls.name),
655
+ "from_db",
656
+ [
657
+ expr.ExprMethodCall(
658
+ expr.ExprIdent("service"),
659
+ "create",
660
+ [
661
+ expr.ExprMethodCall(
662
+ expr.ExprIdent("record"), "to_db", []
663
+ ),
664
+ expr.ExprIdent("session"),
665
+ ],
666
+ )
667
+ ],
668
+ )
669
+ ),
670
+ ),
671
+ )
672
+
673
+ outmod = target_pkg.module("create")
674
+ outmod.write(program)
675
+
676
+ return outmod, func_name
677
+
678
+
679
+ def make_python_update_api(collection: DataCollection, target_pkg: Package):
680
+ """Make an endpoint for updating resource"""
681
+ app = target_pkg.app
682
+
683
+ ServiceNameDep = to_snake_case(f"{collection.name}ServiceDependency")
684
+
685
+ program = Program()
686
+ program.import_("__future__.annotations", True)
687
+ program.import_("litestar.put", True)
688
+ program.import_("litestar.di.Provide", True)
689
+ program.import_("sqlalchemy.orm.Session", True)
690
+ program.import_(app.models.db.path + ".base.get_session", True)
691
+ program.import_(
692
+ f"{app.api.path}.dependencies.{collection.get_pymodule_name()}.{ServiceNameDep}",
693
+ True,
694
+ )
695
+ program.import_(
696
+ app.services.path
697
+ + f".{collection.get_pymodule_name()}.{collection.get_service_name()}",
698
+ True,
699
+ )
700
+ program.import_(
701
+ app.models.data.path + f".{collection.get_pymodule_name()}.{collection.name}",
702
+ True,
703
+ )
704
+ program.import_(
705
+ app.models.data.path
706
+ + f".{collection.get_pymodule_name()}.Upsert{collection.name}",
707
+ True,
708
+ )
709
+
710
+ # assuming the collection has only one class
711
+ cls = collection.cls
712
+ id_prop = assert_not_null(cls.get_id_property())
713
+ id_type = id_prop.datatype.get_python_type().type
714
+
715
+ func_name = "update"
716
+
717
+ program.root(
718
+ stmt.LineBreak(),
719
+ stmt.PythonDecoratorStatement(
720
+ expr.ExprFuncCall(
721
+ expr.ExprIdent("put"),
722
+ [
723
+ expr.ExprConstant("/{id:%s}" % id_type),
724
+ PredefinedFn.keyword_assignment(
725
+ "dependencies",
726
+ PredefinedFn.dict(
727
+ [
728
+ (
729
+ expr.ExprConstant("service"),
730
+ expr.ExprIdent(f"Provide({ServiceNameDep})"),
731
+ ),
732
+ (
733
+ expr.ExprConstant("session"),
734
+ expr.ExprIdent(f"Provide(get_session)"),
735
+ ),
736
+ ]
737
+ ),
738
+ ),
739
+ ],
740
+ )
741
+ ),
742
+ lambda ast10: ast10.func(
743
+ func_name,
744
+ [
745
+ DeferredVar.simple(
746
+ "id",
747
+ expr.ExprIdent(id_type),
748
+ ),
749
+ DeferredVar.simple(
750
+ "record",
751
+ expr.ExprIdent(f"Upsert{cls.name}"),
752
+ ),
753
+ DeferredVar.simple(
754
+ "service",
755
+ expr.ExprIdent(collection.get_service_name()),
756
+ ),
757
+ DeferredVar.simple(
758
+ "session",
759
+ expr.ExprIdent("Session"),
760
+ ),
761
+ ],
762
+ return_type=expr.ExprIdent(cls.name),
763
+ is_async=True,
764
+ )(
765
+ stmt.SingleExprStatement(expr.ExprConstant("Update an existing record")),
766
+ stmt.SingleExprStatement(
767
+ PredefinedFn.attr_setter(
768
+ expr.ExprIdent("record"),
769
+ expr.ExprIdent(id_prop.name),
770
+ expr.ExprIdent("id"),
771
+ )
772
+ ),
773
+ lambda ast13: ast13.return_(
774
+ expr.ExprMethodCall(
775
+ expr.ExprIdent(cls.name),
776
+ "from_db",
777
+ [
778
+ expr.ExprMethodCall(
779
+ expr.ExprIdent("service"),
780
+ "update",
781
+ [
782
+ expr.ExprMethodCall(
783
+ expr.ExprIdent("record"), "to_db", []
784
+ ),
785
+ expr.ExprIdent("session"),
786
+ ],
787
+ )
788
+ ],
789
+ )
790
+ ),
791
+ ),
792
+ )
793
+
794
+ outmod = target_pkg.module("update")
795
+ outmod.write(program)
796
+
797
+ return outmod, func_name