sera-2 1.9.1__py3-none-any.whl → 1.11.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.
@@ -190,6 +190,37 @@ def make_python_data_model(
190
190
  True,
191
191
  alias=f"{cls.name}DB",
192
192
  )
193
+
194
+ has_system_controlled = any(
195
+ prop.data.is_system_controlled for prop in cls.properties.values()
196
+ )
197
+ if has_system_controlled:
198
+ program.import_("typing.TypedDict", True)
199
+ program.root(
200
+ stmt.LineBreak(),
201
+ lambda ast: ast.class_(
202
+ "SystemControlledProps",
203
+ [expr.ExprIdent("TypedDict")],
204
+ )(
205
+ *[
206
+ stmt.DefClassVarStatement(
207
+ prop.name,
208
+ (
209
+ prop.get_data_model_datatype().get_python_type().type
210
+ if isinstance(prop, DataProperty)
211
+ else assert_not_null(prop.target.get_id_property())
212
+ .get_data_model_datatype()
213
+ .get_python_type()
214
+ .type
215
+ ),
216
+ )
217
+ for prop in cls.properties.values()
218
+ if prop.data.is_system_controlled
219
+ ],
220
+ ),
221
+ )
222
+
223
+ program.root.linebreak()
193
224
  cls_ast = program.root.class_(
194
225
  "Upsert" + cls.name,
195
226
  [expr.ExprIdent("msgspec.Struct"), expr.ExprIdent("kw_only=True")],
@@ -203,8 +234,11 @@ def make_python_data_model(
203
234
 
204
235
  if isinstance(prop, DataProperty):
205
236
  pytype = prop.get_data_model_datatype().get_python_type()
206
- if pytype.dep is not None:
207
- program.import_(pytype.dep, True)
237
+ if prop.is_optional:
238
+ pytype = pytype.as_optional_type()
239
+
240
+ for dep in pytype.deps:
241
+ program.import_(dep, True)
208
242
 
209
243
  pytype_type = pytype.type
210
244
  if len(prop.data.constraints) > 0:
@@ -257,11 +291,13 @@ def make_python_data_model(
257
291
  else:
258
292
  pytype = PyTypeWithDep(
259
293
  f"Upsert{prop.target.name}",
260
- f"{target_pkg.module(prop.target.get_pymodule_name()).path}.Upsert{prop.target.name}",
294
+ [
295
+ f"{target_pkg.module(prop.target.get_pymodule_name()).path}.Upsert{prop.target.name}"
296
+ ],
261
297
  )
262
298
 
263
- if pytype.dep is not None:
264
- program.import_(pytype.dep, True)
299
+ for dep in pytype.deps:
300
+ program.import_(dep, True)
265
301
 
266
302
  if prop.cardinality.is_star_to_many():
267
303
  pytype = pytype.as_list_type()
@@ -270,6 +306,69 @@ def make_python_data_model(
270
306
  # has_to_db = True
271
307
  # if any(prop for prop in cls.properties.values() if isinstance(prop, ObjectProperty) and prop.cardinality == Cardinality.MANY_TO_MANY):
272
308
  # # if the class has many-to-many relationship, we need to
309
+
310
+ if has_system_controlled:
311
+ program.import_("typing.Optional", True)
312
+ cls_ast(
313
+ stmt.LineBreak(),
314
+ stmt.Comment(
315
+ "_verified is a special marker to indicate whether the data is updated by the system."
316
+ ),
317
+ stmt.DefClassVarStatement(
318
+ "_verified", "bool", expr.ExprConstant(False)
319
+ ),
320
+ stmt.LineBreak(),
321
+ lambda ast: ast.func(
322
+ "__post_init__",
323
+ [
324
+ DeferredVar.simple("self"),
325
+ ],
326
+ )(
327
+ stmt.AssignStatement(
328
+ PredefinedFn.attr_getter(
329
+ expr.ExprIdent("self"), expr.ExprIdent("_verified")
330
+ ),
331
+ expr.ExprConstant(False),
332
+ )
333
+ ),
334
+ stmt.LineBreak(),
335
+ lambda ast: ast.func(
336
+ "update_system_controlled_props",
337
+ [
338
+ DeferredVar.simple("self"),
339
+ DeferredVar.simple(
340
+ "data",
341
+ expr.ExprIdent("Optional[SystemControlledProps]"),
342
+ ),
343
+ ],
344
+ )(
345
+ lambda ast00: ast00.if_(
346
+ expr.ExprNegation(
347
+ expr.ExprIs(expr.ExprIdent("data"), expr.ExprConstant(None))
348
+ ),
349
+ )(
350
+ *[
351
+ stmt.AssignStatement(
352
+ PredefinedFn.attr_getter(
353
+ expr.ExprIdent("self"), expr.ExprIdent(prop.name)
354
+ ),
355
+ PredefinedFn.item_getter(
356
+ expr.ExprIdent("data"), expr.ExprConstant(prop.name)
357
+ ),
358
+ )
359
+ for prop in cls.properties.values()
360
+ if prop.data.is_system_controlled
361
+ ]
362
+ ),
363
+ stmt.AssignStatement(
364
+ PredefinedFn.attr_getter(
365
+ expr.ExprIdent("self"), expr.ExprIdent("_verified")
366
+ ),
367
+ expr.ExprConstant(True),
368
+ ),
369
+ ),
370
+ )
371
+
273
372
  cls_ast(
274
373
  stmt.LineBreak(),
275
374
  lambda ast00: ast00.func(
@@ -281,6 +380,18 @@ def make_python_data_model(
281
380
  f"{cls.name}DB" if cls.db is not None else cls.name
282
381
  ),
283
382
  )(
383
+ (
384
+ stmt.AssertionStatement(
385
+ PredefinedFn.attr_getter(
386
+ expr.ExprIdent("self"), expr.ExprIdent("_verified")
387
+ ),
388
+ expr.ExprConstant(
389
+ "The model data must be verified before converting to db model"
390
+ ),
391
+ )
392
+ if has_system_controlled
393
+ else None
394
+ ),
284
395
  lambda ast10: ast10.return_(
285
396
  expr.ExprFuncCall(
286
397
  expr.ExprIdent(
@@ -293,7 +404,7 @@ def make_python_data_model(
293
404
  for prop in cls.properties.values()
294
405
  ],
295
406
  )
296
- )
407
+ ),
297
408
  ),
298
409
  )
299
410
 
@@ -320,8 +431,12 @@ def make_python_data_model(
320
431
 
321
432
  if isinstance(prop, DataProperty):
322
433
  pytype = prop.get_data_model_datatype().get_python_type()
323
- if pytype.dep is not None:
324
- program.import_(pytype.dep, True)
434
+ if prop.is_optional:
435
+ pytype = pytype.as_optional_type()
436
+
437
+ for dep in pytype.deps:
438
+ program.import_(dep, True)
439
+
325
440
  cls_ast(stmt.DefClassVarStatement(prop.name, pytype.type))
326
441
  elif isinstance(prop, ObjectProperty):
327
442
  if prop.target.db is not None:
@@ -333,11 +448,13 @@ def make_python_data_model(
333
448
  else:
334
449
  pytype = PyTypeWithDep(
335
450
  prop.target.name,
336
- f"{target_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}",
451
+ [
452
+ f"{target_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}"
453
+ ],
337
454
  )
338
455
 
339
- if pytype.dep is not None:
340
- program.import_(pytype.dep, True)
456
+ for dep in pytype.deps:
457
+ program.import_(dep, True)
341
458
 
342
459
  if prop.cardinality.is_star_to_many():
343
460
  pytype = pytype.as_list_type()
@@ -423,6 +540,7 @@ def make_python_relational_model(
423
540
  # assume configuration for the app at the top level
424
541
  program.import_(f"{app.config.path}.DB_CONNECTION", True)
425
542
  program.import_(f"{app.config.path}.DB_DEBUG", True)
543
+ program.import_(f"contextlib.contextmanager", True)
426
544
 
427
545
  program.root.linebreak()
428
546
 
@@ -484,7 +602,15 @@ def make_python_relational_model(
484
602
  )
485
603
 
486
604
  program.root.linebreak()
487
- program.root.func("get_session", [], is_async=True)(
605
+ program.root.func("async_get_session", [], is_async=True)(
606
+ lambda ast00: ast00.python_stmt("with Session(engine) as session:")(
607
+ lambda ast01: ast01.python_stmt("yield session")
608
+ )
609
+ )
610
+
611
+ program.root.linebreak()
612
+ program.root.python_stmt("@contextmanager")
613
+ program.root.func("get_session", [])(
488
614
  lambda ast00: ast00.python_stmt("with Session(engine) as session:")(
489
615
  lambda ast01: ast01.python_stmt("yield session")
490
616
  )
@@ -701,7 +827,7 @@ def make_python_relational_object_property(
701
827
  expr.ExprFuncCall(
702
828
  expr.ExprIdent("mapped_column"),
703
829
  [
704
- expr.ExprIdent(f"{prop.name}_{p.name}"),
830
+ expr.ExprConstant(f"{prop.name}_{p.name}"),
705
831
  expr.ExprIdent(
706
832
  assert_isinstance(p, DataProperty)
707
833
  .datatype.get_sqlalchemy_type()
@@ -709,7 +835,7 @@ def make_python_relational_object_property(
709
835
  ),
710
836
  PredefinedFn.keyword_assignment(
711
837
  "nullable",
712
- expr.ExprConstant(prop.is_optional),
838
+ expr.ExprConstant(prop.is_optional or p.is_optional),
713
839
  ),
714
840
  ],
715
841
  )
@@ -68,8 +68,8 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
68
68
 
69
69
  if isinstance(prop, DataProperty):
70
70
  tstype = prop.get_data_model_datatype().get_typescript_type()
71
- if tstype.dep is not None:
72
- program.import_(tstype.dep, True)
71
+ for dep in tstype.deps:
72
+ program.import_(dep, True)
73
73
 
74
74
  if idprop is not None and prop.name == idprop.name:
75
75
  # use id type alias
@@ -89,7 +89,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
89
89
  # this class is stored in the database, we store the id instead
90
90
  tstype = TsTypeWithDep(
91
91
  f"{prop.target.name}Id",
92
- f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id",
92
+ [
93
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
94
+ ],
93
95
  )
94
96
  if prop.cardinality.is_star_to_many():
95
97
  tstype = tstype.as_list_type()
@@ -105,7 +107,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
105
107
  # we are going to store the whole object
106
108
  tstype = TsTypeWithDep(
107
109
  prop.target.name,
108
- f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}",
110
+ [
111
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
112
+ ],
109
113
  )
110
114
  if prop.cardinality.is_star_to_many():
111
115
  tstype = tstype.as_list_type()
@@ -148,9 +152,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
148
152
  )
149
153
  )
150
154
 
151
- if tstype.dep is not None:
155
+ for dep in tstype.deps:
152
156
  program.import_(
153
- tstype.dep,
157
+ dep,
154
158
  True,
155
159
  )
156
160
 
@@ -319,14 +323,14 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
319
323
  # use id type alias
320
324
  tstype = TsTypeWithDep(
321
325
  f"{cls.name}Id",
322
- dep=f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id",
326
+ deps=[f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id"],
323
327
  )
324
328
  else:
325
329
  # for none id properties, we need to include a type for "invalid" value
326
330
  tstype = _inject_type_for_invalid_value(tstype)
327
331
 
328
- if tstype.dep is not None:
329
- program.import_(tstype.dep, True)
332
+ for dep in tstype.deps:
333
+ program.import_(dep, True)
330
334
 
331
335
  # however, if this is a primary key and auto-increment, we set a different default value
332
336
  # to be -1 to avoid start from 0
@@ -426,7 +430,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
426
430
  # this class is stored in the database, we store the id instead
427
431
  tstype = TsTypeWithDep(
428
432
  f"{prop.target.name}Id",
429
- f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id",
433
+ [
434
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
435
+ ],
430
436
  )
431
437
  if prop.cardinality.is_star_to_many():
432
438
  tstype = tstype.as_list_type()
@@ -461,7 +467,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
461
467
  # we are going to store the whole object
462
468
  tstype = TsTypeWithDep(
463
469
  f"Draft{prop.target.name}",
464
- f"@.models.{prop.target.get_tsmodule_name()}.Draft{prop.target.name}.Draft{prop.target.name}",
470
+ [
471
+ f"@.models.{prop.target.get_tsmodule_name()}.Draft{prop.target.name}.Draft{prop.target.name}"
472
+ ],
465
473
  )
466
474
  if prop.cardinality.is_star_to_many():
467
475
  tstype = tstype.as_list_type()
@@ -515,9 +523,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
515
523
  )
516
524
  )
517
525
 
518
- if tstype.dep is not None:
526
+ for dep in tstype.deps:
519
527
  program.import_(
520
- tstype.dep,
528
+ dep,
521
529
  True,
522
530
  )
523
531
 
@@ -698,12 +706,16 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
698
706
  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)",
699
707
  )(
700
708
  stmt.ReturnStatement(
701
- stmt.TypescriptStatement(
709
+ expr.ExprRawTypescript(
702
710
  " && ".join(
703
711
  f"{draft_validators}.{prop2tsname[prop.name]}(this.{prop2tsname[prop.name]}).isValid"
704
712
  for prop in cls.properties.values()
705
713
  if not prop.is_optional
706
714
  )
715
+ if any(
716
+ not prop.is_optional for prop in cls.properties.values()
717
+ )
718
+ else "true"
707
719
  )
708
720
  )
709
721
  ),
@@ -859,8 +871,8 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
859
871
  # if prop.name == idprop.name:
860
872
  # # use id type alias
861
873
  # tstype = TsTypeWithDep(f"{cls.name}Id")
862
- if tstype.dep is not None:
863
- program.import_(tstype.dep, True)
874
+ for dep in tstype.deps:
875
+ program.import_(dep, True)
864
876
  tsprop = [
865
877
  (expr.ExprIdent("datatype"), expr.ExprConstant(tstype.type)),
866
878
  (
@@ -885,7 +897,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
885
897
  # we are going to store the whole object
886
898
  tstype = TsTypeWithDep(
887
899
  prop.target.name,
888
- f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}",
900
+ [
901
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
902
+ ],
889
903
  )
890
904
 
891
905
  # we don't store the type itself, but just the name of the type
@@ -901,7 +915,12 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
901
915
  expr.ExprIdent("targetClass"),
902
916
  expr.ExprConstant(prop.target.name),
903
917
  ),
904
- (expr.ExprIdent("datatype"), expr.ExprConstant(tstype.type)),
918
+ (
919
+ expr.ExprIdent("datatype"),
920
+ expr.ExprConstant(
921
+ tstype.type if prop.target.db is not None else "embedded"
922
+ ),
923
+ ),
905
924
  (
906
925
  expr.ExprIdent("cardinality"),
907
926
  expr.ExprConstant(prop.cardinality.value),
@@ -957,24 +976,36 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
957
976
  )
958
977
  )
959
978
 
960
- for type in ["Schema", "ObjectProperty", "DataProperty"]:
979
+ for type in ["ObjectProperty", "DataProperty"]:
961
980
  program.import_(f"sera-db.{type}", True)
981
+ if cls.db is not None:
982
+ program.import_(f"sera-db.Schema", True)
983
+ else:
984
+ program.import_(f"sera-db.EmbeddedSchema", True)
962
985
 
963
986
  program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
964
- program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
965
987
  program.import_(
966
988
  f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
967
989
  )
968
990
  program.import_(
969
991
  f"@.models.{pkg.dir.name}.Draft{cls.name}.draft{cls.name}Validators", True
970
992
  )
993
+ if cls.db is not None:
994
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
995
+
971
996
  program.root(
972
997
  stmt.LineBreak(),
973
998
  stmt.TypescriptStatement(
974
999
  f"export type {cls.name}SchemaType = "
975
1000
  + PredefinedFn.dict(
976
- [
977
- (expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
1001
+ (
1002
+ [
1003
+ (expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
1004
+ ]
1005
+ if cls.db is not None
1006
+ else []
1007
+ )
1008
+ + [
978
1009
  (
979
1010
  expr.ExprIdent("publicProperties"),
980
1011
  expr.ExprIdent(
@@ -1038,7 +1069,11 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1038
1069
  ),
1039
1070
  stmt.LineBreak(),
1040
1071
  stmt.TypescriptStatement(
1041
- f"export const {cls.name}Schema: Schema<{cls.name}SchemaType['id'], {cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties'], {cls.name}SchemaType> = "
1072
+ (
1073
+ f"export const {cls.name}Schema: Schema<{cls.name}SchemaType['id'], {cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties'], {cls.name}SchemaType> = "
1074
+ if cls.db is not None
1075
+ else f"export const {cls.name}Schema: EmbeddedSchema<{cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties']> = "
1076
+ )
1042
1077
  + PredefinedFn.dict(
1043
1078
  [
1044
1079
  (
@@ -1211,7 +1246,7 @@ def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
1211
1246
  else:
1212
1247
  # Need to add parentheses
1213
1248
  inner_type = f"({inner_type} | string)"
1214
- return TsTypeWithDep(inner_type + "[]", tstype.dep)
1249
+ return TsTypeWithDep(inner_type + "[]", tstype.deps)
1215
1250
 
1216
1251
  m = re.match(r"^\(?[a-zA-Z \|]+\)?$", tstype.type)
1217
1252
  if m is not None:
@@ -1222,7 +1257,7 @@ def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
1222
1257
  else:
1223
1258
  # Needs parentheses for clarity
1224
1259
  new_type = f"({tstype.type} | string)"
1225
- return TsTypeWithDep(new_type, tstype.dep)
1260
+ return TsTypeWithDep(new_type, tstype.deps)
1226
1261
  return tstype
1227
1262
 
1228
1263
  raise NotImplementedError(tstype.type)
sera/models/_datatype.py CHANGED
@@ -24,7 +24,7 @@ SQLAlchemyDataType = Literal[
24
24
  @dataclass
25
25
  class PyTypeWithDep:
26
26
  type: str
27
- dep: str | None = None
27
+ deps: list[str] = field(default_factory=list)
28
28
 
29
29
  def get_python_type(self) -> type:
30
30
  """Get the Python type from the type string for typing annotation in Python."""
@@ -50,13 +50,25 @@ class PyTypeWithDep:
50
50
 
51
51
  def as_list_type(self) -> PyTypeWithDep:
52
52
  """Convert the type to a list type."""
53
- return PyTypeWithDep(type=f"list[{self.type}]", dep=self.dep)
53
+ return PyTypeWithDep(type=f"list[{self.type}]", deps=self.deps)
54
+
55
+ def as_optional_type(self) -> PyTypeWithDep:
56
+ """Convert the type to an optional type."""
57
+ if "typing.Optional" not in self.deps:
58
+ deps = self.deps + ["typing.Optional"]
59
+ else:
60
+ deps = self.deps
61
+ if "Optional[" in self.type:
62
+ raise NotImplementedError(
63
+ f"Have not handle nested optional yet: {self.type}"
64
+ )
65
+ return PyTypeWithDep(type=f"Optional[{self.type}]", deps=deps)
54
66
 
55
67
 
56
68
  @dataclass
57
69
  class TsTypeWithDep:
58
70
  type: str
59
- dep: str | None = None
71
+ deps: list[str] = field(default_factory=list)
60
72
 
61
73
  def get_default(self) -> expr.ExprConstant:
62
74
  if self.type.endswith("[]"):
@@ -83,7 +95,7 @@ class TsTypeWithDep:
83
95
  list_type = f"({self.type})[]"
84
96
  else:
85
97
  list_type = f"{self.type}[]"
86
- return TsTypeWithDep(type=list_type, dep=self.dep)
98
+ return TsTypeWithDep(type=list_type, deps=self.deps)
87
99
 
88
100
 
89
101
  @dataclass
@@ -137,16 +149,6 @@ predefined_datatypes = {
137
149
  tstype=TsTypeWithDep(type="string"),
138
150
  is_list=False,
139
151
  ),
140
- "optional[string]": DataType(
141
- pytype=PyTypeWithDep(type="Optional[str]", dep="typing.Optional"),
142
- sqltype=SQLTypeWithDep(
143
- type="String",
144
- mapped_pytype="Optional[str]",
145
- deps=["sqlalchemy.String", "typing.Optional"],
146
- ),
147
- tstype=TsTypeWithDep(type="string | undefined"),
148
- is_list=False,
149
- ),
150
152
  "integer": DataType(
151
153
  pytype=PyTypeWithDep(type="int"),
152
154
  sqltype=SQLTypeWithDep(
@@ -156,7 +158,7 @@ predefined_datatypes = {
156
158
  is_list=False,
157
159
  ),
158
160
  "datetime": DataType(
159
- pytype=PyTypeWithDep(type="datetime", dep="datetime.datetime"),
161
+ pytype=PyTypeWithDep(type="datetime", deps=["datetime.datetime"]),
160
162
  sqltype=SQLTypeWithDep(
161
163
  type="DateTime",
162
164
  mapped_pytype="datetime",
sera/models/_parse.py CHANGED
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import Sequence
7
7
 
8
8
  import serde.yaml
9
+
9
10
  from sera.models._class import Class, ClassDBMapInfo, Index
10
11
  from sera.models._constraints import Constraint, predefined_constraints
11
12
  from sera.models._datatype import (
@@ -101,22 +102,24 @@ def _parse_property(
101
102
  schema: Schema, prop_name: str, prop: dict
102
103
  ) -> DataProperty | ObjectProperty:
103
104
  if isinstance(prop, str):
104
- datatype = prop
105
- if datatype in schema.classes:
106
- return ObjectProperty(
107
- name=prop_name,
108
- label=_parse_multi_lingual_string(prop_name),
109
- description=_parse_multi_lingual_string(""),
110
- target=schema.classes[datatype],
111
- cardinality=Cardinality.ONE_TO_ONE,
112
- )
113
- else:
114
- return DataProperty(
115
- name=prop_name,
116
- label=_parse_multi_lingual_string(prop_name),
117
- description=_parse_multi_lingual_string(""),
118
- datatype=_parse_datatype(schema, datatype),
119
- )
105
+ # deprecated
106
+ assert False, prop
107
+ # datatype = prop
108
+ # if datatype in schema.classes:
109
+ # return ObjectProperty(
110
+ # name=prop_name,
111
+ # label=_parse_multi_lingual_string(prop_name),
112
+ # description=_parse_multi_lingual_string(""),
113
+ # target=schema.classes[datatype],
114
+ # cardinality=Cardinality.ONE_TO_ONE,
115
+ # )
116
+ # else:
117
+ # return DataProperty(
118
+ # name=prop_name,
119
+ # label=_parse_multi_lingual_string(prop_name),
120
+ # description=_parse_multi_lingual_string(""),
121
+ # datatype=_parse_datatype(schema, datatype),
122
+ # )
120
123
 
121
124
  db = prop.get("db", {})
122
125
  _data = prop.get("data", {})
@@ -128,6 +131,7 @@ def _parse_property(
128
131
  constraints=[
129
132
  _parse_constraint(constraint) for constraint in _data.get("constraints", [])
130
133
  ],
134
+ is_system_controlled=prop.get("is_system_controlled", False),
131
135
  )
132
136
 
133
137
  assert isinstance(prop, dict), prop
sera/models/_property.py CHANGED
@@ -71,6 +71,9 @@ class PropDataAttrs:
71
71
  # list of constraints applied to the data model's field
72
72
  constraints: list[Constraint] = field(default_factory=list)
73
73
 
74
+ # whether this property is controlled by the system or not
75
+ is_system_controlled: bool = False
76
+
74
77
 
75
78
  @dataclass(kw_only=True)
76
79
  class Property:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.9.1
3
+ Version: 1.11.2
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -1,18 +1,21 @@
1
1
  sera/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sera/constants.py,sha256=mzAaMyIx8TJK0-RYYJ5I24C4s0Uvj26OLMJmBo0pxHI,123
3
3
  sera/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- sera/libs/api_helper.py,sha256=hUEy0INHM18lxTQ348tgbXNceOHcjiAnqmuL_8CRpLQ,2509
4
+ sera/libs/api_helper.py,sha256=47y1kcwk3Xd2ZEMnUj_0OwCuUmgwOs5kYrE95BDVUn4,5411
5
5
  sera/libs/base_orm.py,sha256=dyh0OT2sbHku5qPJXvRzYAHRTSXvvbQaS-Qwg65Bw04,2918
6
- sera/libs/base_service.py,sha256=l5D4IjxIiz8LBRranUYddb8J0Y6SwSyetKYTLrCUdQA,4098
6
+ sera/libs/base_service.py,sha256=8uKMaIicLc8LbN5vyaK9dGWoCLf_wuaaOw9ooSiqSWM,4614
7
7
  sera/libs/dag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  sera/libs/dag/_dag.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ sera/libs/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ sera/libs/middlewares/auth.py,sha256=aH2fJshQpJT_dw3lUr67doZ8zdf90XyakdY35qZ1iZs,2443
11
+ sera/libs/middlewares/uscp.py,sha256=H5umW8iEQSCdb_MJ5Im49kxg1E7TpxSg1p2_2A5zI1U,2600
9
12
  sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
13
  sera/make/__main__.py,sha256=G5O7s6135-708honwqMFn2yPTs06WbGQTHpupID0eZ4,1417
11
14
  sera/make/make_app.py,sha256=n9NtW73O3s_5Q31VHIRmnd-jEIcpDO7ksAsOdovde2s,5999
12
- sera/make/make_python_api.py,sha256=FcklRwGDGTKPjuhbHKyEog8PYPACi-5DA_nB1ZgxeJU,29424
13
- sera/make/make_python_model.py,sha256=v5edXKMTQ2XAYDoM7fBONx3S9H6DRUPQp9UwWWUowLg,36594
15
+ sera/make/make_python_api.py,sha256=kq5DClmEeeNgg-a3Bb_8GN9jxvjnhjmW3RfBHNzynO8,25407
16
+ sera/make/make_python_model.py,sha256=MyEhVXeeHxAzqUkjSUuVzQJLUCCe7RKL6VxEMiCfTxk,41507
14
17
  sera/make/make_python_services.py,sha256=RsinYZdfkrTlTn9CT50VgqGs9w6IZawsJx-KEmqfnEY,2062
15
- sera/make/make_typescript_model.py,sha256=fMjz4YxdGHijb2pHI4xj7rKJf1PxyJxVLYW0ivsu70c,50128
18
+ sera/make/make_typescript_model.py,sha256=-FNeUlcrqz2Zd1Eg6P14usXTrgTXd9oGhpOApLqntOw,51395
16
19
  sera/misc/__init__.py,sha256=Dh4uDq0D4N53h3zhvmwfa5a0TPVRSUvLzb0hkFuPirk,411
17
20
  sera/misc/_formatter.py,sha256=aCGYL08l8f3aLODHxSocxBBwkRYEo3K1QzCDEn3suj0,1685
18
21
  sera/misc/_utils.py,sha256=V5g4oLGHOhUCR75Kkcn1w01pAvGvaepK-T8Z3pIgHjI,1450
@@ -20,15 +23,15 @@ sera/models/__init__.py,sha256=vJC5Kzo_N7wd16ocNPy1VvAZDGNiWeiAhWJ4ihATKvA,780
20
23
  sera/models/_class.py,sha256=Wf0e8x6-szG9TzoFkAlqj7_dG0SCICMBw_333n3paxk,2514
21
24
  sera/models/_collection.py,sha256=ZnQEriKC4X88Zz48Kn1AVZKH-1_l8OgWa-zf2kcQOOE,1414
22
25
  sera/models/_constraints.py,sha256=L6QwKL3hRJ5DvvIB4JNgLoyvTKbE9FrpYRzezM4CweE,1484
23
- sera/models/_datatype.py,sha256=eF71q-X7LA91sNYG29aXQiDVMjjr8-e6ez7P9NRwq_E,6376
26
+ sera/models/_datatype.py,sha256=zkqOKOTZIkoYhJkIpytWWhGp-LtD10Jzud7lxOSd5UQ,6533
24
27
  sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
25
28
  sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
26
29
  sera/models/_module.py,sha256=8QRSCubZmdDP9rL58rGAS6X5VCrkc1ZHvuMu1I1KrWk,5043
27
30
  sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
28
- sera/models/_parse.py,sha256=sJYfQtwek96ltpgxExG4xUbiLnU3qvNYhTP1CeyXGjs,9746
29
- sera/models/_property.py,sha256=CmEmgOShtSyNFq05YW3tGupwCIVRzPMKudXWld8utPk,5530
31
+ sera/models/_parse.py,sha256=uw6fvvh1ucGqE2jFTCCr-e6_qMfZfSVpaPolNxmrHww,9897
32
+ sera/models/_property.py,sha256=SJSm5fZJimd2rQuL4UH_aZuNyp9v7x64xMbEVbtYx8Q,5633
30
33
  sera/models/_schema.py,sha256=r-Gqg9Lb_wR3UrbNvfXXgt_qs5bts0t2Ve7aquuF_OI,1155
31
34
  sera/typing.py,sha256=Q4QMfbtfrCjC9tFfsZPhsAnbNX4lm4NHQ9lmjNXYdV0,772
32
- sera_2-1.9.1.dist-info/METADATA,sha256=Xg1hqLX0htdcQaJxgMUXAr4e_DAiDp3BiyTXXtIfHpg,856
33
- sera_2-1.9.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
34
- sera_2-1.9.1.dist-info/RECORD,,
35
+ sera_2-1.11.2.dist-info/METADATA,sha256=t32pt4lTWNgWc27jVQKlmlK_xf9c99crcgWGVVQjo-Y,857
36
+ sera_2-1.11.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
37
+ sera_2-1.11.2.dist-info/RECORD,,