sera-2 1.23.0__py3-none-any.whl → 1.25.0__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/base_service.py +85 -21
- sera/libs/search_helper.py +4 -2
- sera/make/make_app.py +4 -5
- sera/make/make_python_model.py +70 -3
- sera/make/make_typescript_model.py +8 -347
- sera/make/ts_frontend/__init__.py +0 -0
- sera/make/ts_frontend/make_class_schema.py +369 -0
- sera/make/ts_frontend/make_enums.py +104 -0
- sera/make/ts_frontend/misc.py +38 -0
- sera/models/__init__.py +8 -1
- sera/models/_class.py +2 -1
- sera/models/_enum.py +2 -1
- sera/models/_parse.py +15 -2
- sera/models/_property.py +8 -0
- sera/typing.py +1 -0
- {sera_2-1.23.0.dist-info → sera_2-1.25.0.dist-info}/METADATA +4 -3
- {sera_2-1.23.0.dist-info → sera_2-1.25.0.dist-info}/RECORD +18 -14
- {sera_2-1.23.0.dist-info → sera_2-1.25.0.dist-info}/WHEEL +1 -1
@@ -0,0 +1,369 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from codegen.models import ImportHelper, PredefinedFn, Program, expr, stmt
|
4
|
+
|
5
|
+
from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
|
6
|
+
from sera.misc import assert_not_null, to_camel_case, to_pascal_case
|
7
|
+
from sera.models import (
|
8
|
+
Class,
|
9
|
+
DataProperty,
|
10
|
+
Enum,
|
11
|
+
ObjectProperty,
|
12
|
+
Package,
|
13
|
+
Schema,
|
14
|
+
TsTypeWithDep,
|
15
|
+
)
|
16
|
+
from sera.typing import is_set
|
17
|
+
|
18
|
+
|
19
|
+
def make_class_schema(schema: Schema, cls: Class, pkg: Package):
|
20
|
+
"""Make schema definition for the class in frontend so that components can use this information select
|
21
|
+
appropriate components to display or edit the data.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
schema: The overall schema of the application, which contains all classes & enums
|
25
|
+
cls: The class that we want to generate the schema
|
26
|
+
pkg: The output package (directory) for the class in the `@.models` package. For example, if the
|
27
|
+
class is `User`, then the package would be `src/models/user`.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
This function do not return anything as it writes the schema directly to a file.
|
31
|
+
"""
|
32
|
+
if not cls.is_public:
|
33
|
+
# skip classes that are not public
|
34
|
+
return
|
35
|
+
|
36
|
+
program = Program()
|
37
|
+
prop_defs: list[tuple[DataProperty | ObjectProperty, expr.Expr, expr.Expr]] = []
|
38
|
+
prop_normalizers: list[tuple[expr.Expr, expr.Expr]] = []
|
39
|
+
|
40
|
+
import_helper = ImportHelper(program, TS_GLOBAL_IDENTS)
|
41
|
+
|
42
|
+
for prop in cls.properties.values():
|
43
|
+
# we must include private properties that are needed during upsert for our forms.
|
44
|
+
# if prop.data.is_private:
|
45
|
+
# # skip private fields as this is for APIs exchange
|
46
|
+
# continue
|
47
|
+
tspropname = to_camel_case(prop.name)
|
48
|
+
pypropname = prop.name
|
49
|
+
if isinstance(prop, ObjectProperty) and prop.target.db is not None:
|
50
|
+
# this is a database object, we append id to the property name
|
51
|
+
tspropname = tspropname + "Id"
|
52
|
+
pypropname = prop.name + "_id"
|
53
|
+
|
54
|
+
tsprop = {}
|
55
|
+
|
56
|
+
if isinstance(prop, DataProperty):
|
57
|
+
tstype = prop.get_data_model_datatype().get_typescript_type()
|
58
|
+
# for schema definition, we need to use the original type, not the type alias
|
59
|
+
# if prop.name == idprop.name:
|
60
|
+
# # use id type alias
|
61
|
+
# tstype = TsTypeWithDep(f"{cls.name}Id")
|
62
|
+
for dep in tstype.deps:
|
63
|
+
program.import_(dep, True)
|
64
|
+
tsprop = [
|
65
|
+
(
|
66
|
+
expr.ExprIdent("datatype"),
|
67
|
+
(
|
68
|
+
expr.ExprConstant(tstype.spectype)
|
69
|
+
if tstype.type not in schema.enums
|
70
|
+
else expr.ExprConstant("enum")
|
71
|
+
),
|
72
|
+
),
|
73
|
+
*(
|
74
|
+
[
|
75
|
+
(
|
76
|
+
expr.ExprIdent("enumType"),
|
77
|
+
export_enum_info(program, schema.enums[tstype.type]),
|
78
|
+
)
|
79
|
+
]
|
80
|
+
if tstype.type in schema.enums
|
81
|
+
else []
|
82
|
+
),
|
83
|
+
*(
|
84
|
+
[
|
85
|
+
(
|
86
|
+
expr.ExprIdent("foreignKeyTarget"),
|
87
|
+
expr.ExprConstant(prop.db.foreign_key.name),
|
88
|
+
)
|
89
|
+
]
|
90
|
+
if prop.db is not None
|
91
|
+
and prop.db.is_primary_key
|
92
|
+
and prop.db.foreign_key is not None
|
93
|
+
else []
|
94
|
+
),
|
95
|
+
(
|
96
|
+
expr.ExprIdent("isRequired"),
|
97
|
+
expr.ExprConstant(
|
98
|
+
not prop.is_optional
|
99
|
+
and prop.default_value is None
|
100
|
+
and prop.default_factory is None
|
101
|
+
),
|
102
|
+
),
|
103
|
+
]
|
104
|
+
|
105
|
+
norm_func = get_normalizer(tstype, import_helper)
|
106
|
+
if norm_func is not None:
|
107
|
+
# we have a normalizer for this type
|
108
|
+
prop_normalizers.append((expr.ExprIdent(tspropname), norm_func))
|
109
|
+
else:
|
110
|
+
assert isinstance(prop, ObjectProperty)
|
111
|
+
if prop.target.db is not None:
|
112
|
+
# this class is stored in the database, we store the id instead
|
113
|
+
tstype = (
|
114
|
+
assert_not_null(prop.target.get_id_property())
|
115
|
+
.get_data_model_datatype()
|
116
|
+
.get_typescript_type()
|
117
|
+
)
|
118
|
+
else:
|
119
|
+
# we are going to store the whole object
|
120
|
+
tstype = TsTypeWithDep(
|
121
|
+
type=prop.target.name,
|
122
|
+
spectype=prop.target.name,
|
123
|
+
deps=[
|
124
|
+
f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
|
125
|
+
],
|
126
|
+
)
|
127
|
+
|
128
|
+
# we don't store the type itself, but just the name of the type
|
129
|
+
# so not need to import the dependency
|
130
|
+
# if tstype.dep is not None:
|
131
|
+
# program.import_(
|
132
|
+
# tstype.dep,
|
133
|
+
# True,
|
134
|
+
# )
|
135
|
+
|
136
|
+
tsprop = [
|
137
|
+
(
|
138
|
+
expr.ExprIdent("targetClass"),
|
139
|
+
expr.ExprConstant(prop.target.name),
|
140
|
+
),
|
141
|
+
(
|
142
|
+
expr.ExprIdent("datatype"),
|
143
|
+
expr.ExprConstant(
|
144
|
+
tstype.spectype if prop.target.db is not None else "undefined"
|
145
|
+
),
|
146
|
+
),
|
147
|
+
(
|
148
|
+
expr.ExprIdent("cardinality"),
|
149
|
+
expr.ExprConstant(prop.cardinality.value),
|
150
|
+
),
|
151
|
+
(
|
152
|
+
expr.ExprIdent("isEmbedded"),
|
153
|
+
expr.ExprConstant(prop.target.db is None),
|
154
|
+
),
|
155
|
+
(
|
156
|
+
expr.ExprIdent("isRequired"),
|
157
|
+
expr.ExprConstant(not prop.is_optional),
|
158
|
+
),
|
159
|
+
]
|
160
|
+
|
161
|
+
prop_defs.append(
|
162
|
+
(
|
163
|
+
prop,
|
164
|
+
expr.ExprIdent(tspropname),
|
165
|
+
PredefinedFn.dict(
|
166
|
+
[
|
167
|
+
(expr.ExprIdent("name"), expr.ExprConstant(pypropname)),
|
168
|
+
(expr.ExprIdent("tsName"), expr.ExprConstant(tspropname)),
|
169
|
+
(
|
170
|
+
expr.ExprIdent("updateFuncName"),
|
171
|
+
expr.ExprConstant(f"update{to_pascal_case(prop.name)}"),
|
172
|
+
),
|
173
|
+
(
|
174
|
+
expr.ExprIdent("label"),
|
175
|
+
expr.ExprConstant(prop.label.to_dict()),
|
176
|
+
),
|
177
|
+
(
|
178
|
+
expr.ExprIdent("description"),
|
179
|
+
(
|
180
|
+
expr.ExprConstant(prop.description.to_dict())
|
181
|
+
if not prop.description.is_empty()
|
182
|
+
else expr.ExprConstant("undefined")
|
183
|
+
),
|
184
|
+
),
|
185
|
+
(
|
186
|
+
expr.ExprIdent("constraints"),
|
187
|
+
PredefinedFn.list(
|
188
|
+
[
|
189
|
+
expr.ExprConstant(
|
190
|
+
constraint.get_typescript_constraint()
|
191
|
+
)
|
192
|
+
for constraint in prop.data.constraints
|
193
|
+
]
|
194
|
+
),
|
195
|
+
),
|
196
|
+
]
|
197
|
+
+ tsprop
|
198
|
+
),
|
199
|
+
)
|
200
|
+
)
|
201
|
+
|
202
|
+
for type in ["ObjectProperty", "DataProperty"]:
|
203
|
+
program.import_(f"sera-db.{type}", True)
|
204
|
+
if cls.db is not None:
|
205
|
+
program.import_(f"sera-db.Schema", True)
|
206
|
+
else:
|
207
|
+
program.import_(f"sera-db.EmbeddedSchema", True)
|
208
|
+
|
209
|
+
program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
|
210
|
+
program.import_(f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True)
|
211
|
+
program.import_(
|
212
|
+
f"@.models.{pkg.dir.name}.Draft{cls.name}.draft{cls.name}Validators", True
|
213
|
+
)
|
214
|
+
if cls.db is not None:
|
215
|
+
program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
|
216
|
+
|
217
|
+
program.root(
|
218
|
+
stmt.LineBreak(),
|
219
|
+
stmt.TypescriptStatement(
|
220
|
+
f"export type {cls.name}SchemaType = "
|
221
|
+
+ PredefinedFn.dict(
|
222
|
+
(
|
223
|
+
[
|
224
|
+
(expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
|
225
|
+
]
|
226
|
+
if cls.db is not None
|
227
|
+
else []
|
228
|
+
)
|
229
|
+
+ [
|
230
|
+
(
|
231
|
+
expr.ExprIdent("publicProperties"),
|
232
|
+
expr.ExprIdent(
|
233
|
+
" | ".join(
|
234
|
+
[
|
235
|
+
expr.ExprConstant(
|
236
|
+
to_camel_case(prop.name) + "Id"
|
237
|
+
if isinstance(prop, ObjectProperty)
|
238
|
+
and prop.target.db is not None
|
239
|
+
else to_camel_case(prop.name)
|
240
|
+
).to_typescript()
|
241
|
+
for prop in cls.properties.values()
|
242
|
+
if not prop.data.is_private
|
243
|
+
]
|
244
|
+
)
|
245
|
+
),
|
246
|
+
),
|
247
|
+
(
|
248
|
+
expr.ExprIdent("allProperties"),
|
249
|
+
expr.ExprIdent(
|
250
|
+
f"{cls.name}SchemaType['publicProperties']"
|
251
|
+
+ (
|
252
|
+
" | "
|
253
|
+
+ " | ".join(
|
254
|
+
[
|
255
|
+
expr.ExprConstant(
|
256
|
+
to_camel_case(prop.name)
|
257
|
+
).to_typescript()
|
258
|
+
for prop in cls.properties.values()
|
259
|
+
if prop.data.is_private
|
260
|
+
]
|
261
|
+
)
|
262
|
+
if any(
|
263
|
+
prop.data.is_private
|
264
|
+
for prop in cls.properties.values()
|
265
|
+
)
|
266
|
+
else ""
|
267
|
+
)
|
268
|
+
),
|
269
|
+
),
|
270
|
+
(
|
271
|
+
expr.ExprIdent("cls"),
|
272
|
+
expr.ExprIdent(cls.name),
|
273
|
+
),
|
274
|
+
(
|
275
|
+
expr.ExprIdent("draftCls"),
|
276
|
+
expr.ExprIdent(f"Draft{cls.name}"),
|
277
|
+
),
|
278
|
+
]
|
279
|
+
).to_typescript()
|
280
|
+
+ ";",
|
281
|
+
),
|
282
|
+
stmt.LineBreak(),
|
283
|
+
stmt.TypescriptStatement(
|
284
|
+
f"const publicProperties: Record<{cls.name}SchemaType['publicProperties'], DataProperty | ObjectProperty> = "
|
285
|
+
+ PredefinedFn.dict(
|
286
|
+
[
|
287
|
+
(prop_name, prop_def)
|
288
|
+
for prop, prop_name, prop_def in prop_defs
|
289
|
+
if not prop.data.is_private
|
290
|
+
]
|
291
|
+
).to_typescript()
|
292
|
+
+ ";"
|
293
|
+
),
|
294
|
+
stmt.LineBreak(),
|
295
|
+
stmt.TypescriptStatement(
|
296
|
+
(
|
297
|
+
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> = "
|
298
|
+
if cls.db is not None
|
299
|
+
else f"export const {cls.name}Schema: EmbeddedSchema<{cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties']> = "
|
300
|
+
)
|
301
|
+
+ PredefinedFn.dict(
|
302
|
+
[
|
303
|
+
(
|
304
|
+
expr.ExprIdent("publicProperties"),
|
305
|
+
expr.ExprIdent("publicProperties"),
|
306
|
+
),
|
307
|
+
(
|
308
|
+
expr.ExprIdent("allProperties"),
|
309
|
+
expr.ExprIdent(
|
310
|
+
"{ ...publicProperties, "
|
311
|
+
+ ", ".join(
|
312
|
+
[
|
313
|
+
f"{prop_name.to_typescript()}: {prop_def.to_typescript()}"
|
314
|
+
for prop, prop_name, prop_def in prop_defs
|
315
|
+
if prop.data.is_private
|
316
|
+
]
|
317
|
+
)
|
318
|
+
+ "}"
|
319
|
+
),
|
320
|
+
),
|
321
|
+
(
|
322
|
+
expr.ExprIdent("validators"),
|
323
|
+
expr.ExprIdent(f"draft{cls.name}Validators"),
|
324
|
+
),
|
325
|
+
(
|
326
|
+
expr.ExprIdent("normalizers"),
|
327
|
+
PredefinedFn.dict(prop_normalizers),
|
328
|
+
),
|
329
|
+
]
|
330
|
+
+ (
|
331
|
+
[
|
332
|
+
(
|
333
|
+
expr.ExprIdent("primaryKey"),
|
334
|
+
expr.ExprConstant(
|
335
|
+
assert_not_null(cls.get_id_property()).name
|
336
|
+
),
|
337
|
+
)
|
338
|
+
]
|
339
|
+
if cls.db is not None
|
340
|
+
else []
|
341
|
+
)
|
342
|
+
).to_typescript()
|
343
|
+
+ ";"
|
344
|
+
),
|
345
|
+
)
|
346
|
+
pkg.module(cls.name + "Schema").write(program)
|
347
|
+
|
348
|
+
|
349
|
+
def export_enum_info(program: Program, enum: Enum) -> expr.Expr:
|
350
|
+
"""Export enum information to
|
351
|
+
|
352
|
+
```
|
353
|
+
{
|
354
|
+
type: <EnumType>,
|
355
|
+
label: { [value]: MultiLingualString },
|
356
|
+
description: { [value]: MultiLingualString }
|
357
|
+
}
|
358
|
+
```
|
359
|
+
"""
|
360
|
+
for key in ["Label", "Description"]:
|
361
|
+
program.import_(f"@.models.enums.{enum.name}{key}", True)
|
362
|
+
|
363
|
+
return PredefinedFn.dict(
|
364
|
+
[
|
365
|
+
(expr.ExprIdent("type"), expr.ExprIdent(enum.name)),
|
366
|
+
(expr.ExprIdent("label"), expr.ExprIdent(enum.name + "Label")),
|
367
|
+
(expr.ExprIdent("description"), expr.ExprIdent(enum.name + "Description")),
|
368
|
+
]
|
369
|
+
)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import re
|
4
|
+
from typing import Any, Callable
|
5
|
+
|
6
|
+
from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
|
7
|
+
from codegen.models.var import DeferredVar
|
8
|
+
from loguru import logger
|
9
|
+
|
10
|
+
from sera.make.ts_frontend.make_class_schema import make_class_schema
|
11
|
+
from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
|
12
|
+
from sera.misc import (
|
13
|
+
assert_isinstance,
|
14
|
+
assert_not_null,
|
15
|
+
identity,
|
16
|
+
to_camel_case,
|
17
|
+
to_pascal_case,
|
18
|
+
to_snake_case,
|
19
|
+
)
|
20
|
+
from sera.models import (
|
21
|
+
Class,
|
22
|
+
DataProperty,
|
23
|
+
Enum,
|
24
|
+
ObjectProperty,
|
25
|
+
Package,
|
26
|
+
Schema,
|
27
|
+
TsTypeWithDep,
|
28
|
+
)
|
29
|
+
from sera.typing import is_set
|
30
|
+
|
31
|
+
|
32
|
+
def make_typescript_enums(schema: Schema, target_pkg: Package):
|
33
|
+
"""Make typescript enum for the schema"""
|
34
|
+
enum_pkg = target_pkg.pkg("enums")
|
35
|
+
|
36
|
+
for enum in schema.enums.values():
|
37
|
+
make_enum(enum, enum_pkg)
|
38
|
+
|
39
|
+
program = Program()
|
40
|
+
for enum in schema.enums.values():
|
41
|
+
program.import_(f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}", True)
|
42
|
+
program.import_(
|
43
|
+
f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}Label", True
|
44
|
+
)
|
45
|
+
program.import_(
|
46
|
+
f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}Description", True
|
47
|
+
)
|
48
|
+
|
49
|
+
program.root(
|
50
|
+
stmt.LineBreak(),
|
51
|
+
stmt.TypescriptStatement(
|
52
|
+
"export { "
|
53
|
+
+ ", ".join([enum.name for enum in schema.enums.values()])
|
54
|
+
+ ","
|
55
|
+
+ ", ".join([enum.name + "Label" for enum in schema.enums.values()])
|
56
|
+
+ ","
|
57
|
+
+ ", ".join([enum.name + "Description" for enum in schema.enums.values()])
|
58
|
+
+ "};"
|
59
|
+
),
|
60
|
+
)
|
61
|
+
enum_pkg.module("index").write(program)
|
62
|
+
|
63
|
+
|
64
|
+
def make_enum(enum: Enum, pkg: Package):
|
65
|
+
program = Program()
|
66
|
+
program.root(
|
67
|
+
stmt.LineBreak(),
|
68
|
+
lambda ast: ast.class_like("enum", enum.name)(
|
69
|
+
*[
|
70
|
+
stmt.DefEnumValueStatement(
|
71
|
+
name=value.name,
|
72
|
+
value=expr.ExprConstant(value.value),
|
73
|
+
)
|
74
|
+
for value in enum.values.values()
|
75
|
+
]
|
76
|
+
),
|
77
|
+
stmt.LineBreak(),
|
78
|
+
stmt.TypescriptStatement(
|
79
|
+
f"export const {enum.name}Label = "
|
80
|
+
+ PredefinedFn.dict(
|
81
|
+
[
|
82
|
+
(
|
83
|
+
expr.ExprConstant(value.value),
|
84
|
+
expr.ExprConstant(value.label.to_dict()),
|
85
|
+
)
|
86
|
+
for value in enum.values.values()
|
87
|
+
]
|
88
|
+
).to_typescript()
|
89
|
+
),
|
90
|
+
stmt.LineBreak(),
|
91
|
+
stmt.TypescriptStatement(
|
92
|
+
f"export const {enum.name}Description = "
|
93
|
+
+ PredefinedFn.dict(
|
94
|
+
[
|
95
|
+
(
|
96
|
+
expr.ExprConstant(value.value),
|
97
|
+
expr.ExprConstant(value.description.to_dict()),
|
98
|
+
)
|
99
|
+
for value in enum.values.values()
|
100
|
+
]
|
101
|
+
).to_typescript()
|
102
|
+
),
|
103
|
+
)
|
104
|
+
pkg.module(enum.get_tsmodule_name()).write(program)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
|
6
|
+
|
7
|
+
from sera.models import (
|
8
|
+
Class,
|
9
|
+
DataProperty,
|
10
|
+
Enum,
|
11
|
+
ObjectProperty,
|
12
|
+
Package,
|
13
|
+
Schema,
|
14
|
+
TsTypeWithDep,
|
15
|
+
)
|
16
|
+
|
17
|
+
TS_GLOBAL_IDENTS = {
|
18
|
+
"normalizers.normalizeNumber": "sera-db.normalizers",
|
19
|
+
"normalizers.normalizeOptionalNumber": "sera-db.normalizers",
|
20
|
+
"normalizers.normalizeDate": "sera-db.normalizers",
|
21
|
+
"normalizers.normalizeOptionalDate": "sera-db.normalizers",
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
def get_normalizer(
|
26
|
+
tstype: TsTypeWithDep, import_helper: ImportHelper
|
27
|
+
) -> Optional[expr.ExprIdent]:
|
28
|
+
if tstype.type == "number":
|
29
|
+
return import_helper.use("normalizers.normalizeNumber")
|
30
|
+
if tstype.type == "number | undefined":
|
31
|
+
return import_helper.use("normalizers.normalizeOptionalNumber")
|
32
|
+
if tstype.type == "Date":
|
33
|
+
return import_helper.use("normalizers.normalizeDate")
|
34
|
+
if tstype.type == "Date | undefined":
|
35
|
+
return import_helper.use("normalizers.normalizeOptionalDate")
|
36
|
+
|
37
|
+
assert "number" not in tstype.type, tstype.type
|
38
|
+
return None
|
sera/models/__init__.py
CHANGED
@@ -5,7 +5,13 @@ from sera.models._enum import Enum
|
|
5
5
|
from sera.models._module import App, Module, Package
|
6
6
|
from sera.models._multi_lingual_string import MultiLingualString
|
7
7
|
from sera.models._parse import parse_schema
|
8
|
-
from sera.models._property import
|
8
|
+
from sera.models._property import (
|
9
|
+
Cardinality,
|
10
|
+
DataProperty,
|
11
|
+
IndexType,
|
12
|
+
ObjectProperty,
|
13
|
+
Property,
|
14
|
+
)
|
9
15
|
from sera.models._schema import Schema
|
10
16
|
|
11
17
|
__all__ = [
|
@@ -14,6 +20,7 @@ __all__ = [
|
|
14
20
|
"Property",
|
15
21
|
"DataProperty",
|
16
22
|
"ObjectProperty",
|
23
|
+
"IndexType",
|
17
24
|
"Class",
|
18
25
|
"Cardinality",
|
19
26
|
"DataType",
|
sera/models/_class.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
|
|
5
5
|
|
6
6
|
from sera.misc import to_kebab_case, to_snake_case
|
7
7
|
from sera.models._multi_lingual_string import MultiLingualString
|
8
|
-
from sera.models._property import DataProperty, ObjectProperty
|
8
|
+
from sera.models._property import DataProperty, IndexType, ObjectProperty
|
9
9
|
|
10
10
|
|
11
11
|
@dataclass(kw_only=True)
|
@@ -13,6 +13,7 @@ class Index:
|
|
13
13
|
name: str
|
14
14
|
columns: list[str]
|
15
15
|
unique: bool = False
|
16
|
+
index_type: IndexType = IndexType.DEFAULT
|
16
17
|
|
17
18
|
|
18
19
|
@dataclass(kw_only=True)
|
sera/models/_enum.py
CHANGED
@@ -11,6 +11,7 @@ from sera.models._multi_lingual_string import MultiLingualString
|
|
11
11
|
class EnumValue:
|
12
12
|
name: str
|
13
13
|
value: str | int
|
14
|
+
label: MultiLingualString
|
14
15
|
description: MultiLingualString
|
15
16
|
|
16
17
|
|
@@ -44,7 +45,7 @@ class Enum:
|
|
44
45
|
|
45
46
|
def get_tsmodule_name(self) -> str:
|
46
47
|
"""Get the typescript module name of this enum as if there is a typescript module created to store this enum only."""
|
47
|
-
return
|
48
|
+
return self.name
|
48
49
|
|
49
50
|
def is_str_enum(self) -> bool:
|
50
51
|
"""Check if this enum is a string enum."""
|
sera/models/_parse.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import re
|
4
4
|
from copy import deepcopy
|
5
|
+
from operator import index
|
5
6
|
from pathlib import Path
|
6
7
|
from typing import Sequence
|
7
8
|
|
@@ -30,6 +31,7 @@ from sera.models._property import (
|
|
30
31
|
ForeignKeyOnDelete,
|
31
32
|
ForeignKeyOnUpdate,
|
32
33
|
GetSCPropValueFunc,
|
34
|
+
IndexType,
|
33
35
|
ObjectPropDBInfo,
|
34
36
|
ObjectProperty,
|
35
37
|
PropDataAttrs,
|
@@ -91,13 +93,17 @@ def _parse_enum(schema: Schema, enum_name: str, enum: dict) -> Enum:
|
|
91
93
|
for k, v in enum.items():
|
92
94
|
if isinstance(v, (str, int)):
|
93
95
|
values[k] = EnumValue(
|
94
|
-
name=k,
|
96
|
+
name=k,
|
97
|
+
value=v,
|
98
|
+
label=MultiLingualString.en(""),
|
99
|
+
description=MultiLingualString.en(""),
|
95
100
|
)
|
96
101
|
else:
|
97
102
|
try:
|
98
103
|
values[k] = EnumValue(
|
99
104
|
name=k,
|
100
105
|
value=v["value"],
|
106
|
+
label=_parse_multi_lingual_string(v.get("label", "")),
|
101
107
|
description=_parse_multi_lingual_string(v.get("desc", "")),
|
102
108
|
)
|
103
109
|
except KeyError as e:
|
@@ -146,7 +152,7 @@ def _parse_property(
|
|
146
152
|
|
147
153
|
assert isinstance(prop, dict), prop
|
148
154
|
if "datatype" in prop:
|
149
|
-
|
155
|
+
return_prop = DataProperty(
|
150
156
|
name=prop_name,
|
151
157
|
label=_parse_multi_lingual_string(prop.get("label", prop_name)),
|
152
158
|
description=_parse_multi_lingual_string(prop.get("desc", "")),
|
@@ -160,6 +166,9 @@ def _parse_property(
|
|
160
166
|
is_indexed=db.get("is_indexed", False)
|
161
167
|
or db.get("is_unique", False)
|
162
168
|
or db.get("is_primary_key", False),
|
169
|
+
index_type=(
|
170
|
+
IndexType(db["index_type"]) if "index_type" in db else None
|
171
|
+
),
|
163
172
|
foreign_key=schema.classes.get(db.get("foreign_key")),
|
164
173
|
)
|
165
174
|
if "db" in prop
|
@@ -169,6 +178,10 @@ def _parse_property(
|
|
169
178
|
default_value=_parse_default_value(prop.get("default_value", None)),
|
170
179
|
default_factory=_parse_default_factory(prop.get("default_factory", None)),
|
171
180
|
)
|
181
|
+
if return_prop.db is not None and return_prop.db.is_indexed:
|
182
|
+
if return_prop.db.index_type is None:
|
183
|
+
return_prop.db.index_type = IndexType.DEFAULT
|
184
|
+
return return_prop
|
172
185
|
|
173
186
|
assert "target" in prop, prop
|
174
187
|
return ObjectProperty(
|
sera/models/_property.py
CHANGED
@@ -16,6 +16,12 @@ if TYPE_CHECKING:
|
|
16
16
|
from sera.models._class import Class
|
17
17
|
|
18
18
|
|
19
|
+
class IndexType(str, Enum):
|
20
|
+
DEFAULT = "default"
|
21
|
+
POSTGRES_FTS_SEVI = "postgres_fts_sevi"
|
22
|
+
POSTGRES_TRIGRAM = "postgres_trigram"
|
23
|
+
|
24
|
+
|
19
25
|
class ForeignKeyOnDelete(str, Enum):
|
20
26
|
CASCADE = "cascade"
|
21
27
|
SET_NULL = "set null"
|
@@ -150,6 +156,8 @@ class DataPropDBInfo:
|
|
150
156
|
is_unique: bool = False
|
151
157
|
# whether this property is indexed or not
|
152
158
|
is_indexed: bool = False
|
159
|
+
# type of the index if it is indexed --- if is_indexed is True, this must be not None
|
160
|
+
index_type: Optional[IndexType] = None
|
153
161
|
# this is used in conjunction with is_primary_key = True for the case of
|
154
162
|
# extending a table with frequently updated properties. The value for the `foreign_key`
|
155
163
|
# will be a target class. The cardinality is one-to-one, on target class deletion,
|
sera/typing.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: sera-2
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.25.0
|
4
4
|
Summary:
|
5
5
|
Author: Binh Vu
|
6
6
|
Author-email: bvu687@gmail.com
|
@@ -8,8 +8,9 @@ Requires-Python: >=3.12,<4.0
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
9
|
Classifier: Programming Language :: Python :: 3.12
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
11
12
|
Requires-Dist: black (==25.1.0)
|
12
|
-
Requires-Dist: codegen-2 (>=2.
|
13
|
+
Requires-Dist: codegen-2 (>=2.15.1,<3.0.0)
|
13
14
|
Requires-Dist: graph-wrapper (>=1.7.3,<2.0.0)
|
14
15
|
Requires-Dist: isort (==6.0.1)
|
15
16
|
Requires-Dist: litestar (>=2.15.1,<3.0.0)
|