sera-2 1.26.2__py3-none-any.whl → 1.26.4__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.
- sera/make/make_typescript_model.py +4 -858
- sera/make/ts_frontend/make_class_schema.py +0 -1
- sera/make/ts_frontend/make_draft_model.py +811 -0
- sera/make/ts_frontend/make_query.py +33 -15
- {sera_2-1.26.2.dist-info → sera_2-1.26.4.dist-info}/METADATA +1 -1
- {sera_2-1.26.2.dist-info → sera_2-1.26.4.dist-info}/RECORD +7 -6
- {sera_2-1.26.2.dist-info → sera_2-1.26.4.dist-info}/WHEEL +1 -1
@@ -1,23 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import Any, Callable
|
5
|
-
|
6
|
-
from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
|
3
|
+
from codegen.models import PredefinedFn, Program, expr, stmt
|
7
4
|
from codegen.models.var import DeferredVar
|
8
5
|
from loguru import logger
|
9
6
|
|
10
7
|
from sera.make.ts_frontend.make_class_schema import make_class_schema
|
8
|
+
from sera.make.ts_frontend.make_draft_model import make_draft
|
11
9
|
from sera.make.ts_frontend.make_query import make_query
|
12
|
-
from sera.
|
13
|
-
from sera.misc import (
|
14
|
-
assert_isinstance,
|
15
|
-
assert_not_null,
|
16
|
-
identity,
|
17
|
-
to_camel_case,
|
18
|
-
to_pascal_case,
|
19
|
-
to_snake_case,
|
20
|
-
)
|
10
|
+
from sera.misc import assert_isinstance, assert_not_null, to_camel_case, to_snake_case
|
21
11
|
from sera.models import (
|
22
12
|
Class,
|
23
13
|
DataProperty,
|
@@ -27,7 +17,6 @@ from sera.models import (
|
|
27
17
|
Schema,
|
28
18
|
TsTypeWithDep,
|
29
19
|
)
|
30
|
-
from sera.typing import is_set
|
31
20
|
|
32
21
|
|
33
22
|
def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
@@ -43,11 +32,6 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
43
32
|
idprop.get_data_model_datatype().get_typescript_type()
|
44
33
|
)
|
45
34
|
|
46
|
-
def clone_prop(prop: DataProperty | ObjectProperty, value: expr.Expr):
|
47
|
-
# detect all complex types is hard, we can assume that any update to this does not mutate
|
48
|
-
# the original object, then it's okay.
|
49
|
-
return value
|
50
|
-
|
51
35
|
def get_normal_deser_args(
|
52
36
|
prop: DataProperty | ObjectProperty,
|
53
37
|
) -> expr.Expr:
|
@@ -308,772 +292,6 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
308
292
|
|
309
293
|
pkg.module(cls.name).write(program)
|
310
294
|
|
311
|
-
def make_draft(cls: Class, pkg: Package):
|
312
|
-
if not cls.is_public:
|
313
|
-
# skip classes that are not public
|
314
|
-
return
|
315
|
-
|
316
|
-
idprop = cls.get_id_property()
|
317
|
-
|
318
|
-
draft_clsname = "Draft" + cls.name
|
319
|
-
draft_validators = f"draft{cls.name}Validators"
|
320
|
-
|
321
|
-
program = Program()
|
322
|
-
program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
|
323
|
-
program.import_("mobx.makeObservable", True)
|
324
|
-
program.import_("mobx.observable", True)
|
325
|
-
program.import_("mobx.action", True)
|
326
|
-
program.import_("sera-db.validators", True)
|
327
|
-
|
328
|
-
import_helper = ImportHelper(program, TS_GLOBAL_IDENTS)
|
329
|
-
|
330
|
-
program.root(
|
331
|
-
stmt.LineBreak(),
|
332
|
-
stmt.TypescriptStatement(
|
333
|
-
"const {getValidator, memoizeOneValidators} = validators;"
|
334
|
-
),
|
335
|
-
stmt.LineBreak(),
|
336
|
-
)
|
337
|
-
|
338
|
-
# make sure that the property stale is not in existing properties
|
339
|
-
if "stale" in cls.properties:
|
340
|
-
raise ValueError(f"Class {cls.name} already has property stale")
|
341
|
-
|
342
|
-
# information about class primary key
|
343
|
-
cls_pk = None
|
344
|
-
observable_args: list[tuple[expr.Expr, expr.ExprIdent]] = []
|
345
|
-
prop_defs = []
|
346
|
-
prop_validators: list[tuple[expr.ExprIdent, expr.Expr]] = []
|
347
|
-
prop_constructor_assigns = []
|
348
|
-
# attrs needed for the cls.create function
|
349
|
-
create_args = []
|
350
|
-
update_args = []
|
351
|
-
ser_args = []
|
352
|
-
to_record_args = []
|
353
|
-
update_field_funcs: list[Callable[[AST], Any]] = []
|
354
|
-
|
355
|
-
prop2tsname = {}
|
356
|
-
|
357
|
-
for prop in cls.properties.values():
|
358
|
-
# if prop.data.is_private:
|
359
|
-
# # skip private fields as this is for APIs exchange
|
360
|
-
# continue
|
361
|
-
|
362
|
-
propname = to_camel_case(prop.name)
|
363
|
-
if isinstance(prop, ObjectProperty) and prop.target.db is not None:
|
364
|
-
propname = propname + "Id"
|
365
|
-
prop2tsname[prop.name] = propname
|
366
|
-
|
367
|
-
def _update_field_func(
|
368
|
-
prop: DataProperty | ObjectProperty,
|
369
|
-
propname: str,
|
370
|
-
tstype: TsTypeWithDep,
|
371
|
-
draft_clsname: str,
|
372
|
-
):
|
373
|
-
return lambda ast: ast(
|
374
|
-
stmt.LineBreak(),
|
375
|
-
lambda ast01: ast01.func(
|
376
|
-
f"update{to_pascal_case(prop.name)}",
|
377
|
-
[
|
378
|
-
DeferredVar.simple(
|
379
|
-
"value",
|
380
|
-
expr.ExprIdent(tstype.type),
|
381
|
-
),
|
382
|
-
],
|
383
|
-
expr.ExprIdent(draft_clsname),
|
384
|
-
comment=f"Update the `{prop.name}` field",
|
385
|
-
)(
|
386
|
-
stmt.AssignStatement(
|
387
|
-
PredefinedFn.attr_getter(
|
388
|
-
expr.ExprIdent("this"), expr.ExprIdent(propname)
|
389
|
-
),
|
390
|
-
expr.ExprIdent("value"),
|
391
|
-
),
|
392
|
-
stmt.AssignStatement(
|
393
|
-
PredefinedFn.attr_getter(
|
394
|
-
expr.ExprIdent("this"), expr.ExprIdent("stale")
|
395
|
-
),
|
396
|
-
expr.ExprConstant(True),
|
397
|
-
),
|
398
|
-
stmt.ReturnStatement(expr.ExprIdent("this")),
|
399
|
-
),
|
400
|
-
)
|
401
|
-
|
402
|
-
if isinstance(prop, DataProperty):
|
403
|
-
tstype = prop.get_data_model_datatype().get_typescript_type()
|
404
|
-
original_tstype = tstype
|
405
|
-
|
406
|
-
if idprop is not None and prop.name == idprop.name:
|
407
|
-
# use id type alias
|
408
|
-
tstype = TsTypeWithDep(
|
409
|
-
type=f"{cls.name}Id",
|
410
|
-
spectype=tstype.spectype,
|
411
|
-
deps=[f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id"],
|
412
|
-
)
|
413
|
-
elif tstype.type not in schema.enums:
|
414
|
-
# for none id & none enum properties, we need to include a type for "invalid" value
|
415
|
-
tstype = _inject_type_for_invalid_value(tstype)
|
416
|
-
|
417
|
-
if prop.is_optional:
|
418
|
-
# convert type to optional
|
419
|
-
tstype = tstype.as_optional_type()
|
420
|
-
original_tstype = original_tstype.as_optional_type()
|
421
|
-
|
422
|
-
for dep in tstype.deps:
|
423
|
-
program.import_(dep, True)
|
424
|
-
|
425
|
-
# however, if this is a primary key and auto-increment, we set a different default value
|
426
|
-
# to be -1 to avoid start from 0
|
427
|
-
if (
|
428
|
-
prop.db is not None
|
429
|
-
and prop.db.is_primary_key
|
430
|
-
and prop.db.is_auto_increment
|
431
|
-
):
|
432
|
-
create_propvalue = expr.ExprConstant(-1)
|
433
|
-
elif is_set(prop.data.default_value):
|
434
|
-
create_propvalue = expr.ExprConstant(prop.data.default_value)
|
435
|
-
else:
|
436
|
-
if tstype.type in idprop_aliases:
|
437
|
-
create_propvalue = idprop_aliases[tstype.type].get_default()
|
438
|
-
elif tstype.type in schema.enums:
|
439
|
-
enum_value_name = next(
|
440
|
-
iter(schema.enums[tstype.type].values.values())
|
441
|
-
).name
|
442
|
-
assert isinstance(enum_value_name, str), enum_value_name
|
443
|
-
create_propvalue = expr.ExprIdent(
|
444
|
-
tstype.type + "." + enum_value_name
|
445
|
-
)
|
446
|
-
else:
|
447
|
-
create_propvalue = tstype.get_default()
|
448
|
-
|
449
|
-
prop_validators.append(
|
450
|
-
(
|
451
|
-
expr.ExprIdent(propname),
|
452
|
-
expr.ExprFuncCall(
|
453
|
-
expr.ExprIdent("getValidator"),
|
454
|
-
[
|
455
|
-
PredefinedFn.list(
|
456
|
-
[
|
457
|
-
expr.ExprConstant(
|
458
|
-
constraint.get_typescript_constraint()
|
459
|
-
)
|
460
|
-
for constraint in prop.data.constraints
|
461
|
-
]
|
462
|
-
),
|
463
|
-
],
|
464
|
-
),
|
465
|
-
)
|
466
|
-
)
|
467
|
-
|
468
|
-
if prop.db is not None and prop.db.is_primary_key:
|
469
|
-
# for checking if the primary key is from the database or default (create_propvalue)
|
470
|
-
cls_pk = (expr.ExprIdent(propname), create_propvalue)
|
471
|
-
|
472
|
-
# if this field is private, we cannot get it from the normal record
|
473
|
-
# we have to create a default value for it.
|
474
|
-
if prop.data.is_private:
|
475
|
-
update_propvalue = create_propvalue
|
476
|
-
else:
|
477
|
-
update_propvalue = PredefinedFn.attr_getter(
|
478
|
-
expr.ExprIdent("record"), expr.ExprIdent(propname)
|
479
|
-
)
|
480
|
-
|
481
|
-
if (
|
482
|
-
original_tstype.type != tstype.type
|
483
|
-
and tstype.type != f"{cls.name}Id"
|
484
|
-
):
|
485
|
-
norm_func = get_norm_func(original_tstype, import_helper)
|
486
|
-
else:
|
487
|
-
norm_func = identity
|
488
|
-
|
489
|
-
ser_args.append(
|
490
|
-
(
|
491
|
-
expr.ExprIdent(prop.name),
|
492
|
-
(
|
493
|
-
expr.ExprTernary(
|
494
|
-
PredefinedFn.attr_getter(
|
495
|
-
expr.ExprFuncCall(
|
496
|
-
PredefinedFn.attr_getter(
|
497
|
-
expr.ExprIdent(draft_validators),
|
498
|
-
expr.ExprIdent(propname),
|
499
|
-
),
|
500
|
-
[
|
501
|
-
PredefinedFn.attr_getter(
|
502
|
-
expr.ExprIdent("this"),
|
503
|
-
expr.ExprIdent(propname),
|
504
|
-
)
|
505
|
-
],
|
506
|
-
),
|
507
|
-
expr.ExprIdent("isValid"),
|
508
|
-
),
|
509
|
-
original_tstype.get_json_ser_func(
|
510
|
-
norm_func(
|
511
|
-
PredefinedFn.attr_getter(
|
512
|
-
expr.ExprIdent("this"),
|
513
|
-
expr.ExprIdent(propname),
|
514
|
-
)
|
515
|
-
)
|
516
|
-
),
|
517
|
-
expr.ExprIdent("undefined"),
|
518
|
-
)
|
519
|
-
if prop.is_optional
|
520
|
-
else original_tstype.get_json_ser_func(
|
521
|
-
norm_func(
|
522
|
-
PredefinedFn.attr_getter(
|
523
|
-
expr.ExprIdent("this"), expr.ExprIdent(propname)
|
524
|
-
)
|
525
|
-
)
|
526
|
-
)
|
527
|
-
),
|
528
|
-
)
|
529
|
-
)
|
530
|
-
|
531
|
-
if not prop.data.is_private:
|
532
|
-
# private property does not include in the public record
|
533
|
-
to_record_args.append(
|
534
|
-
(
|
535
|
-
expr.ExprIdent(propname),
|
536
|
-
(
|
537
|
-
expr.ExprTernary(
|
538
|
-
PredefinedFn.attr_getter(
|
539
|
-
expr.ExprFuncCall(
|
540
|
-
PredefinedFn.attr_getter(
|
541
|
-
expr.ExprIdent(draft_validators),
|
542
|
-
expr.ExprIdent(propname),
|
543
|
-
),
|
544
|
-
[
|
545
|
-
PredefinedFn.attr_getter(
|
546
|
-
expr.ExprIdent("this"),
|
547
|
-
expr.ExprIdent(propname),
|
548
|
-
)
|
549
|
-
],
|
550
|
-
),
|
551
|
-
expr.ExprIdent("isValid"),
|
552
|
-
),
|
553
|
-
norm_func(
|
554
|
-
PredefinedFn.attr_getter(
|
555
|
-
expr.ExprIdent("this"),
|
556
|
-
expr.ExprIdent(propname),
|
557
|
-
)
|
558
|
-
),
|
559
|
-
expr.ExprIdent("undefined"),
|
560
|
-
)
|
561
|
-
if prop.is_optional
|
562
|
-
else norm_func(
|
563
|
-
PredefinedFn.attr_getter(
|
564
|
-
expr.ExprIdent("this"), expr.ExprIdent(propname)
|
565
|
-
)
|
566
|
-
)
|
567
|
-
),
|
568
|
-
)
|
569
|
-
)
|
570
|
-
if not (prop.db is not None and prop.db.is_primary_key):
|
571
|
-
# skip observable for primary key as it is not needed
|
572
|
-
observable_args.append(
|
573
|
-
(
|
574
|
-
expr.ExprIdent(propname),
|
575
|
-
expr.ExprIdent("observable"),
|
576
|
-
)
|
577
|
-
)
|
578
|
-
observable_args.append(
|
579
|
-
(
|
580
|
-
expr.ExprIdent(f"update{to_pascal_case(prop.name)}"),
|
581
|
-
expr.ExprIdent("action"),
|
582
|
-
)
|
583
|
-
)
|
584
|
-
else:
|
585
|
-
assert isinstance(prop, ObjectProperty)
|
586
|
-
if prop.target.db is not None:
|
587
|
-
# this class is stored in the database, we store the id instead
|
588
|
-
tstype = TsTypeWithDep(
|
589
|
-
type=f"{prop.target.name}Id",
|
590
|
-
spectype=assert_not_null(prop.target.get_id_property())
|
591
|
-
.get_data_model_datatype()
|
592
|
-
.get_typescript_type()
|
593
|
-
.spectype,
|
594
|
-
deps=[
|
595
|
-
f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
|
596
|
-
],
|
597
|
-
)
|
598
|
-
if prop.cardinality.is_star_to_many():
|
599
|
-
tstype = tstype.as_list_type()
|
600
|
-
create_propvalue = expr.ExprConstant([])
|
601
|
-
else:
|
602
|
-
if prop.is_optional:
|
603
|
-
# convert type to optional - for list type, we don't need to do this
|
604
|
-
# as we will use empty list as no value
|
605
|
-
tstype = tstype.as_optional_type()
|
606
|
-
# if target class has an auto-increment primary key, we set a different default value
|
607
|
-
# to be -1 to avoid start from 0
|
608
|
-
target_idprop = prop.target.get_id_property()
|
609
|
-
if (
|
610
|
-
target_idprop is not None
|
611
|
-
and target_idprop.db is not None
|
612
|
-
and target_idprop.db.is_primary_key
|
613
|
-
and target_idprop.db.is_auto_increment
|
614
|
-
):
|
615
|
-
create_propvalue = expr.ExprConstant(-1)
|
616
|
-
else:
|
617
|
-
assert tstype.type in idprop_aliases
|
618
|
-
create_propvalue = idprop_aliases[tstype.type].get_default()
|
619
|
-
|
620
|
-
update_propvalue = PredefinedFn.attr_getter(
|
621
|
-
expr.ExprIdent("record"), expr.ExprIdent(propname)
|
622
|
-
)
|
623
|
-
ser_args.append(
|
624
|
-
(
|
625
|
-
expr.ExprIdent(prop.name + "_id"),
|
626
|
-
PredefinedFn.attr_getter(
|
627
|
-
expr.ExprIdent("this"), expr.ExprIdent(propname)
|
628
|
-
),
|
629
|
-
)
|
630
|
-
)
|
631
|
-
|
632
|
-
if not prop.data.is_private:
|
633
|
-
# private property does not include in the public record
|
634
|
-
to_record_args.append(
|
635
|
-
(
|
636
|
-
expr.ExprIdent(propname),
|
637
|
-
PredefinedFn.attr_getter(
|
638
|
-
expr.ExprIdent("this"), expr.ExprIdent(propname)
|
639
|
-
),
|
640
|
-
)
|
641
|
-
)
|
642
|
-
else:
|
643
|
-
# we are going to store the whole object
|
644
|
-
tstype = TsTypeWithDep(
|
645
|
-
type=f"Draft{prop.target.name}",
|
646
|
-
spectype=f"Draft{prop.target.name}",
|
647
|
-
deps=[
|
648
|
-
f"@.models.{prop.target.get_tsmodule_name()}.Draft{prop.target.name}.Draft{prop.target.name}"
|
649
|
-
],
|
650
|
-
)
|
651
|
-
if prop.cardinality.is_star_to_many():
|
652
|
-
create_propvalue = expr.ExprConstant([])
|
653
|
-
update_propvalue = PredefinedFn.map_list(
|
654
|
-
PredefinedFn.attr_getter(
|
655
|
-
expr.ExprIdent("record"), expr.ExprIdent(propname)
|
656
|
-
),
|
657
|
-
lambda item: expr.ExprMethodCall(
|
658
|
-
expr.ExprIdent(tstype.type),
|
659
|
-
"update",
|
660
|
-
[item],
|
661
|
-
),
|
662
|
-
)
|
663
|
-
ser_args.append(
|
664
|
-
(
|
665
|
-
expr.ExprIdent(prop.name),
|
666
|
-
PredefinedFn.map_list(
|
667
|
-
PredefinedFn.attr_getter(
|
668
|
-
expr.ExprIdent("this"), expr.ExprIdent(propname)
|
669
|
-
),
|
670
|
-
lambda item: expr.ExprMethodCall(item, "ser", []),
|
671
|
-
(
|
672
|
-
(
|
673
|
-
lambda item: PredefinedFn.attr_getter(
|
674
|
-
expr.ExprFuncCall(
|
675
|
-
PredefinedFn.attr_getter(
|
676
|
-
expr.ExprIdent(
|
677
|
-
draft_validators
|
678
|
-
),
|
679
|
-
expr.ExprIdent(propname),
|
680
|
-
),
|
681
|
-
[item],
|
682
|
-
),
|
683
|
-
expr.ExprIdent("isValid"),
|
684
|
-
)
|
685
|
-
)
|
686
|
-
if prop.is_optional
|
687
|
-
else None
|
688
|
-
),
|
689
|
-
),
|
690
|
-
)
|
691
|
-
)
|
692
|
-
|
693
|
-
if not prop.data.is_private:
|
694
|
-
# private property does not include in the public record
|
695
|
-
to_record_args.append(
|
696
|
-
(
|
697
|
-
expr.ExprIdent(propname),
|
698
|
-
PredefinedFn.map_list(
|
699
|
-
PredefinedFn.attr_getter(
|
700
|
-
expr.ExprIdent("this"),
|
701
|
-
expr.ExprIdent(propname),
|
702
|
-
),
|
703
|
-
lambda item: expr.ExprMethodCall(
|
704
|
-
item, "toRecord", []
|
705
|
-
),
|
706
|
-
(
|
707
|
-
(
|
708
|
-
lambda item: PredefinedFn.attr_getter(
|
709
|
-
expr.ExprFuncCall(
|
710
|
-
PredefinedFn.attr_getter(
|
711
|
-
expr.ExprIdent(
|
712
|
-
draft_validators
|
713
|
-
),
|
714
|
-
expr.ExprIdent(propname),
|
715
|
-
),
|
716
|
-
[item],
|
717
|
-
),
|
718
|
-
expr.ExprIdent("isValid"),
|
719
|
-
)
|
720
|
-
)
|
721
|
-
if prop.is_optional
|
722
|
-
else None
|
723
|
-
),
|
724
|
-
),
|
725
|
-
)
|
726
|
-
)
|
727
|
-
|
728
|
-
tstype = tstype.as_list_type()
|
729
|
-
else:
|
730
|
-
create_propvalue = expr.ExprMethodCall(
|
731
|
-
expr.ExprIdent(tstype.type),
|
732
|
-
"create",
|
733
|
-
[],
|
734
|
-
)
|
735
|
-
update_propvalue = expr.ExprMethodCall(
|
736
|
-
expr.ExprIdent(tstype.type),
|
737
|
-
"update",
|
738
|
-
[
|
739
|
-
PredefinedFn.attr_getter(
|
740
|
-
expr.ExprIdent("record"), expr.ExprIdent(propname)
|
741
|
-
),
|
742
|
-
],
|
743
|
-
)
|
744
|
-
|
745
|
-
if prop.is_optional:
|
746
|
-
ser_args.append(
|
747
|
-
(
|
748
|
-
expr.ExprIdent(prop.name),
|
749
|
-
expr.ExprTernary(
|
750
|
-
PredefinedFn.attr_getter(
|
751
|
-
expr.ExprFuncCall(
|
752
|
-
PredefinedFn.attr_getter(
|
753
|
-
expr.ExprIdent(draft_validators),
|
754
|
-
expr.ExprIdent(propname),
|
755
|
-
),
|
756
|
-
[
|
757
|
-
PredefinedFn.attr_getter(
|
758
|
-
expr.ExprIdent("this"),
|
759
|
-
expr.ExprIdent(propname),
|
760
|
-
)
|
761
|
-
],
|
762
|
-
),
|
763
|
-
expr.ExprIdent("isValid"),
|
764
|
-
),
|
765
|
-
expr.ExprMethodCall(
|
766
|
-
PredefinedFn.attr_getter(
|
767
|
-
expr.ExprIdent("this"),
|
768
|
-
expr.ExprIdent(propname),
|
769
|
-
),
|
770
|
-
"ser",
|
771
|
-
[],
|
772
|
-
),
|
773
|
-
expr.ExprIdent("undefined"),
|
774
|
-
),
|
775
|
-
)
|
776
|
-
)
|
777
|
-
if not prop.data.is_private:
|
778
|
-
# private property does not include in the public record
|
779
|
-
to_record_args.append(
|
780
|
-
(
|
781
|
-
expr.ExprIdent(propname),
|
782
|
-
expr.ExprTernary(
|
783
|
-
PredefinedFn.attr_getter(
|
784
|
-
expr.ExprFuncCall(
|
785
|
-
PredefinedFn.attr_getter(
|
786
|
-
expr.ExprIdent(
|
787
|
-
draft_validators
|
788
|
-
),
|
789
|
-
expr.ExprIdent(propname),
|
790
|
-
),
|
791
|
-
[
|
792
|
-
PredefinedFn.attr_getter(
|
793
|
-
expr.ExprIdent("this"),
|
794
|
-
expr.ExprIdent(propname),
|
795
|
-
)
|
796
|
-
],
|
797
|
-
),
|
798
|
-
expr.ExprIdent("isValid"),
|
799
|
-
),
|
800
|
-
expr.ExprMethodCall(
|
801
|
-
PredefinedFn.attr_getter(
|
802
|
-
expr.ExprIdent("this"),
|
803
|
-
expr.ExprIdent(propname),
|
804
|
-
),
|
805
|
-
"toRecord",
|
806
|
-
[],
|
807
|
-
),
|
808
|
-
expr.ExprIdent("undefined"),
|
809
|
-
),
|
810
|
-
)
|
811
|
-
)
|
812
|
-
else:
|
813
|
-
ser_args.append(
|
814
|
-
(
|
815
|
-
expr.ExprIdent(prop.name),
|
816
|
-
expr.ExprMethodCall(
|
817
|
-
PredefinedFn.attr_getter(
|
818
|
-
expr.ExprIdent("this"),
|
819
|
-
expr.ExprIdent(propname),
|
820
|
-
),
|
821
|
-
"ser",
|
822
|
-
[],
|
823
|
-
),
|
824
|
-
)
|
825
|
-
)
|
826
|
-
if not prop.data.is_private:
|
827
|
-
# private property does not include in the public record
|
828
|
-
to_record_args.append(
|
829
|
-
(
|
830
|
-
expr.ExprIdent(propname),
|
831
|
-
expr.ExprMethodCall(
|
832
|
-
PredefinedFn.attr_getter(
|
833
|
-
expr.ExprIdent("this"),
|
834
|
-
expr.ExprIdent(propname),
|
835
|
-
),
|
836
|
-
"toRecord",
|
837
|
-
[],
|
838
|
-
),
|
839
|
-
)
|
840
|
-
)
|
841
|
-
|
842
|
-
if prop.is_optional:
|
843
|
-
# convert type to optional - for list type, we don't need to do this
|
844
|
-
# as we will use empty list as no value
|
845
|
-
tstype = tstype.as_optional_type()
|
846
|
-
|
847
|
-
for dep in tstype.deps:
|
848
|
-
program.import_(
|
849
|
-
dep,
|
850
|
-
True,
|
851
|
-
)
|
852
|
-
|
853
|
-
observable_args.append(
|
854
|
-
(
|
855
|
-
expr.ExprIdent(propname),
|
856
|
-
expr.ExprIdent("observable"),
|
857
|
-
)
|
858
|
-
)
|
859
|
-
observable_args.append(
|
860
|
-
(
|
861
|
-
expr.ExprIdent(f"update{to_pascal_case(prop.name)}"),
|
862
|
-
expr.ExprIdent("action"),
|
863
|
-
)
|
864
|
-
)
|
865
|
-
|
866
|
-
# TODO: fix me! fix me what?? next time give more context.
|
867
|
-
prop_validators.append(
|
868
|
-
(
|
869
|
-
expr.ExprIdent(propname),
|
870
|
-
expr.ExprFuncCall(
|
871
|
-
expr.ExprIdent("getValidator"),
|
872
|
-
[
|
873
|
-
PredefinedFn.list(
|
874
|
-
[
|
875
|
-
expr.ExprConstant(
|
876
|
-
constraint.get_typescript_constraint()
|
877
|
-
)
|
878
|
-
for constraint in prop.data.constraints
|
879
|
-
]
|
880
|
-
),
|
881
|
-
],
|
882
|
-
),
|
883
|
-
)
|
884
|
-
)
|
885
|
-
|
886
|
-
prop_defs.append(stmt.DefClassVarStatement(propname, tstype.type))
|
887
|
-
prop_constructor_assigns.append(
|
888
|
-
stmt.AssignStatement(
|
889
|
-
PredefinedFn.attr_getter(
|
890
|
-
expr.ExprIdent("this"),
|
891
|
-
expr.ExprIdent(propname),
|
892
|
-
),
|
893
|
-
expr.ExprIdent("args." + propname),
|
894
|
-
)
|
895
|
-
)
|
896
|
-
create_args.append((expr.ExprIdent(propname), create_propvalue))
|
897
|
-
update_args.append(
|
898
|
-
(
|
899
|
-
expr.ExprIdent(propname),
|
900
|
-
# if this is mutable property, we need to copy to make it immutable.
|
901
|
-
clone_prop(prop, update_propvalue),
|
902
|
-
)
|
903
|
-
)
|
904
|
-
update_field_funcs.append(
|
905
|
-
_update_field_func(prop, propname, tstype, draft_clsname)
|
906
|
-
)
|
907
|
-
|
908
|
-
prop_defs.append(stmt.DefClassVarStatement("stale", "boolean"))
|
909
|
-
prop_constructor_assigns.append(
|
910
|
-
stmt.AssignStatement(
|
911
|
-
PredefinedFn.attr_getter(
|
912
|
-
expr.ExprIdent("this"), expr.ExprIdent("stale")
|
913
|
-
),
|
914
|
-
expr.ExprIdent("args.stale"),
|
915
|
-
)
|
916
|
-
)
|
917
|
-
observable_args.append(
|
918
|
-
(
|
919
|
-
expr.ExprIdent("stale"),
|
920
|
-
expr.ExprIdent("observable"),
|
921
|
-
)
|
922
|
-
)
|
923
|
-
create_args.append(
|
924
|
-
(
|
925
|
-
expr.ExprIdent("stale"),
|
926
|
-
expr.ExprConstant(True),
|
927
|
-
),
|
928
|
-
)
|
929
|
-
update_args.append(
|
930
|
-
(
|
931
|
-
expr.ExprIdent("stale"),
|
932
|
-
expr.ExprConstant(False),
|
933
|
-
),
|
934
|
-
)
|
935
|
-
observable_args.sort(key=lambda x: {"observable": 0, "action": 1}[x[1].ident])
|
936
|
-
|
937
|
-
validators = expr.ExprFuncCall(
|
938
|
-
expr.ExprIdent("memoizeOneValidators"), [PredefinedFn.dict(prop_validators)]
|
939
|
-
)
|
940
|
-
|
941
|
-
program.root(
|
942
|
-
lambda ast00: ast00.class_like(
|
943
|
-
"interface",
|
944
|
-
draft_clsname + "ConstructorArgs",
|
945
|
-
)(*prop_defs),
|
946
|
-
stmt.LineBreak(),
|
947
|
-
lambda ast10: ast10.class_(draft_clsname)(
|
948
|
-
*prop_defs,
|
949
|
-
stmt.LineBreak(),
|
950
|
-
lambda ast10: ast10.func(
|
951
|
-
"constructor",
|
952
|
-
[
|
953
|
-
DeferredVar.simple(
|
954
|
-
"args",
|
955
|
-
expr.ExprIdent(draft_clsname + "ConstructorArgs"),
|
956
|
-
),
|
957
|
-
],
|
958
|
-
)(
|
959
|
-
*prop_constructor_assigns,
|
960
|
-
stmt.LineBreak(),
|
961
|
-
stmt.SingleExprStatement(
|
962
|
-
expr.ExprFuncCall(
|
963
|
-
expr.ExprIdent("makeObservable"),
|
964
|
-
[
|
965
|
-
expr.ExprIdent("this"),
|
966
|
-
PredefinedFn.dict(observable_args),
|
967
|
-
],
|
968
|
-
)
|
969
|
-
),
|
970
|
-
),
|
971
|
-
stmt.LineBreak(),
|
972
|
-
lambda ast11: (
|
973
|
-
ast11.func(
|
974
|
-
"isNewRecord",
|
975
|
-
[],
|
976
|
-
expr.ExprIdent("boolean"),
|
977
|
-
comment="Check if this draft is for creating a new record",
|
978
|
-
)(
|
979
|
-
stmt.ReturnStatement(
|
980
|
-
expr.ExprEqual(
|
981
|
-
PredefinedFn.attr_getter(
|
982
|
-
expr.ExprIdent("this"), cls_pk[0]
|
983
|
-
),
|
984
|
-
cls_pk[1],
|
985
|
-
)
|
986
|
-
)
|
987
|
-
)
|
988
|
-
if cls_pk is not None
|
989
|
-
else None
|
990
|
-
),
|
991
|
-
stmt.LineBreak(),
|
992
|
-
lambda ast12: ast12.func(
|
993
|
-
"create",
|
994
|
-
[],
|
995
|
-
expr.ExprIdent(draft_clsname),
|
996
|
-
is_static=True,
|
997
|
-
comment="Make a new draft for creating a new record",
|
998
|
-
)(
|
999
|
-
stmt.ReturnStatement(
|
1000
|
-
expr.ExprNewInstance(
|
1001
|
-
expr.ExprIdent(draft_clsname),
|
1002
|
-
[PredefinedFn.dict(create_args)],
|
1003
|
-
)
|
1004
|
-
),
|
1005
|
-
),
|
1006
|
-
stmt.LineBreak(),
|
1007
|
-
lambda ast13: ast13.func(
|
1008
|
-
"update",
|
1009
|
-
[DeferredVar.simple("record", expr.ExprIdent(cls.name))],
|
1010
|
-
expr.ExprIdent(draft_clsname),
|
1011
|
-
is_static=True,
|
1012
|
-
comment="Make a new draft for updating an existing record",
|
1013
|
-
)(
|
1014
|
-
stmt.ReturnStatement(
|
1015
|
-
expr.ExprNewInstance(
|
1016
|
-
expr.ExprIdent(draft_clsname),
|
1017
|
-
[PredefinedFn.dict(update_args)],
|
1018
|
-
)
|
1019
|
-
),
|
1020
|
-
),
|
1021
|
-
*update_field_funcs,
|
1022
|
-
stmt.LineBreak(),
|
1023
|
-
lambda ast14: ast14.func(
|
1024
|
-
"isValid",
|
1025
|
-
[],
|
1026
|
-
expr.ExprIdent("boolean"),
|
1027
|
-
comment="Check if the draft is valid (only check the required fields as the non-required fields if it's invalid will be set to undefined)",
|
1028
|
-
)(
|
1029
|
-
stmt.ReturnStatement(
|
1030
|
-
expr.ExprRawTypescript(
|
1031
|
-
" && ".join(
|
1032
|
-
f"{draft_validators}.{prop2tsname[prop.name]}(this.{prop2tsname[prop.name]}).isValid"
|
1033
|
-
for prop in cls.properties.values()
|
1034
|
-
if not prop.is_optional
|
1035
|
-
)
|
1036
|
-
if any(
|
1037
|
-
not prop.is_optional for prop in cls.properties.values()
|
1038
|
-
)
|
1039
|
-
else "true"
|
1040
|
-
)
|
1041
|
-
)
|
1042
|
-
),
|
1043
|
-
stmt.LineBreak(),
|
1044
|
-
lambda ast15: ast15.func(
|
1045
|
-
"ser",
|
1046
|
-
[],
|
1047
|
-
expr.ExprIdent("any"),
|
1048
|
-
comment="Serialize the draft to communicate with the server. `isValid` must be called first to ensure all data is valid",
|
1049
|
-
)(
|
1050
|
-
stmt.ReturnStatement(
|
1051
|
-
PredefinedFn.dict(ser_args),
|
1052
|
-
),
|
1053
|
-
),
|
1054
|
-
stmt.LineBreak(),
|
1055
|
-
lambda ast16: ast16.func(
|
1056
|
-
"toRecord",
|
1057
|
-
[],
|
1058
|
-
expr.ExprIdent(cls.name),
|
1059
|
-
comment="Convert the draft to a normal record. `isValid` must be called first to ensure all data is valid",
|
1060
|
-
)(
|
1061
|
-
stmt.ReturnStatement(
|
1062
|
-
expr.ExprNewInstance(
|
1063
|
-
expr.ExprIdent(cls.name),
|
1064
|
-
[PredefinedFn.dict(to_record_args)],
|
1065
|
-
),
|
1066
|
-
)
|
1067
|
-
),
|
1068
|
-
),
|
1069
|
-
stmt.LineBreak(),
|
1070
|
-
stmt.TypescriptStatement(
|
1071
|
-
f"export const {draft_validators} = " + validators.to_typescript() + ";"
|
1072
|
-
),
|
1073
|
-
)
|
1074
|
-
|
1075
|
-
pkg.module("Draft" + cls.name).write(program)
|
1076
|
-
|
1077
295
|
def make_table(cls: Class, pkg: Package):
|
1078
296
|
if not cls.is_public or cls.db is None:
|
1079
297
|
# skip classes that are not public and not stored in the database
|
@@ -1201,7 +419,7 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
1201
419
|
for cls in schema.topological_sort():
|
1202
420
|
pkg = target_pkg.pkg(cls.get_tsmodule_name())
|
1203
421
|
make_normal(cls, pkg)
|
1204
|
-
make_draft(cls, pkg)
|
422
|
+
make_draft(schema, cls, pkg, idprop_aliases)
|
1205
423
|
make_query(schema, cls, pkg)
|
1206
424
|
make_table(cls, pkg)
|
1207
425
|
make_class_schema(schema, cls, pkg)
|
@@ -1245,75 +463,3 @@ def make_typescript_enum(schema: Schema, target_pkg: Package):
|
|
1245
463
|
),
|
1246
464
|
)
|
1247
465
|
enum_pkg.module("index").write(program)
|
1248
|
-
|
1249
|
-
|
1250
|
-
def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
|
1251
|
-
"""
|
1252
|
-
Inject a type for "invalid" values into the given TypeScript type. For context, see the discussion in Data Modeling Problems:
|
1253
|
-
What would be an appropriate type for an invalid value? Since it's user input, it will be a string type.
|
1254
|
-
|
1255
|
-
However, there are some exceptions such as boolean type, which will always be valid and do not need injection.
|
1256
|
-
|
1257
|
-
If the type already includes `string` type, no changes are needed. Otherwise, we add `string` to the type. For example:
|
1258
|
-
- (number | undefined) -> (number | undefined | string)
|
1259
|
-
- number | undefined -> number | undefined | string
|
1260
|
-
- number[] -> (number | string)[]
|
1261
|
-
- (number | undefined)[] -> (number | undefined | string)[]
|
1262
|
-
"""
|
1263
|
-
if tstype.type == "boolean":
|
1264
|
-
return tstype
|
1265
|
-
|
1266
|
-
# TODO: fix me and make it more robust!
|
1267
|
-
m = re.match(r"(\(?[a-zA-Z \|]+\)?)(\[\])", tstype.type)
|
1268
|
-
if m is not None:
|
1269
|
-
# This is an array type, add string to the inner type
|
1270
|
-
inner_type = m.group(1)
|
1271
|
-
inner_spectype = assert_not_null(
|
1272
|
-
re.match(r"(\(?[a-zA-Z \|]+\)?)(\[\])", tstype.spectype)
|
1273
|
-
).group(1)
|
1274
|
-
if "string" not in inner_type:
|
1275
|
-
if inner_type.startswith("(") and inner_type.endswith(")"):
|
1276
|
-
# Already has parentheses
|
1277
|
-
inner_type = f"{inner_type[:-1]} | string)"
|
1278
|
-
inner_spectype = f"{inner_spectype[:-1]} | string)"
|
1279
|
-
else:
|
1280
|
-
# Need to add parentheses
|
1281
|
-
inner_type = f"({inner_type} | string)"
|
1282
|
-
inner_spectype = f"({inner_spectype} | string)"
|
1283
|
-
return TsTypeWithDep(inner_type + "[]", inner_spectype + "[]", tstype.deps)
|
1284
|
-
|
1285
|
-
m = re.match(r"^\(?[a-zA-Z \|]+\)?$", tstype.type)
|
1286
|
-
if m is not None:
|
1287
|
-
if "string" not in tstype.type:
|
1288
|
-
if tstype.type.startswith("(") and tstype.type.endswith(")"):
|
1289
|
-
# Already has parentheses
|
1290
|
-
new_type = f"{tstype.type[:-1]} | string)"
|
1291
|
-
new_spectype = f"{tstype.spectype[:-1]} | string)"
|
1292
|
-
else:
|
1293
|
-
# Needs parentheses for clarity
|
1294
|
-
new_type = f"({tstype.type} | string)"
|
1295
|
-
new_spectype = f"({tstype.spectype} | string)"
|
1296
|
-
return TsTypeWithDep(new_type, new_spectype, tstype.deps)
|
1297
|
-
return tstype
|
1298
|
-
|
1299
|
-
raise NotImplementedError(tstype.type)
|
1300
|
-
|
1301
|
-
|
1302
|
-
def get_norm_func(
|
1303
|
-
tstype: TsTypeWithDep, import_helper: ImportHelper
|
1304
|
-
) -> Callable[[expr.Expr], expr.Expr]:
|
1305
|
-
"""
|
1306
|
-
Get the normalizer function for the given TypeScript type.
|
1307
|
-
If no normalizer is available, return None.
|
1308
|
-
"""
|
1309
|
-
norm_func = get_normalizer(tstype, import_helper)
|
1310
|
-
if norm_func is not None:
|
1311
|
-
|
1312
|
-
def modify_expr(value: expr.Expr) -> expr.Expr:
|
1313
|
-
return expr.ExprFuncCall(
|
1314
|
-
norm_func,
|
1315
|
-
[value],
|
1316
|
-
)
|
1317
|
-
|
1318
|
-
return modify_expr
|
1319
|
-
return identity # Return the value as is if no normalizer is available
|