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 +9 -4
- sera/libs/middlewares/uscp.py +4 -9
- sera/make/make_typescript_model.py +70 -22
- sera/misc/__init__.py +2 -0
- sera/misc/_utils.py +55 -3
- sera/models/_datatype.py +57 -12
- sera/models/_parse.py +3 -1
- {sera_2-1.20.4.dist-info → sera_2-1.20.12.dist-info}/METADATA +3 -3
- {sera_2-1.20.4.dist-info → sera_2-1.20.12.dist-info}/RECORD +10 -10
- {sera_2-1.20.4.dist-info → sera_2-1.20.12.dist-info}/WHEEL +0 -0
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
|
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
|
-
|
140
|
-
|
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
|
|
sera/libs/middlewares/uscp.py
CHANGED
@@ -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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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(
|
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
|
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
|
-
|
484
|
-
|
485
|
-
|
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
|
492
|
-
|
493
|
-
|
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.
|
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.
|
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[
|
124
|
-
record_deser:
|
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
|
-
|
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
|
-
|
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(
|
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="
|
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="
|
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,
|
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.
|
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.
|
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.
|
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
|
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=
|
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=
|
29
|
-
sera/misc/__init__.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
46
|
-
sera_2-1.20.
|
47
|
-
sera_2-1.20.
|
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,,
|
File without changes
|