sera-2 1.7.0__py3-none-any.whl → 1.7.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.
File without changes
sera/libs/dag/_dag.py ADDED
File without changes
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  from typing import Any, Callable
4
5
 
5
6
  from codegen.models import AST, PredefinedFn, Program, expr, stmt
6
7
  from codegen.models.var import DeferredVar
7
8
  from loguru import logger
9
+
8
10
  from sera.misc import (
9
11
  assert_isinstance,
10
12
  assert_not_null,
@@ -239,8 +241,15 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
239
241
  program.import_("mobx.makeObservable", True)
240
242
  program.import_("mobx.observable", True)
241
243
  program.import_("mobx.action", True)
244
+ program.import_("sera-db.validators", True)
242
245
 
243
- program.root.linebreak()
246
+ program.root(
247
+ stmt.LineBreak(),
248
+ stmt.TypescriptStatement(
249
+ "const {getValidator, memoizeOneValidators} = validators;"
250
+ ),
251
+ stmt.LineBreak(),
252
+ )
244
253
 
245
254
  # make sure that the property stale is not in existing properties
246
255
  if "stale" in cls.properties:
@@ -250,6 +259,7 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
250
259
  cls_pk = None
251
260
  observable_args: list[tuple[expr.Expr, expr.ExprIdent]] = []
252
261
  prop_defs = []
262
+ prop_validators = []
253
263
  prop_constructor_assigns = []
254
264
  # attrs needed for the cls.create function
255
265
  create_args = []
@@ -307,6 +317,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
307
317
  f"{cls.name}Id",
308
318
  dep=f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id",
309
319
  )
320
+ else:
321
+ # for none id properties, we need to include a type for "invalid" value
322
+ tstype = _inject_type_for_invalid_value(tstype)
323
+
310
324
  if tstype.dep is not None:
311
325
  program.import_(tstype.dep, True)
312
326
 
@@ -324,6 +338,25 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
324
338
  else:
325
339
  create_propvalue = tstype.get_default()
326
340
 
341
+ prop_validators.append(
342
+ (
343
+ expr.ExprIdent(propname),
344
+ expr.ExprFuncCall(
345
+ expr.ExprIdent("getValidator"),
346
+ [
347
+ PredefinedFn.list(
348
+ [
349
+ expr.ExprConstant(
350
+ constraint.get_typescript_constraint()
351
+ )
352
+ for constraint in prop.data.constraints
353
+ ]
354
+ ),
355
+ ],
356
+ ),
357
+ )
358
+ )
359
+
327
360
  if prop.db is not None and prop.db.is_primary_key:
328
361
  # for checking if the primary key is from the database or default (create_propvalue)
329
362
  cls_pk = (expr.ExprIdent(propname), create_propvalue)
@@ -524,6 +557,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
524
557
  )
525
558
  observable_args.sort(key=lambda x: {"observable": 0, "action": 1}[x[1].ident])
526
559
 
560
+ validators = expr.ExprFuncCall(
561
+ expr.ExprIdent("memoizeOneValidators"), [PredefinedFn.dict(prop_validators)]
562
+ )
563
+
527
564
  program.root(
528
565
  lambda ast00: ast00.class_like(
529
566
  "interface",
@@ -533,6 +570,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
533
570
  lambda ast10: ast10.class_(draft_clsname)(
534
571
  *prop_defs,
535
572
  stmt.LineBreak(),
573
+ stmt.DefClassVarStatement(
574
+ "validators", type=None, value=validators, is_static=True
575
+ ),
576
+ stmt.LineBreak(),
536
577
  lambda ast10: ast10.func(
537
578
  "constructor",
538
579
  [
@@ -822,7 +863,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
822
863
  [
823
864
  (expr.ExprIdent("name"), expr.ExprConstant(prop.name)),
824
865
  (expr.ExprIdent("tsName"), expr.ExprConstant(propname)),
825
- (expr.ExprIdent("updateFuncName"), expr.ExprConstant(f"update{to_pascal_case(prop.name)}")),
866
+ (
867
+ expr.ExprIdent("updateFuncName"),
868
+ expr.ExprConstant(f"update{to_pascal_case(prop.name)}"),
869
+ ),
826
870
  (
827
871
  expr.ExprIdent("label"),
828
872
  expr.ExprConstant(prop.label.to_dict()),
@@ -1069,3 +1113,48 @@ def make_typescript_enum(schema: Schema, target_pkg: Package):
1069
1113
  ),
1070
1114
  )
1071
1115
  enum_pkg.module("index").write(program)
1116
+
1117
+
1118
+ def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
1119
+ """
1120
+ Inject a type for "invalid" values into the given TypeScript type. For context, see the discussion in Data Modeling Problems:
1121
+ What would be an appropriate type for an invalid value? Since it's user input, it will be a string type.
1122
+
1123
+ However, there are some exceptions such as boolean type, which will always be valid and do not need injection.
1124
+
1125
+ If the type already includes `string` type, no changes are needed. Otherwise, we add `string` to the type. For example:
1126
+ - (number | undefined) -> (number | undefined | string)
1127
+ - number | undefined -> number | undefined | string
1128
+ - number[] -> (number | string)[]
1129
+ - (number | undefined)[] -> (number | undefined | string)[]
1130
+ """
1131
+ if tstype.type == "boolean":
1132
+ return tstype
1133
+
1134
+ # TODO: fix me and make it more robust!
1135
+ m = re.match(r"(\(?[a-zA-Z \|]+\)?)(\[\])", tstype.type)
1136
+ if m is not None:
1137
+ # This is an array type, add string to the inner type
1138
+ inner_type = m.group(1)
1139
+ if "string" not in inner_type:
1140
+ if inner_type.startswith("(") and inner_type.endswith(")"):
1141
+ # Already has parentheses
1142
+ inner_type = f"{inner_type[:-1]} | string)"
1143
+ else:
1144
+ # Need to add parentheses
1145
+ inner_type = f"({inner_type} | string)"
1146
+ return TsTypeWithDep(inner_type + "[]", tstype.dep)
1147
+
1148
+ m = re.match(r"^\(?[a-zA-Z \|]+\)?$", tstype.type)
1149
+ if m is not None:
1150
+ if "string" not in tstype.type:
1151
+ if tstype.type.startswith("(") and tstype.type.endswith(")"):
1152
+ # Already has parentheses
1153
+ new_type = f"{tstype.type[:-1]} | string)"
1154
+ else:
1155
+ # Needs parentheses for clarity
1156
+ new_type = f"({tstype.type} | string)"
1157
+ return TsTypeWithDep(new_type, tstype.dep)
1158
+ return tstype
1159
+
1160
+ raise NotImplementedError(tstype.type)
sera/models/_datatype.py CHANGED
@@ -69,10 +69,21 @@ class TsTypeWithDep:
69
69
  return expr.ExprConstant(False)
70
70
  if self.type == "string | undefined":
71
71
  return expr.ExprConstant("undefined")
72
+ if self.type.endswith("| string)") or self.type.endswith("| string"):
73
+ return expr.ExprConstant("")
72
74
  raise ValueError(f"Unknown type: {self.type}")
73
75
 
74
76
  def as_list_type(self) -> TsTypeWithDep:
75
- return TsTypeWithDep(type=f"{self.type}[]", dep=self.dep)
77
+ """Convert the type to a list type.
78
+ If the type is not a simple identifier, wrap it in parentheses.
79
+ """
80
+ # Check if type is a simple identifier or needs parentheses
81
+ if not all(c.isalnum() or c == "_" for c in self.type.strip()):
82
+ # Type contains special chars like | or spaces, wrap in parentheses
83
+ list_type = f"({self.type})[]"
84
+ else:
85
+ list_type = f"{self.type}[]"
86
+ return TsTypeWithDep(type=list_type, dep=self.dep)
76
87
 
77
88
 
78
89
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -4,13 +4,15 @@ sera/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  sera/libs/api_helper.py,sha256=hUEy0INHM18lxTQ348tgbXNceOHcjiAnqmuL_8CRpLQ,2509
5
5
  sera/libs/base_orm.py,sha256=dyh0OT2sbHku5qPJXvRzYAHRTSXvvbQaS-Qwg65Bw04,2918
6
6
  sera/libs/base_service.py,sha256=l5D4IjxIiz8LBRranUYddb8J0Y6SwSyetKYTLrCUdQA,4098
7
+ sera/libs/dag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ sera/libs/dag/_dag.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
9
  sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
10
  sera/make/__main__.py,sha256=G5O7s6135-708honwqMFn2yPTs06WbGQTHpupID0eZ4,1417
9
11
  sera/make/make_app.py,sha256=n9NtW73O3s_5Q31VHIRmnd-jEIcpDO7ksAsOdovde2s,5999
10
12
  sera/make/make_python_api.py,sha256=RuJUm9z-4plBEtjobeOPr12o27OT-0tSeXI4ZlM3IY0,29433
11
13
  sera/make/make_python_model.py,sha256=xf4revAwVWEnF6QhxbbqPyUGgXOOB--Gu3jPxsESg0Y,36593
12
14
  sera/make/make_python_services.py,sha256=RsinYZdfkrTlTn9CT50VgqGs9w6IZawsJx-KEmqfnEY,2062
13
- sera/make/make_typescript_model.py,sha256=k69A0AtifoyO9zOaIZFInGSdBETuqpApggTdLiihFo0,43231
15
+ sera/make/make_typescript_model.py,sha256=27lk4YgfJ6_7xxBirxJKgK0-wBNowMWZkvcMUFdmStE,46865
14
16
  sera/misc/__init__.py,sha256=Dh4uDq0D4N53h3zhvmwfa5a0TPVRSUvLzb0hkFuPirk,411
15
17
  sera/misc/_formatter.py,sha256=aCGYL08l8f3aLODHxSocxBBwkRYEo3K1QzCDEn3suj0,1685
16
18
  sera/misc/_utils.py,sha256=V5g4oLGHOhUCR75Kkcn1w01pAvGvaepK-T8Z3pIgHjI,1450
@@ -18,7 +20,7 @@ sera/models/__init__.py,sha256=vJC5Kzo_N7wd16ocNPy1VvAZDGNiWeiAhWJ4ihATKvA,780
18
20
  sera/models/_class.py,sha256=Wf0e8x6-szG9TzoFkAlqj7_dG0SCICMBw_333n3paxk,2514
19
21
  sera/models/_collection.py,sha256=ZnQEriKC4X88Zz48Kn1AVZKH-1_l8OgWa-zf2kcQOOE,1414
20
22
  sera/models/_constraints.py,sha256=L6QwKL3hRJ5DvvIB4JNgLoyvTKbE9FrpYRzezM4CweE,1484
21
- sera/models/_datatype.py,sha256=uMxK_8wBLowaIMIAYCb3V17YmkzJrKKc5orjImzqWbA,5818
23
+ sera/models/_datatype.py,sha256=eF71q-X7LA91sNYG29aXQiDVMjjr8-e6ez7P9NRwq_E,6376
22
24
  sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
23
25
  sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
24
26
  sera/models/_module.py,sha256=8QRSCubZmdDP9rL58rGAS6X5VCrkc1ZHvuMu1I1KrWk,5043
@@ -27,6 +29,6 @@ sera/models/_parse.py,sha256=sJYfQtwek96ltpgxExG4xUbiLnU3qvNYhTP1CeyXGjs,9746
27
29
  sera/models/_property.py,sha256=CmEmgOShtSyNFq05YW3tGupwCIVRzPMKudXWld8utPk,5530
28
30
  sera/models/_schema.py,sha256=r-Gqg9Lb_wR3UrbNvfXXgt_qs5bts0t2Ve7aquuF_OI,1155
29
31
  sera/typing.py,sha256=Q4QMfbtfrCjC9tFfsZPhsAnbNX4lm4NHQ9lmjNXYdV0,772
30
- sera_2-1.7.0.dist-info/METADATA,sha256=vRXwLTHQXWHB9gV7jz5kJOtM0HPfhRrTlkPcS3rO1Gw,856
31
- sera_2-1.7.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- sera_2-1.7.0.dist-info/RECORD,,
32
+ sera_2-1.7.2.dist-info/METADATA,sha256=xzCJfINCVbrv5Xd3oAKXJnhF_tYm7A51PpdTpdVqUW8,856
33
+ sera_2-1.7.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
34
+ sera_2-1.7.2.dist-info/RECORD,,
File without changes