sera-2 1.20.4__py3-none-any.whl → 1.20.13__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 +10 -5
- 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 +70 -12
- sera/models/_parse.py +3 -1
- {sera_2-1.20.4.dist-info → sera_2-1.20.13.dist-info}/METADATA +3 -3
- {sera_2-1.20.4.dist-info → sera_2-1.20.13.dist-info}/RECORD +10 -10
- {sera_2-1.20.4.dist-info → sera_2-1.20.13.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
|
@@ -91,7 +91,7 @@ def parse_query(
|
|
91
91
|
v = [norm_func(x) for x in v]
|
92
92
|
else:
|
93
93
|
v = norm_func(v)
|
94
|
-
except ValueError:
|
94
|
+
except (ValueError, KeyError):
|
95
95
|
if debug:
|
96
96
|
raise HTTPException(
|
97
97
|
status_code=status_codes.HTTP_400_BAD_REQUEST,
|
@@ -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[
|
@@ -76,6 +78,19 @@ class PyTypeWithDep:
|
|
76
78
|
return ("TypeConversion.to_float", "sera.libs.api_helper.TypeConversion")
|
77
79
|
if self.type == "bool":
|
78
80
|
return ("TypeConversion.to_bool", "sera.libs.api_helper.TypeConversion")
|
81
|
+
if any(
|
82
|
+
dep.find(".models.enums.") != -1 and dep.endswith(self.type)
|
83
|
+
for dep in self.deps
|
84
|
+
):
|
85
|
+
# This is an enum type, we directly use the enum constructor as the conversion function
|
86
|
+
return (
|
87
|
+
self.type,
|
88
|
+
[
|
89
|
+
dep
|
90
|
+
for dep in self.deps
|
91
|
+
if dep.find(".models.enums.") != -1 and dep.endswith(self.type)
|
92
|
+
][0],
|
93
|
+
)
|
79
94
|
else:
|
80
95
|
raise NotImplementedError()
|
81
96
|
|
@@ -83,6 +98,9 @@ class PyTypeWithDep:
|
|
83
98
|
@dataclass
|
84
99
|
class TsTypeWithDep:
|
85
100
|
type: str
|
101
|
+
# the specific type of the value, to provide more details for the type because typescript use
|
102
|
+
# number for both int and float, date for both date and datetime.
|
103
|
+
spectype: str
|
86
104
|
deps: list[str] = field(default_factory=list)
|
87
105
|
|
88
106
|
def get_default(self) -> expr.ExprConstant:
|
@@ -94,6 +112,8 @@ class TsTypeWithDep:
|
|
94
112
|
return expr.ExprConstant(0)
|
95
113
|
if self.type == "boolean":
|
96
114
|
return expr.ExprConstant(False)
|
115
|
+
if self.type == "Date":
|
116
|
+
return expr.ExprConstant("new Date()")
|
97
117
|
if self.type.endswith("| undefined"):
|
98
118
|
return expr.ExprConstant("undefined")
|
99
119
|
if self.type.endswith("| string)") or self.type.endswith("| string"):
|
@@ -108,16 +128,54 @@ class TsTypeWithDep:
|
|
108
128
|
if not all(c.isalnum() or c == "_" for c in self.type.strip()):
|
109
129
|
# Type contains special chars like | or spaces, wrap in parentheses
|
110
130
|
list_type = f"({self.type})[]"
|
131
|
+
list_spectype = f"({self.spectype})[]"
|
111
132
|
else:
|
112
133
|
list_type = f"{self.type}[]"
|
113
|
-
|
134
|
+
list_spectype = f"{self.spectype}[]"
|
135
|
+
return TsTypeWithDep(type=list_type, spectype=list_spectype, deps=self.deps)
|
114
136
|
|
115
137
|
def as_optional_type(self) -> TsTypeWithDep:
|
116
138
|
if "undefined" in self.type:
|
117
139
|
raise NotImplementedError(
|
118
140
|
f"Have not handle nested optional yet: {self.type}"
|
119
141
|
)
|
120
|
-
return TsTypeWithDep(
|
142
|
+
return TsTypeWithDep(
|
143
|
+
type=f"{self.type} | undefined",
|
144
|
+
# not changing the spectype because we convert to optional when the value is missing
|
145
|
+
# spectype is used to tell the main type of the value when it is present.
|
146
|
+
spectype=self.spectype,
|
147
|
+
deps=self.deps,
|
148
|
+
)
|
149
|
+
|
150
|
+
def get_json_deser_func(self, value: expr.Expr) -> expr.Expr:
|
151
|
+
"""Get the typescript expression to convert the value from json format to the correct type."""
|
152
|
+
if self.type in {"string", "number", "boolean", "string[]"}:
|
153
|
+
return value
|
154
|
+
if self.type == "Date":
|
155
|
+
return expr.ExprRawTypescript(f"new Date({value.to_typescript()})")
|
156
|
+
if any(x.startswith("@.models.enum") for x in self.deps):
|
157
|
+
# enum type, we don't need to do anything as we use strings for enums
|
158
|
+
return value
|
159
|
+
raise ValueError(f"Unknown type: {self.type}")
|
160
|
+
|
161
|
+
def get_json_ser_func(self, value: expr.Expr) -> expr.Expr:
|
162
|
+
"""Get the typescript expression to convert the value to json format."""
|
163
|
+
if self.type in {
|
164
|
+
"string",
|
165
|
+
"number",
|
166
|
+
"boolean",
|
167
|
+
"string[]",
|
168
|
+
"number | undefined",
|
169
|
+
"boolean | undefined",
|
170
|
+
"string | undefined",
|
171
|
+
}:
|
172
|
+
return value
|
173
|
+
if self.type == "Date":
|
174
|
+
return expr.ExprRawTypescript(f"{value.to_typescript()}.toISOString()")
|
175
|
+
if any(x.startswith("@.models.enum") for x in self.deps):
|
176
|
+
# enum type, we don't need to do anything as we use strings for enums
|
177
|
+
return value
|
178
|
+
raise ValueError(f"Unknown type: {self.type}")
|
121
179
|
|
122
180
|
|
123
181
|
@dataclass
|
@@ -185,7 +243,7 @@ predefined_datatypes = {
|
|
185
243
|
sqltype=SQLTypeWithDep(
|
186
244
|
type="String", mapped_pytype="str", deps=["sqlalchemy.String"]
|
187
245
|
),
|
188
|
-
tstype=TsTypeWithDep(type="string"),
|
246
|
+
tstype=TsTypeWithDep(type="string", spectype="string"),
|
189
247
|
is_list=False,
|
190
248
|
),
|
191
249
|
"integer": DataType(
|
@@ -193,7 +251,7 @@ predefined_datatypes = {
|
|
193
251
|
sqltype=SQLTypeWithDep(
|
194
252
|
type="Integer", mapped_pytype="int", deps=["sqlalchemy.Integer"]
|
195
253
|
),
|
196
|
-
tstype=TsTypeWithDep(type="number"),
|
254
|
+
tstype=TsTypeWithDep(type="number", spectype="integer"),
|
197
255
|
is_list=False,
|
198
256
|
),
|
199
257
|
"date": DataType(
|
@@ -203,7 +261,7 @@ predefined_datatypes = {
|
|
203
261
|
mapped_pytype="date",
|
204
262
|
deps=["sqlalchemy.Date", "datetime.date"],
|
205
263
|
),
|
206
|
-
tstype=TsTypeWithDep(type="
|
264
|
+
tstype=TsTypeWithDep(type="Date", spectype="date"),
|
207
265
|
is_list=False,
|
208
266
|
),
|
209
267
|
"datetime": DataType(
|
@@ -213,7 +271,7 @@ predefined_datatypes = {
|
|
213
271
|
mapped_pytype="datetime",
|
214
272
|
deps=["sqlalchemy.DateTime", "datetime.datetime"],
|
215
273
|
),
|
216
|
-
tstype=TsTypeWithDep(type="
|
274
|
+
tstype=TsTypeWithDep(type="Date", spectype="datetime"),
|
217
275
|
is_list=False,
|
218
276
|
),
|
219
277
|
"float": DataType(
|
@@ -221,7 +279,7 @@ predefined_datatypes = {
|
|
221
279
|
sqltype=SQLTypeWithDep(
|
222
280
|
type="Float", mapped_pytype="float", deps=["sqlalchemy.Float"]
|
223
281
|
),
|
224
|
-
tstype=TsTypeWithDep(type="number"),
|
282
|
+
tstype=TsTypeWithDep(type="number", spectype="float"),
|
225
283
|
is_list=False,
|
226
284
|
),
|
227
285
|
"boolean": DataType(
|
@@ -229,7 +287,7 @@ predefined_datatypes = {
|
|
229
287
|
sqltype=SQLTypeWithDep(
|
230
288
|
type="Boolean", mapped_pytype="bool", deps=["sqlalchemy.Boolean"]
|
231
289
|
),
|
232
|
-
tstype=TsTypeWithDep(type="boolean"),
|
290
|
+
tstype=TsTypeWithDep(type="boolean", spectype="boolean"),
|
233
291
|
is_list=False,
|
234
292
|
),
|
235
293
|
"bytes": DataType(
|
@@ -237,7 +295,7 @@ predefined_datatypes = {
|
|
237
295
|
sqltype=SQLTypeWithDep(
|
238
296
|
type="LargeBinary", mapped_pytype="bytes", deps=["sqlalchemy.LargeBinary"]
|
239
297
|
),
|
240
|
-
tstype=TsTypeWithDep(type="string"),
|
298
|
+
tstype=TsTypeWithDep(type="string", spectype="bytes"),
|
241
299
|
is_list=False,
|
242
300
|
),
|
243
301
|
"dict": DataType(
|
@@ -245,7 +303,7 @@ predefined_datatypes = {
|
|
245
303
|
sqltype=SQLTypeWithDep(
|
246
304
|
type="JSON", mapped_pytype="dict", deps=["sqlalchemy.JSON"]
|
247
305
|
),
|
248
|
-
tstype=TsTypeWithDep(type="string"),
|
306
|
+
tstype=TsTypeWithDep(type="string", spectype="dict"),
|
249
307
|
is_list=False,
|
250
308
|
),
|
251
309
|
}
|
@@ -257,5 +315,5 @@ predefined_sql_datatypes = {
|
|
257
315
|
),
|
258
316
|
}
|
259
317
|
predefined_ts_datatypes = {
|
260
|
-
"string": TsTypeWithDep(type="string"),
|
318
|
+
"string": TsTypeWithDep(type="string", spectype="string"),
|
261
319
|
}
|
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.13
|
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=lSnnNz-LzNaWmb5g7ic9bpJHl9IQd8u1GqH1kHFR3xk,6677
|
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=RUKXwOjTr4YTloB0KQcOOAARXamoqMDXGtZ_xjldwD4,11095
|
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.13.dist-info/METADATA,sha256=Z0AAY0cKuVh4__UTmDqCXW0LQygzBeVrM50JOYp1-wY,937
|
46
|
+
sera_2-1.20.13.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
47
|
+
sera_2-1.20.13.dist-info/RECORD,,
|
File without changes
|