sera-2 1.20.4__py3-none-any.whl → 1.20.12__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/libs/api_helper.py CHANGED
@@ -15,7 +15,7 @@ from litestar.typing import FieldDefinition
15
15
  from msgspec import Struct
16
16
 
17
17
  from sera.libs.base_service import Query, QueryOp
18
- from sera.libs.middlewares.uscp import STATE_SYSTEM_CONTROLLED_PROP_KEY
18
+ from sera.libs.middlewares.uscp import SKIP_UPDATE_SYSTEM_CONTROLLED_PROPS_KEY
19
19
  from sera.typing import T
20
20
 
21
21
  # for parsing field names and operations from query string
@@ -136,9 +136,14 @@ class SingleAutoUSCP(MsgspecDTO[S], Generic[S]):
136
136
  "data_backend"
137
137
  ] # pyright: ignore
138
138
  obj = backend.populate_data_from_raw(value, self.asgi_connection)
139
- obj.update_system_controlled_props(
140
- self.asgi_connection.scope["state"][STATE_SYSTEM_CONTROLLED_PROP_KEY]
141
- )
139
+ if self.asgi_connection.scope["state"][SKIP_UPDATE_SYSTEM_CONTROLLED_PROPS_KEY]:
140
+ # Skip updating system-controlled properties
141
+ # TODO: dirty fix as this assumes every struct has _is_scp_updated property. find a
142
+ # better solution and fix me!
143
+ obj._is_scp_updated = True
144
+ return obj
145
+
146
+ obj.update_system_controlled_props(self.asgi_connection)
142
147
  return obj
143
148
 
144
149
 
@@ -6,7 +6,7 @@ from litestar.connection.base import UserT
6
6
  from litestar.middleware import AbstractMiddleware
7
7
  from litestar.types import ASGIApp, Message, Receive, Scope, Scopes, Send
8
8
 
9
- STATE_SYSTEM_CONTROLLED_PROP_KEY = "system_controlled_properties"
9
+ SKIP_UPDATE_SYSTEM_CONTROLLED_PROPS_KEY = "skip_uscp_1157"
10
10
 
11
11
 
12
12
  class USCPMiddleware(AbstractMiddleware):
@@ -21,7 +21,6 @@ class USCPMiddleware(AbstractMiddleware):
21
21
  def __init__(
22
22
  self,
23
23
  app: ASGIApp,
24
- get_system_controlled_props: Callable[[UserT], dict],
25
24
  skip_update_system_controlled_props: Callable[[UserT], bool],
26
25
  exclude: str | list[str] | None = None,
27
26
  exclude_opt_key: str | None = None,
@@ -39,17 +38,13 @@ class USCPMiddleware(AbstractMiddleware):
39
38
  either or both 'ScopeType.HTTP' and 'ScopeType.WEBSOCKET'.
40
39
  """
41
40
  super().__init__(app, exclude, exclude_opt_key, scopes)
42
- self.get_system_controlled_props = get_system_controlled_props
43
41
  self.skip_update_system_controlled_props = skip_update_system_controlled_props
44
42
 
45
43
  async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
46
44
  user = scope["user"]
47
- if self.skip_update_system_controlled_props(user):
48
- scope["state"][STATE_SYSTEM_CONTROLLED_PROP_KEY] = None
49
- else:
50
- scope["state"][STATE_SYSTEM_CONTROLLED_PROP_KEY] = (
51
- self.get_system_controlled_props(user)
52
- )
45
+ scope["state"][SKIP_UPDATE_SYSTEM_CONTROLLED_PROPS_KEY] = (
46
+ self.skip_update_system_controlled_props(user)
47
+ )
53
48
  await self.app(scope, receive, send)
54
49
 
55
50
 
@@ -29,6 +29,8 @@ from sera.typing import is_set
29
29
  TS_GLOBAL_IDENTS = {
30
30
  "normalizers.normalizeNumber": "sera-db.normalizers",
31
31
  "normalizers.normalizeOptionalNumber": "sera-db.normalizers",
32
+ "normalizers.normalizeDate": "sera-db.normalizers",
33
+ "normalizers.normalizeOptionalDate": "sera-db.normalizers",
32
34
  }
33
35
 
34
36
 
@@ -66,6 +68,14 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
66
68
  )
67
69
  if prop.is_optional:
68
70
  value = handle_optional(value)
71
+ value.true_expr = (
72
+ prop.datatype.get_typescript_type().get_json_deser_func(
73
+ value.true_expr
74
+ )
75
+ )
76
+ else:
77
+ value = prop.datatype.get_typescript_type().get_json_deser_func(value)
78
+
69
79
  return value
70
80
 
71
81
  assert isinstance(prop, ObjectProperty)
@@ -140,7 +150,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
140
150
 
141
151
  if idprop is not None and prop.name == idprop.name:
142
152
  # use id type alias
143
- tstype = TsTypeWithDep(f"{cls.name}Id")
153
+ tstype = TsTypeWithDep(
154
+ type=f"{cls.name}Id", spectype=tstype.spectype
155
+ )
144
156
 
145
157
  if prop.is_optional:
146
158
  # convert type to optional
@@ -158,8 +170,12 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
158
170
  # this class is stored in the database, we store the id instead
159
171
  propname = propname + "Id"
160
172
  tstype = TsTypeWithDep(
161
- f"{prop.target.name}Id",
162
- (
173
+ type=f"{prop.target.name}Id",
174
+ spectype=assert_not_null(prop.target.get_id_property())
175
+ .get_data_model_datatype()
176
+ .get_typescript_type()
177
+ .spectype,
178
+ deps=(
163
179
  [
164
180
  f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
165
181
  ]
@@ -181,8 +197,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
181
197
  else:
182
198
  # we are going to store the whole object
183
199
  tstype = TsTypeWithDep(
184
- prop.target.name,
185
- [
200
+ type=prop.target.name,
201
+ spectype=prop.target.name,
202
+ deps=[
186
203
  f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
187
204
  ],
188
205
  )
@@ -249,6 +266,17 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
249
266
  ],
250
267
  )(*prop_constructor_assigns),
251
268
  stmt.LineBreak(),
269
+ lambda ast12: ast12.func(
270
+ "className",
271
+ [],
272
+ expr.ExprIdent("string"),
273
+ is_static=True,
274
+ modifiers=["get"],
275
+ comment="Name of the class in the Schema",
276
+ )(
277
+ stmt.ReturnStatement(expr.ExprConstant(cls.name)),
278
+ ),
279
+ stmt.LineBreak(),
252
280
  lambda ast12: ast12.func(
253
281
  "deser",
254
282
  [
@@ -382,10 +410,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
382
410
  if idprop is not None and prop.name == idprop.name:
383
411
  # use id type alias
384
412
  tstype = TsTypeWithDep(
385
- f"{cls.name}Id",
413
+ type=f"{cls.name}Id",
414
+ spectype=tstype.spectype,
386
415
  deps=[f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id"],
387
416
  )
388
- original_tstype = tstype
389
417
  elif tstype.type not in schema.enums:
390
418
  # for none id & none enum properties, we need to include a type for "invalid" value
391
419
  tstype = _inject_type_for_invalid_value(tstype)
@@ -455,7 +483,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
455
483
  expr.ExprIdent("record"), expr.ExprIdent(propname)
456
484
  )
457
485
 
458
- if original_tstype.type != tstype.type:
486
+ if (
487
+ original_tstype.type != tstype.type
488
+ and tstype.type != f"{cls.name}Id"
489
+ ):
459
490
  norm_func = get_norm_func(original_tstype, import_helper)
460
491
  else:
461
492
  norm_func = identity
@@ -480,17 +511,22 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
480
511
  ),
481
512
  expr.ExprIdent("isValid"),
482
513
  ),
483
- norm_func(
484
- PredefinedFn.attr_getter(
485
- expr.ExprIdent("this"), expr.ExprIdent(propname)
514
+ original_tstype.get_json_ser_func(
515
+ norm_func(
516
+ PredefinedFn.attr_getter(
517
+ expr.ExprIdent("this"),
518
+ expr.ExprIdent(propname),
519
+ )
486
520
  )
487
521
  ),
488
522
  expr.ExprIdent("undefined"),
489
523
  )
490
524
  if prop.is_optional
491
- else norm_func(
492
- PredefinedFn.attr_getter(
493
- expr.ExprIdent("this"), expr.ExprIdent(propname)
525
+ else original_tstype.get_json_ser_func(
526
+ norm_func(
527
+ PredefinedFn.attr_getter(
528
+ expr.ExprIdent("this"), expr.ExprIdent(propname)
529
+ )
494
530
  )
495
531
  )
496
532
  ),
@@ -555,8 +591,12 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
555
591
  if prop.target.db is not None:
556
592
  # this class is stored in the database, we store the id instead
557
593
  tstype = TsTypeWithDep(
558
- f"{prop.target.name}Id",
559
- [
594
+ type=f"{prop.target.name}Id",
595
+ spectype=assert_not_null(prop.target.get_id_property())
596
+ .get_data_model_datatype()
597
+ .get_typescript_type()
598
+ .spectype,
599
+ deps=[
560
600
  f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
561
601
  ],
562
602
  )
@@ -607,8 +647,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
607
647
  else:
608
648
  # we are going to store the whole object
609
649
  tstype = TsTypeWithDep(
610
- f"Draft{prop.target.name}",
611
- [
650
+ type=f"Draft{prop.target.name}",
651
+ spectype=f"Draft{prop.target.name}",
652
+ deps=[
612
653
  f"@.models.{prop.target.get_tsmodule_name()}.Draft{prop.target.name}.Draft{prop.target.name}"
613
654
  ],
614
655
  )
@@ -1191,7 +1232,7 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1191
1232
  (
1192
1233
  expr.ExprIdent("datatype"),
1193
1234
  (
1194
- expr.ExprConstant(tstype.type)
1235
+ expr.ExprConstant(tstype.spectype)
1195
1236
  if tstype.type not in schema.enums
1196
1237
  else expr.ExprConstant("enum")
1197
1238
  ),
@@ -1227,8 +1268,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1227
1268
  else:
1228
1269
  # we are going to store the whole object
1229
1270
  tstype = TsTypeWithDep(
1230
- prop.target.name,
1231
- [
1271
+ type=prop.target.name,
1272
+ spectype=prop.target.name,
1273
+ deps=[
1232
1274
  f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
1233
1275
  ],
1234
1276
  )
@@ -1249,7 +1291,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1249
1291
  (
1250
1292
  expr.ExprIdent("datatype"),
1251
1293
  expr.ExprConstant(
1252
- tstype.type if prop.target.db is not None else "undefined"
1294
+ tstype.spectype
1295
+ if prop.target.db is not None
1296
+ else "undefined"
1253
1297
  ),
1254
1298
  ),
1255
1299
  (
@@ -1605,6 +1649,10 @@ def get_normalizer(
1605
1649
  return import_helper.use("normalizers.normalizeNumber")
1606
1650
  if tstype.type == "number | undefined":
1607
1651
  return import_helper.use("normalizers.normalizeOptionalNumber")
1652
+ if tstype.type == "Date":
1653
+ return import_helper.use("normalizers.normalizeDate")
1654
+ if tstype.type == "Date | undefined":
1655
+ return import_helper.use("normalizers.normalizeOptionalDate")
1608
1656
 
1609
1657
  assert "number" not in tstype.type, tstype.type
1610
1658
  return None
sera/misc/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from sera.misc._formatter import File, Formatter
2
2
  from sera.misc._utils import (
3
3
  LoadTableDataArgs,
4
+ RelTableIndex,
4
5
  assert_isinstance,
5
6
  assert_not_null,
6
7
  filter_duplication,
@@ -26,4 +27,5 @@ __all__ = [
26
27
  "identity",
27
28
  "get_classpath",
28
29
  "LoadTableDataArgs",
30
+ "RelTableIndex",
29
31
  ]
sera/misc/_utils.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import inspect
3
4
  import re
5
+ from collections import defaultdict
4
6
  from dataclasses import dataclass
5
7
  from pathlib import Path
6
8
  from typing import Any, Callable, Iterable, Optional, Sequence, Type, TypedDict, TypeVar
@@ -120,8 +122,38 @@ class LoadTableDataArgs(TypedDict, total=False):
120
122
  tables: Sequence[type]
121
123
  file: Path
122
124
  files: Sequence[Path]
123
- file_deser: Callable[[Path], list[dict]]
124
- record_deser: Callable[[dict], Any | list[Any]]
125
+ file_deser: Callable[[Path], list[Any]]
126
+ record_deser: (
127
+ Callable[[dict], Any | list[Any]]
128
+ | Callable[[dict, RelTableIndex], Any | list[Any]]
129
+ )
130
+ table_unique_index: dict[type, list[str]]
131
+
132
+
133
+ class RelTableIndex:
134
+ """An index of relational tables to find a record by its unique property."""
135
+
136
+ def __init__(self, cls2index: Optional[dict[str, list[str]]] = None):
137
+ self.table2rows: dict[str, dict[str, Any]] = defaultdict(dict)
138
+ self.table2uniqindex2id: dict[str, dict[str, int]] = defaultdict(dict)
139
+ self.cls2index = cls2index or {}
140
+
141
+ def set_index(self, clsname: str, props: list[str]):
142
+ """Set the unique index for a class."""
143
+ self.cls2index[clsname] = props
144
+
145
+ def add(self, record: Any):
146
+ clsname = record.__class__.__name__
147
+ self.table2rows[clsname][record.id] = record
148
+ if clsname in self.cls2index:
149
+ for prop in self.cls2index[clsname]:
150
+ self.table2uniqindex2id[clsname][getattr(record, prop)] = record.id
151
+
152
+ def get_record(self, clsname: str, uniq_prop: str) -> Optional[Any]:
153
+ tbl = self.table2uniqindex2id[clsname]
154
+ if uniq_prop not in tbl:
155
+ return None
156
+ return self.table2rows[clsname][tbl[uniq_prop]]
125
157
 
126
158
 
127
159
  def load_data(
@@ -143,6 +175,8 @@ def load_data(
143
175
  with Session(engine) as session:
144
176
  create_db_and_tables()
145
177
 
178
+ reltable_index = RelTableIndex()
179
+
146
180
  for args in tqdm(table_data, disable=not verbose, desc="Loading data"):
147
181
  if "table" in args:
148
182
  tbls = [args["table"]]
@@ -162,6 +196,12 @@ def load_data(
162
196
  else:
163
197
  raise ValueError("Either 'file' or 'files' must be provided in args.")
164
198
 
199
+ if "table_unique_index" in args:
200
+ for tbl in tbls:
201
+ reltable_index.set_index(
202
+ tbl.__name__, args["table_unique_index"].get(tbl, [])
203
+ )
204
+
165
205
  raw_records = []
166
206
  if "file_deser" not in args:
167
207
  for file in files:
@@ -175,8 +215,17 @@ def load_data(
175
215
  for file in files:
176
216
  raw_records.extend(args["file_deser"](file))
177
217
 
218
+ assert "record_deser" in args
178
219
  deser = args["record_deser"]
179
- records = [deser(row) for row in raw_records]
220
+
221
+ sig = inspect.signature(deser)
222
+ param_count = len(sig.parameters)
223
+ if param_count == 1:
224
+ records = [deser(row) for row in raw_records]
225
+ else:
226
+ assert param_count == 2
227
+ records = [deser(row, reltable_index) for row in raw_records]
228
+
180
229
  for r in tqdm(
181
230
  records,
182
231
  desc=f"load {', '.join(tbl.__name__ for tbl in tbls)}",
@@ -185,8 +234,11 @@ def load_data(
185
234
  if isinstance(r, Sequence):
186
235
  for x in r:
187
236
  session.merge(x)
237
+ reltable_index.add(x)
188
238
  else:
189
239
  session.merge(r)
240
+ reltable_index.add(r)
241
+
190
242
  session.flush()
191
243
 
192
244
  # Reset the sequence for each table
sera/models/_datatype.py CHANGED
@@ -2,10 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import datetime
4
4
  from dataclasses import dataclass, field
5
- from typing import Literal
5
+ from typing import Callable, Literal
6
6
 
7
7
  from codegen.models import expr
8
8
 
9
+ from sera.misc import identity
10
+
9
11
  PyDataType = Literal["str", "int", "datetime", "float", "bool", "bytes", "dict"]
10
12
  TypescriptDataType = Literal["string", "number", "boolean"]
11
13
  SQLAlchemyDataType = Literal[
@@ -83,6 +85,9 @@ class PyTypeWithDep:
83
85
  @dataclass
84
86
  class TsTypeWithDep:
85
87
  type: str
88
+ # the specific type of the value, to provide more details for the type because typescript use
89
+ # number for both int and float, date for both date and datetime.
90
+ spectype: str
86
91
  deps: list[str] = field(default_factory=list)
87
92
 
88
93
  def get_default(self) -> expr.ExprConstant:
@@ -94,6 +99,8 @@ class TsTypeWithDep:
94
99
  return expr.ExprConstant(0)
95
100
  if self.type == "boolean":
96
101
  return expr.ExprConstant(False)
102
+ if self.type == "Date":
103
+ return expr.ExprConstant("new Date()")
97
104
  if self.type.endswith("| undefined"):
98
105
  return expr.ExprConstant("undefined")
99
106
  if self.type.endswith("| string)") or self.type.endswith("| string"):
@@ -108,16 +115,54 @@ class TsTypeWithDep:
108
115
  if not all(c.isalnum() or c == "_" for c in self.type.strip()):
109
116
  # Type contains special chars like | or spaces, wrap in parentheses
110
117
  list_type = f"({self.type})[]"
118
+ list_spectype = f"({self.spectype})[]"
111
119
  else:
112
120
  list_type = f"{self.type}[]"
113
- return TsTypeWithDep(type=list_type, deps=self.deps)
121
+ list_spectype = f"{self.spectype}[]"
122
+ return TsTypeWithDep(type=list_type, spectype=list_spectype, deps=self.deps)
114
123
 
115
124
  def as_optional_type(self) -> TsTypeWithDep:
116
125
  if "undefined" in self.type:
117
126
  raise NotImplementedError(
118
127
  f"Have not handle nested optional yet: {self.type}"
119
128
  )
120
- return TsTypeWithDep(type=f"{self.type} | undefined", deps=self.deps)
129
+ return TsTypeWithDep(
130
+ type=f"{self.type} | undefined",
131
+ # not changing the spectype because we convert to optional when the value is missing
132
+ # spectype is used to tell the main type of the value when it is present.
133
+ spectype=self.spectype,
134
+ deps=self.deps,
135
+ )
136
+
137
+ def get_json_deser_func(self, value: expr.Expr) -> expr.Expr:
138
+ """Get the typescript expression to convert the value from json format to the correct type."""
139
+ if self.type in {"string", "number", "boolean", "string[]"}:
140
+ return value
141
+ if self.type == "Date":
142
+ return expr.ExprRawTypescript(f"new Date({value.to_typescript()})")
143
+ if any(x.startswith("@.models.enum") for x in self.deps):
144
+ # enum type, we don't need to do anything as we use strings for enums
145
+ return value
146
+ raise ValueError(f"Unknown type: {self.type}")
147
+
148
+ def get_json_ser_func(self, value: expr.Expr) -> expr.Expr:
149
+ """Get the typescript expression to convert the value to json format."""
150
+ if self.type in {
151
+ "string",
152
+ "number",
153
+ "boolean",
154
+ "string[]",
155
+ "number | undefined",
156
+ "boolean | undefined",
157
+ "string | undefined",
158
+ }:
159
+ return value
160
+ if self.type == "Date":
161
+ return expr.ExprRawTypescript(f"{value.to_typescript()}.toISOString()")
162
+ if any(x.startswith("@.models.enum") for x in self.deps):
163
+ # enum type, we don't need to do anything as we use strings for enums
164
+ return value
165
+ raise ValueError(f"Unknown type: {self.type}")
121
166
 
122
167
 
123
168
  @dataclass
@@ -185,7 +230,7 @@ predefined_datatypes = {
185
230
  sqltype=SQLTypeWithDep(
186
231
  type="String", mapped_pytype="str", deps=["sqlalchemy.String"]
187
232
  ),
188
- tstype=TsTypeWithDep(type="string"),
233
+ tstype=TsTypeWithDep(type="string", spectype="string"),
189
234
  is_list=False,
190
235
  ),
191
236
  "integer": DataType(
@@ -193,7 +238,7 @@ predefined_datatypes = {
193
238
  sqltype=SQLTypeWithDep(
194
239
  type="Integer", mapped_pytype="int", deps=["sqlalchemy.Integer"]
195
240
  ),
196
- tstype=TsTypeWithDep(type="number"),
241
+ tstype=TsTypeWithDep(type="number", spectype="integer"),
197
242
  is_list=False,
198
243
  ),
199
244
  "date": DataType(
@@ -203,7 +248,7 @@ predefined_datatypes = {
203
248
  mapped_pytype="date",
204
249
  deps=["sqlalchemy.Date", "datetime.date"],
205
250
  ),
206
- tstype=TsTypeWithDep(type="string"),
251
+ tstype=TsTypeWithDep(type="Date", spectype="date"),
207
252
  is_list=False,
208
253
  ),
209
254
  "datetime": DataType(
@@ -213,7 +258,7 @@ predefined_datatypes = {
213
258
  mapped_pytype="datetime",
214
259
  deps=["sqlalchemy.DateTime", "datetime.datetime"],
215
260
  ),
216
- tstype=TsTypeWithDep(type="string"),
261
+ tstype=TsTypeWithDep(type="Date", spectype="datetime"),
217
262
  is_list=False,
218
263
  ),
219
264
  "float": DataType(
@@ -221,7 +266,7 @@ predefined_datatypes = {
221
266
  sqltype=SQLTypeWithDep(
222
267
  type="Float", mapped_pytype="float", deps=["sqlalchemy.Float"]
223
268
  ),
224
- tstype=TsTypeWithDep(type="number"),
269
+ tstype=TsTypeWithDep(type="number", spectype="float"),
225
270
  is_list=False,
226
271
  ),
227
272
  "boolean": DataType(
@@ -229,7 +274,7 @@ predefined_datatypes = {
229
274
  sqltype=SQLTypeWithDep(
230
275
  type="Boolean", mapped_pytype="bool", deps=["sqlalchemy.Boolean"]
231
276
  ),
232
- tstype=TsTypeWithDep(type="boolean"),
277
+ tstype=TsTypeWithDep(type="boolean", spectype="boolean"),
233
278
  is_list=False,
234
279
  ),
235
280
  "bytes": DataType(
@@ -237,7 +282,7 @@ predefined_datatypes = {
237
282
  sqltype=SQLTypeWithDep(
238
283
  type="LargeBinary", mapped_pytype="bytes", deps=["sqlalchemy.LargeBinary"]
239
284
  ),
240
- tstype=TsTypeWithDep(type="string"),
285
+ tstype=TsTypeWithDep(type="string", spectype="bytes"),
241
286
  is_list=False,
242
287
  ),
243
288
  "dict": DataType(
@@ -245,7 +290,7 @@ predefined_datatypes = {
245
290
  sqltype=SQLTypeWithDep(
246
291
  type="JSON", mapped_pytype="dict", deps=["sqlalchemy.JSON"]
247
292
  ),
248
- tstype=TsTypeWithDep(type="string"),
293
+ tstype=TsTypeWithDep(type="string", spectype="dict"),
249
294
  is_list=False,
250
295
  ),
251
296
  }
@@ -257,5 +302,5 @@ predefined_sql_datatypes = {
257
302
  ),
258
303
  }
259
304
  predefined_ts_datatypes = {
260
- "string": TsTypeWithDep(type="string"),
305
+ "string": TsTypeWithDep(type="string", spectype="string"),
261
306
  }
sera/models/_parse.py CHANGED
@@ -246,7 +246,9 @@ def _parse_datatype(schema: Schema, datatype: dict | str) -> DataType:
246
246
  ],
247
247
  ),
248
248
  tstype=TsTypeWithDep(
249
- type=enum.name, deps=[f"@.models.enums.{enum.name}"]
249
+ type=enum.name,
250
+ spectype=enum.name,
251
+ deps=[f"@.models.enums.{enum.name}"],
250
252
  ),
251
253
  is_list=is_list,
252
254
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.20.4
3
+ Version: 1.20.12
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3.12
10
10
  Classifier: Programming Language :: Python :: 3.13
11
11
  Requires-Dist: black (==25.1.0)
12
12
  Requires-Dist: codegen-2 (>=2.12.2,<3.0.0)
13
- Requires-Dist: graph-wrapper (>=1.7.2,<2.0.0)
13
+ Requires-Dist: graph-wrapper (>=1.7.3,<2.0.0)
14
14
  Requires-Dist: isort (==6.0.1)
15
15
  Requires-Dist: litestar (>=2.15.1,<3.0.0)
16
16
  Requires-Dist: loguru (>=0.7.0,<0.8.0)
@@ -18,7 +18,7 @@ Requires-Dist: msgspec (>=0.19.0,<0.20.0)
18
18
  Requires-Dist: serde2 (>=1.9.2,<2.0.0)
19
19
  Requires-Dist: sqlalchemy[asyncio] (>=2.0.41,<3.0.0)
20
20
  Requires-Dist: tqdm (>=4.67.1,<5.0.0)
21
- Requires-Dist: typer (>=0.12.3,<0.13.0)
21
+ Requires-Dist: typer (>=0.16.0,<0.17.0)
22
22
  Project-URL: Repository, https://github.com/binh-vu/sera
23
23
  Description-Content-Type: text/markdown
24
24
 
@@ -4,7 +4,7 @@ sera/exports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  sera/exports/schema.py,sha256=wEBUrDOyuCoCJC8X4RlmoWpeqSugaboG-9Q1UQ8HEzk,7824
5
5
  sera/exports/test.py,sha256=jK1EJmLGiy7eREpnY_68IIVRH43uH8S_u5Z7STPbXOM,2002
6
6
  sera/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- sera/libs/api_helper.py,sha256=VZsYDeeMxzH4-7quM8W-1rl3zLFEEVShb8kLHS2UdHE,6382
7
+ sera/libs/api_helper.py,sha256=-75GzGNboJnFUp45pxckDSBvOrnrA2Tw_UAZBnSK_20,6665
8
8
  sera/libs/api_test_helper.py,sha256=3tRr8sLN4dBSrHgKAHMmyoENI0xh7K_JLel8AvujU7k,1323
9
9
  sera/libs/base_orm.py,sha256=5hOH_diUeaABm3cpE2-9u50VRqG1QW2osPQnvVHIhIA,3365
10
10
  sera/libs/base_service.py,sha256=V6wug1QA5KD0FsQU9r1XQmLUTB85E6oIR2e12Fi1IE0,6457
@@ -18,30 +18,30 @@ sera/libs/directed_computing_graph/_runtime.py,sha256=76Ccl1Rj31SkzRJPWFvYNu9ZzU
18
18
  sera/libs/directed_computing_graph/_type_conversion.py,sha256=_XGvDidOJVmHS4gqdPlhJGzdV34YtNiPF5Kr2nV6ZgE,6806
19
19
  sera/libs/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  sera/libs/middlewares/auth.py,sha256=r6aix1ZBwxMd1Jv5hMCTB8a_gFOJQ6egvxIrf3DWEOs,2323
21
- sera/libs/middlewares/uscp.py,sha256=H5umW8iEQSCdb_MJ5Im49kxg1E7TpxSg1p2_2A5zI1U,2600
21
+ sera/libs/middlewares/uscp.py,sha256=DRy99nmS3qS5HLjRMIGP0oNUtQIci_a4hL5xQh-lXNY,2322
22
22
  sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  sera/make/__main__.py,sha256=HRfOR53p351h6KblVvYm3DLhDIfEtk6R0kjl78_S_S8,1453
24
24
  sera/make/make_app.py,sha256=n9NtW73O3s_5Q31VHIRmnd-jEIcpDO7ksAsOdovde2s,5999
25
25
  sera/make/make_python_api.py,sha256=aOm8QSiXNLe4akiOx_KKsDCwLVwetRzuxOgZaWqEj0w,27172
26
26
  sera/make/make_python_model.py,sha256=t_2RUaO6WL4b5FtYj-Ly70njmcGGYwoddbL_oim9378,63479
27
27
  sera/make/make_python_services.py,sha256=0ZpWLwQ7Nwfn8BXAikAB4JRpNknpSJyJgY5b1cjtxV4,2073
28
- sera/make/make_typescript_model.py,sha256=u9pbe9JnSwiV1wLAS_H3p2mdOL_FKXjsZRCRuPu2QBY,69156
29
- sera/misc/__init__.py,sha256=rOmGMv7QNzpSKZSyxChbRmEnBr3O443UlLGS0FIs3AI,561
28
+ sera/make/make_typescript_model.py,sha256=CZc_LMigy7e6tP-6bJ5gHZlHYeyRtU_CNR4s64x51xE,71369
29
+ sera/misc/__init__.py,sha256=BuwDvVl0nYAA_pYM0MAbRl8DqmuasZzidO46kNiu-5A,601
30
30
  sera/misc/_formatter.py,sha256=aCGYL08l8f3aLODHxSocxBBwkRYEo3K1QzCDEn3suj0,1685
31
- sera/misc/_utils.py,sha256=_-17XbK6qp3HobcI9iLF4xfaATvFg1ckUzgg7r7Ctmw,7135
31
+ sera/misc/_utils.py,sha256=bum84Eh6gVsax0Gl4SxkmD6hUWazEljxsV16Q3BNcN8,9019
32
32
  sera/models/__init__.py,sha256=vJC5Kzo_N7wd16ocNPy1VvAZDGNiWeiAhWJ4ihATKvA,780
33
33
  sera/models/_class.py,sha256=1J4Bd_LanzhhDWwZFHWGtFYD7lupe_alaB3D02ebNDI,2862
34
34
  sera/models/_collection.py,sha256=nLlP85OfEhfj4pFAYxB5ZvtBN6EdPSWPJ5ZFh5SSEC4,2686
35
35
  sera/models/_constraints.py,sha256=RpWDU-TfCslXaMUaTG9utWbl5z8Z6nzvF_fhqlek6ew,1987
36
- sera/models/_datatype.py,sha256=6s2TIhUX8Eb1w3XXKUcOoVybF_lrmKtNAriDgzv2JAE,8407
36
+ sera/models/_datatype.py,sha256=Whfdyt3X0-T_eLi-K38P8V-87AMna69R_1yeqWwjjXI,10611
37
37
  sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
38
38
  sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
39
39
  sera/models/_module.py,sha256=I-GfnTgAa-5R87qTAvEzOt-VVEGeFBBwubGCgUkXVSw,5159
40
40
  sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
41
- sera/models/_parse.py,sha256=sm-TuhFuru4e-g8HYmPo694sGJooBtIFZeky24X7YYk,12588
41
+ sera/models/_parse.py,sha256=9iaW-8Ajq8e440FrTJ-X9oivHJJ447kPU-ZqffFxSvw,12649
42
42
  sera/models/_property.py,sha256=9yMDxrmbyuF6-29lQjiq163Xzwbk75TlmGBpu0NLpkI,7485
43
43
  sera/models/_schema.py,sha256=VxJEiqgVvbXgcSUK4UW6JnRcggk4nsooVSE6MyXmfNY,1636
44
44
  sera/typing.py,sha256=m4rir-fB6Cgcm7_ZSXXcNdla2LJgq96WXxtTTrDaJno,1058
45
- sera_2-1.20.4.dist-info/METADATA,sha256=2HIbHZt_ScaEAzPZGlpNNX7jEmiv1sGmMVgmK-7nJU4,936
46
- sera_2-1.20.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
47
- sera_2-1.20.4.dist-info/RECORD,,
45
+ sera_2-1.20.12.dist-info/METADATA,sha256=KspmNESywYnx1I-C6I_QzElaqC3-07W11VyQ6a9CbrE,937
46
+ sera_2-1.20.12.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
47
+ sera_2-1.20.12.dist-info/RECORD,,