sera-2 1.12.3__py3-none-any.whl → 1.13.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/exports/__init__.py +0 -0
- sera/exports/schema.py +157 -0
- sera/exports/test.py +70 -0
- sera/libs/base_orm.py +21 -3
- sera/libs/base_service.py +29 -20
- sera/libs/middlewares/auth.py +3 -6
- sera/make/__main__.py +2 -1
- sera/make/make_python_api.py +100 -79
- sera/make/make_python_model.py +185 -51
- sera/make/make_python_services.py +3 -2
- sera/models/__init__.py +8 -1
- sera/models/_constraints.py +15 -1
- sera/models/_datatype.py +10 -0
- sera/models/_parse.py +9 -4
- sera/models/_property.py +14 -1
- sera/models/_schema.py +11 -0
- {sera_2-1.12.3.dist-info → sera_2-1.13.0.dist-info}/METADATA +3 -3
- {sera_2-1.12.3.dist-info → sera_2-1.13.0.dist-info}/RECORD +19 -16
- {sera_2-1.12.3.dist-info → sera_2-1.13.0.dist-info}/WHEEL +0 -0
sera/make/make_python_model.py
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from operator import is_
|
4
3
|
from typing import Callable, Optional, Sequence
|
5
4
|
|
6
|
-
from codegen.models import
|
5
|
+
from codegen.models import (
|
6
|
+
AST,
|
7
|
+
DeferredVar,
|
8
|
+
ImportHelper,
|
9
|
+
PredefinedFn,
|
10
|
+
Program,
|
11
|
+
expr,
|
12
|
+
stmt,
|
13
|
+
)
|
7
14
|
|
8
15
|
from sera.misc import (
|
9
16
|
assert_isinstance,
|
@@ -20,6 +27,7 @@ from sera.models import (
|
|
20
27
|
PyTypeWithDep,
|
21
28
|
Schema,
|
22
29
|
)
|
30
|
+
from sera.models._property import SystemControlledMode
|
23
31
|
from sera.typing import ObjectPath
|
24
32
|
|
25
33
|
|
@@ -211,10 +219,19 @@ def make_python_data_model(
|
|
211
219
|
alias=f"{cls.name}DB",
|
212
220
|
)
|
213
221
|
|
214
|
-
|
215
|
-
|
222
|
+
ident_manager = ImportHelper(
|
223
|
+
program,
|
224
|
+
{
|
225
|
+
"UNSET": "sera.typing.UNSET",
|
226
|
+
},
|
227
|
+
)
|
228
|
+
|
229
|
+
# property that normal users cannot set, but super users can
|
230
|
+
has_restricted_system_controlled = any(
|
231
|
+
prop.data.is_system_controlled == SystemControlledMode.RESTRICTED
|
232
|
+
for prop in cls.properties.values()
|
216
233
|
)
|
217
|
-
if
|
234
|
+
if has_restricted_system_controlled:
|
218
235
|
program.import_("typing.TypedDict", True)
|
219
236
|
program.root(
|
220
237
|
stmt.LineBreak(),
|
@@ -236,6 +253,7 @@ def make_python_data_model(
|
|
236
253
|
)
|
237
254
|
for prop in cls.properties.values()
|
238
255
|
if prop.data.is_system_controlled
|
256
|
+
== SystemControlledMode.RESTRICTED
|
239
257
|
],
|
240
258
|
),
|
241
259
|
)
|
@@ -246,11 +264,10 @@ def make_python_data_model(
|
|
246
264
|
[expr.ExprIdent("msgspec.Struct"), expr.ExprIdent("kw_only=True")],
|
247
265
|
)
|
248
266
|
for prop in cls.properties.values():
|
249
|
-
#
|
250
|
-
#
|
251
|
-
|
252
|
-
|
253
|
-
# continue
|
267
|
+
# a field that is fully controlled by the system (e.g., cached or derived fields)
|
268
|
+
# aren't allowed to be set by the users, so we skip them
|
269
|
+
if prop.data.is_system_controlled == SystemControlledMode.AUTO:
|
270
|
+
continue
|
254
271
|
|
255
272
|
if isinstance(prop, DataProperty):
|
256
273
|
pytype = prop.get_data_model_datatype().get_python_type()
|
@@ -272,14 +289,20 @@ def make_python_data_model(
|
|
272
289
|
else:
|
273
290
|
raise NotImplementedError(prop.data.constraints)
|
274
291
|
|
275
|
-
if
|
292
|
+
if (
|
293
|
+
prop.data.is_private
|
294
|
+
or prop.data.is_system_controlled == SystemControlledMode.RESTRICTED
|
295
|
+
):
|
276
296
|
program.import_("typing.Union", True)
|
277
297
|
program.import_("sera.typing.UnsetType", True)
|
278
298
|
program.import_("sera.typing.UNSET", True)
|
279
299
|
pytype_type = f"Union[{pytype_type}, UnsetType]"
|
280
300
|
|
281
301
|
prop_default_value = None
|
282
|
-
if
|
302
|
+
if (
|
303
|
+
prop.data.is_private
|
304
|
+
or prop.data.is_system_controlled == SystemControlledMode.RESTRICTED
|
305
|
+
):
|
283
306
|
prop_default_value = expr.ExprIdent("UNSET")
|
284
307
|
elif prop.default_value is not None:
|
285
308
|
prop_default_value = expr.ExprConstant(prop.default_value)
|
@@ -321,25 +344,28 @@ def make_python_data_model(
|
|
321
344
|
elif prop.is_optional:
|
322
345
|
pytype = pytype.as_optional_type()
|
323
346
|
|
347
|
+
pytype_type = pytype.type
|
348
|
+
prop_default_value = None
|
349
|
+
if prop.data.is_system_controlled == SystemControlledMode.RESTRICTED:
|
350
|
+
program.import_("typing.Union", True)
|
351
|
+
program.import_("sera.typing.UnsetType", True)
|
352
|
+
program.import_("sera.typing.UNSET", True)
|
353
|
+
pytype_type = f"Union[{pytype_type}, UnsetType]"
|
354
|
+
prop_default_value = expr.ExprIdent("UNSET")
|
355
|
+
|
324
356
|
for dep in pytype.deps:
|
325
357
|
program.import_(dep, True)
|
326
358
|
|
327
|
-
cls_ast(
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
359
|
+
cls_ast(
|
360
|
+
stmt.DefClassVarStatement(
|
361
|
+
prop.name, pytype_type, prop_default_value
|
362
|
+
)
|
363
|
+
)
|
332
364
|
|
333
|
-
if
|
365
|
+
if has_restricted_system_controlled:
|
334
366
|
program.import_("typing.Optional", True)
|
367
|
+
program.import_("sera.typing.is_set", True)
|
335
368
|
cls_ast(
|
336
|
-
stmt.LineBreak(),
|
337
|
-
stmt.Comment(
|
338
|
-
"_verified is a special marker to indicate whether the data is updated by the system."
|
339
|
-
),
|
340
|
-
stmt.DefClassVarStatement(
|
341
|
-
"_verified", "bool", expr.ExprConstant(False)
|
342
|
-
),
|
343
369
|
stmt.LineBreak(),
|
344
370
|
lambda ast: ast.func(
|
345
371
|
"__post_init__",
|
@@ -347,12 +373,17 @@ def make_python_data_model(
|
|
347
373
|
DeferredVar.simple("self"),
|
348
374
|
],
|
349
375
|
)(
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
376
|
+
*[
|
377
|
+
stmt.AssignStatement(
|
378
|
+
PredefinedFn.attr_getter(
|
379
|
+
expr.ExprIdent("self"), expr.ExprIdent(prop.name)
|
380
|
+
),
|
381
|
+
expr.ExprIdent("UNSET"),
|
382
|
+
)
|
383
|
+
for prop in cls.properties.values()
|
384
|
+
if prop.data.is_system_controlled
|
385
|
+
== SystemControlledMode.RESTRICTED
|
386
|
+
]
|
356
387
|
),
|
357
388
|
stmt.LineBreak(),
|
358
389
|
lambda ast: ast.func(
|
@@ -381,14 +412,9 @@ def make_python_data_model(
|
|
381
412
|
)
|
382
413
|
for prop in cls.properties.values()
|
383
414
|
if prop.data.is_system_controlled
|
415
|
+
== SystemControlledMode.RESTRICTED
|
384
416
|
]
|
385
417
|
),
|
386
|
-
stmt.AssignStatement(
|
387
|
-
PredefinedFn.attr_getter(
|
388
|
-
expr.ExprIdent("self"), expr.ExprIdent("_verified")
|
389
|
-
),
|
390
|
-
expr.ExprConstant(True),
|
391
|
-
),
|
392
418
|
),
|
393
419
|
)
|
394
420
|
|
@@ -405,14 +431,27 @@ def make_python_data_model(
|
|
405
431
|
)(
|
406
432
|
(
|
407
433
|
stmt.AssertionStatement(
|
408
|
-
|
409
|
-
|
434
|
+
expr.ExprLogicalAnd(
|
435
|
+
[
|
436
|
+
expr.ExprFuncCall(
|
437
|
+
expr.ExprIdent("is_set"),
|
438
|
+
[
|
439
|
+
PredefinedFn.attr_getter(
|
440
|
+
expr.ExprIdent("self"),
|
441
|
+
expr.ExprIdent(prop.name),
|
442
|
+
)
|
443
|
+
],
|
444
|
+
)
|
445
|
+
for prop in cls.properties.values()
|
446
|
+
if prop.data.is_system_controlled
|
447
|
+
== SystemControlledMode.RESTRICTED
|
448
|
+
]
|
410
449
|
),
|
411
450
|
expr.ExprConstant(
|
412
451
|
"The model data must be verified before converting to db model"
|
413
452
|
),
|
414
453
|
)
|
415
|
-
if
|
454
|
+
if has_restricted_system_controlled
|
416
455
|
else None
|
417
456
|
),
|
418
457
|
lambda ast10: ast10.return_(
|
@@ -421,8 +460,13 @@ def make_python_data_model(
|
|
421
460
|
f"{cls.name}DB" if cls.db is not None else cls.name
|
422
461
|
),
|
423
462
|
[
|
424
|
-
|
425
|
-
|
463
|
+
(
|
464
|
+
to_db_type_conversion(
|
465
|
+
program, expr.ExprIdent("self"), cls, prop
|
466
|
+
)
|
467
|
+
if prop.data.is_system_controlled
|
468
|
+
!= SystemControlledMode.AUTO
|
469
|
+
else ident_manager.use("UNSET")
|
426
470
|
)
|
427
471
|
for prop in cls.properties.values()
|
428
472
|
],
|
@@ -623,8 +667,11 @@ def make_python_relational_model(
|
|
623
667
|
program.import_("__future__.annotations", True)
|
624
668
|
program.import_("sera.libs.base_orm.BaseORM", True)
|
625
669
|
program.import_("sera.libs.base_orm.create_engine", True)
|
670
|
+
program.import_("sera.libs.base_orm.create_async_engine", True)
|
626
671
|
program.import_("sqlalchemy.orm.DeclarativeBase", True)
|
627
672
|
program.import_("sqlalchemy.orm.Session", True)
|
673
|
+
program.import_("sqlalchemy.ext.asyncio.AsyncSession", True)
|
674
|
+
program.import_("sqlalchemy.text", True)
|
628
675
|
|
629
676
|
# assume configuration for the app at the top level
|
630
677
|
program.import_(f"{app.config.path}.DB_CONNECTION", True)
|
@@ -673,14 +720,22 @@ def make_python_relational_model(
|
|
673
720
|
|
674
721
|
program.root.linebreak()
|
675
722
|
program.root.assign(
|
676
|
-
DeferredVar("engine"
|
723
|
+
DeferredVar.simple("engine"),
|
677
724
|
expr.ExprFuncCall(
|
678
725
|
expr.ExprIdent("create_engine"),
|
679
726
|
[
|
680
727
|
expr.ExprIdent("DB_CONNECTION"),
|
681
|
-
PredefinedFn.keyword_assignment(
|
682
|
-
|
683
|
-
|
728
|
+
PredefinedFn.keyword_assignment("echo", expr.ExprIdent("DB_DEBUG")),
|
729
|
+
],
|
730
|
+
),
|
731
|
+
)
|
732
|
+
program.root.assign(
|
733
|
+
DeferredVar.simple("async_engine"),
|
734
|
+
expr.ExprFuncCall(
|
735
|
+
expr.ExprIdent("create_async_engine"),
|
736
|
+
[
|
737
|
+
expr.ExprIdent("DB_CONNECTION"),
|
738
|
+
PredefinedFn.keyword_assignment("echo", expr.ExprIdent("DB_DEBUG")),
|
684
739
|
],
|
685
740
|
),
|
686
741
|
)
|
@@ -691,17 +746,96 @@ def make_python_relational_model(
|
|
691
746
|
)
|
692
747
|
|
693
748
|
program.root.linebreak()
|
694
|
-
program.root.func("
|
695
|
-
lambda
|
696
|
-
|
749
|
+
program.root.func("get_async_session", [], is_async=True)(
|
750
|
+
lambda ast: ast.python_stmt(
|
751
|
+
"async with AsyncSession(async_engine, expire_on_commit=False) as session:"
|
752
|
+
)(
|
753
|
+
lambda ast_l1: ast_l1.try_()(stmt.PythonStatement("yield session")),
|
754
|
+
lambda ast_l1: ast_l1.catch()(
|
755
|
+
stmt.SingleExprStatement(
|
756
|
+
expr.ExprAwait(
|
757
|
+
expr.ExprFuncCall(
|
758
|
+
PredefinedFn.attr_getter(
|
759
|
+
expr.ExprIdent("session"),
|
760
|
+
expr.ExprIdent("rollback"),
|
761
|
+
),
|
762
|
+
[],
|
763
|
+
)
|
764
|
+
)
|
765
|
+
),
|
766
|
+
stmt.PythonStatement("raise"),
|
767
|
+
),
|
768
|
+
lambda ast_l1: ast_l1.else_()(
|
769
|
+
stmt.SingleExprStatement(
|
770
|
+
expr.ExprAwait(
|
771
|
+
expr.ExprFuncCall(
|
772
|
+
PredefinedFn.attr_getter(
|
773
|
+
expr.ExprIdent("session"), expr.ExprIdent("execute")
|
774
|
+
),
|
775
|
+
[
|
776
|
+
expr.ExprFuncCall(
|
777
|
+
expr.ExprIdent("text"),
|
778
|
+
[expr.ExprConstant("RESET ROLE;")],
|
779
|
+
)
|
780
|
+
],
|
781
|
+
)
|
782
|
+
)
|
783
|
+
),
|
784
|
+
stmt.SingleExprStatement(
|
785
|
+
expr.ExprAwait(
|
786
|
+
expr.ExprFuncCall(
|
787
|
+
PredefinedFn.attr_getter(
|
788
|
+
expr.ExprIdent("session"), expr.ExprIdent("commit")
|
789
|
+
),
|
790
|
+
[],
|
791
|
+
)
|
792
|
+
)
|
793
|
+
),
|
794
|
+
),
|
697
795
|
)
|
698
796
|
)
|
699
797
|
|
700
798
|
program.root.linebreak()
|
701
799
|
program.root.python_stmt("@contextmanager")
|
702
800
|
program.root.func("get_session", [])(
|
703
|
-
lambda
|
704
|
-
|
801
|
+
lambda ast: ast.python_stmt(
|
802
|
+
"with Session(engine, expire_on_commit=False) as session:"
|
803
|
+
)(
|
804
|
+
lambda ast_l1: ast_l1.try_()(stmt.PythonStatement("yield session")),
|
805
|
+
lambda ast_l1: ast_l1.catch()(
|
806
|
+
stmt.SingleExprStatement(
|
807
|
+
expr.ExprFuncCall(
|
808
|
+
PredefinedFn.attr_getter(
|
809
|
+
expr.ExprIdent("session"), expr.ExprIdent("rollback")
|
810
|
+
),
|
811
|
+
[],
|
812
|
+
)
|
813
|
+
),
|
814
|
+
stmt.PythonStatement("raise"),
|
815
|
+
),
|
816
|
+
lambda ast_l1: ast_l1.else_()(
|
817
|
+
stmt.SingleExprStatement(
|
818
|
+
expr.ExprFuncCall(
|
819
|
+
PredefinedFn.attr_getter(
|
820
|
+
expr.ExprIdent("session"), expr.ExprIdent("execute")
|
821
|
+
),
|
822
|
+
[
|
823
|
+
expr.ExprFuncCall(
|
824
|
+
expr.ExprIdent("text"),
|
825
|
+
[expr.ExprConstant("RESET ROLE;")],
|
826
|
+
)
|
827
|
+
],
|
828
|
+
)
|
829
|
+
),
|
830
|
+
stmt.SingleExprStatement(
|
831
|
+
expr.ExprFuncCall(
|
832
|
+
PredefinedFn.attr_getter(
|
833
|
+
expr.ExprIdent("session"), expr.ExprIdent("commit")
|
834
|
+
),
|
835
|
+
[],
|
836
|
+
)
|
837
|
+
),
|
838
|
+
),
|
705
839
|
)
|
706
840
|
)
|
707
841
|
|
@@ -4,6 +4,7 @@ from typing import Sequence
|
|
4
4
|
|
5
5
|
from codegen.models import DeferredVar, Program, expr, stmt
|
6
6
|
from loguru import logger
|
7
|
+
|
7
8
|
from sera.misc import assert_not_null
|
8
9
|
from sera.models import App, DataCollection, Package
|
9
10
|
|
@@ -35,13 +36,13 @@ def make_python_service(collection: DataCollection, target_pkg: Package):
|
|
35
36
|
True,
|
36
37
|
)
|
37
38
|
program.import_(app.config.path + f".schema", True)
|
38
|
-
program.import_("sera.libs.base_service.
|
39
|
+
program.import_("sera.libs.base_service.BaseAsyncService", True)
|
39
40
|
|
40
41
|
program.root(
|
41
42
|
stmt.LineBreak(),
|
42
43
|
lambda ast00: ast00.class_(
|
43
44
|
collection.get_service_name(),
|
44
|
-
[expr.ExprIdent(f"
|
45
|
+
[expr.ExprIdent(f"BaseAsyncService[{id_type}, {cls.name}]")],
|
45
46
|
)(
|
46
47
|
lambda ast01: ast01.func(
|
47
48
|
"__init__",
|
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
|
+
ObjectProperty,
|
12
|
+
Property,
|
13
|
+
SystemControlledMode,
|
14
|
+
)
|
9
15
|
from sera.models._schema import Schema
|
10
16
|
|
11
17
|
__all__ = [
|
@@ -25,4 +31,5 @@ __all__ = [
|
|
25
31
|
"PyTypeWithDep",
|
26
32
|
"TsTypeWithDep",
|
27
33
|
"Enum",
|
34
|
+
"SystemControlledMode",
|
28
35
|
]
|
sera/models/_constraints.py
CHANGED
@@ -3,7 +3,15 @@ from __future__ import annotations
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from typing import Literal
|
5
5
|
|
6
|
-
ConstraintName = Literal[
|
6
|
+
ConstraintName = Literal[
|
7
|
+
"phone_number",
|
8
|
+
"email",
|
9
|
+
"not_empty",
|
10
|
+
"username",
|
11
|
+
"password",
|
12
|
+
"whole_number",
|
13
|
+
"positive_number",
|
14
|
+
]
|
7
15
|
|
8
16
|
|
9
17
|
@dataclass
|
@@ -25,6 +33,10 @@ class Constraint:
|
|
25
33
|
)
|
26
34
|
elif self.name == "password":
|
27
35
|
return "msgspec.Meta(min_length=8, max_length=40)"
|
36
|
+
elif self.name == "whole_number":
|
37
|
+
return "msgspec.Meta(ge=0)"
|
38
|
+
elif self.name == "positive_number":
|
39
|
+
return "msgspec.Meta(gt=0)"
|
28
40
|
|
29
41
|
raise NotImplementedError()
|
30
42
|
|
@@ -43,4 +55,6 @@ predefined_constraints: dict[ConstraintName, Constraint] = {
|
|
43
55
|
"not_empty": Constraint("not_empty", ()),
|
44
56
|
"username": Constraint("username", ()),
|
45
57
|
"password": Constraint("password", ()),
|
58
|
+
"whole_number": Constraint("whole_number", ()),
|
59
|
+
"positive_number": Constraint("positive_number", ()),
|
46
60
|
}
|
sera/models/_datatype.py
CHANGED
@@ -164,6 +164,16 @@ predefined_datatypes = {
|
|
164
164
|
tstype=TsTypeWithDep(type="number"),
|
165
165
|
is_list=False,
|
166
166
|
),
|
167
|
+
"date": DataType(
|
168
|
+
pytype=PyTypeWithDep(type="date", deps=["datetime.date"]),
|
169
|
+
sqltype=SQLTypeWithDep(
|
170
|
+
type="Date",
|
171
|
+
mapped_pytype="date",
|
172
|
+
deps=["sqlalchemy.Date", "datetime.date"],
|
173
|
+
),
|
174
|
+
tstype=TsTypeWithDep(type="string"),
|
175
|
+
is_list=False,
|
176
|
+
),
|
167
177
|
"datetime": DataType(
|
168
178
|
pytype=PyTypeWithDep(type="datetime", deps=["datetime.datetime"]),
|
169
179
|
sqltype=SQLTypeWithDep(
|
sera/models/_parse.py
CHANGED
@@ -31,6 +31,7 @@ from sera.models._property import (
|
|
31
31
|
ObjectPropDBInfo,
|
32
32
|
ObjectProperty,
|
33
33
|
PropDataAttrs,
|
34
|
+
SystemControlledMode,
|
34
35
|
)
|
35
36
|
from sera.models._schema import Schema
|
36
37
|
|
@@ -76,7 +77,7 @@ def _parse_class_without_prop(schema: Schema, clsname: str, cls: dict) -> Class:
|
|
76
77
|
return Class(
|
77
78
|
name=clsname,
|
78
79
|
label=_parse_multi_lingual_string(cls["label"]),
|
79
|
-
description=_parse_multi_lingual_string(cls
|
80
|
+
description=_parse_multi_lingual_string(cls.get("desc", "")),
|
80
81
|
properties={},
|
81
82
|
db=db,
|
82
83
|
)
|
@@ -131,7 +132,9 @@ def _parse_property(
|
|
131
132
|
constraints=[
|
132
133
|
_parse_constraint(constraint) for constraint in _data.get("constraints", [])
|
133
134
|
],
|
134
|
-
is_system_controlled=
|
135
|
+
is_system_controlled=SystemControlledMode(
|
136
|
+
_data.get("is_system_controlled", SystemControlledMode.NO.value)
|
137
|
+
),
|
135
138
|
)
|
136
139
|
|
137
140
|
assert isinstance(prop, dict), prop
|
@@ -222,12 +225,14 @@ def _parse_datatype(schema: Schema, datatype: dict | str) -> DataType:
|
|
222
225
|
# the correct package yet.
|
223
226
|
pytype=PyTypeWithDep(
|
224
227
|
type=enum.name,
|
225
|
-
|
228
|
+
deps=[
|
229
|
+
f"{schema.name}.models.enums.{enum.get_pymodule_name()}.{enum.name}"
|
230
|
+
],
|
226
231
|
),
|
227
232
|
sqltype=SQLTypeWithDep(
|
228
233
|
type="String", mapped_pytype="str", deps=["sqlalchemy.String"]
|
229
234
|
),
|
230
|
-
tstype=TsTypeWithDep(type=enum.name,
|
235
|
+
tstype=TsTypeWithDep(type=enum.name, deps=["@/models/enums"]),
|
231
236
|
is_list=is_list,
|
232
237
|
)
|
233
238
|
|
sera/models/_property.py
CHANGED
@@ -56,6 +56,19 @@ class Cardinality(str, Enum):
|
|
56
56
|
]
|
57
57
|
|
58
58
|
|
59
|
+
class SystemControlledMode(str, Enum):
|
60
|
+
"""Indicates if this property is controlled by the system.
|
61
|
+
|
62
|
+
There are two modes:
|
63
|
+
1. The system automatically sets the value, and users cannot modify it.
|
64
|
+
2. Users with special roles are allowed to set the value and other users cannot modify it
|
65
|
+
"""
|
66
|
+
|
67
|
+
AUTO = "auto"
|
68
|
+
RESTRICTED = "restricted"
|
69
|
+
NO = "no"
|
70
|
+
|
71
|
+
|
59
72
|
@dataclass(kw_only=True)
|
60
73
|
class PropDataAttrs:
|
61
74
|
"""Storing other attributes for generating data model (upsert & public) -- this is different from a db model"""
|
@@ -72,7 +85,7 @@ class PropDataAttrs:
|
|
72
85
|
constraints: list[Constraint] = field(default_factory=list)
|
73
86
|
|
74
87
|
# whether this property is controlled by the system or not
|
75
|
-
is_system_controlled:
|
88
|
+
is_system_controlled: SystemControlledMode = SystemControlledMode.NO
|
76
89
|
|
77
90
|
|
78
91
|
@dataclass(kw_only=True)
|
sera/models/_schema.py
CHANGED
@@ -34,3 +34,14 @@ class Schema:
|
|
34
34
|
|
35
35
|
# Convert sorted names back to Class objects
|
36
36
|
return [self.classes[name] for name in sorted_names]
|
37
|
+
|
38
|
+
def get_upstream_classes(self, cls: Class) -> list[tuple[Class, ObjectProperty]]:
|
39
|
+
"""
|
40
|
+
Get all classes that depend on the given class.
|
41
|
+
"""
|
42
|
+
upstream_classes = []
|
43
|
+
for other_cls in self.classes.values():
|
44
|
+
for prop in other_cls.properties.values():
|
45
|
+
if isinstance(prop, ObjectProperty) and prop.target.name == cls.name:
|
46
|
+
upstream_classes.append((other_cls, prop))
|
47
|
+
return upstream_classes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: sera-2
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.13.0
|
4
4
|
Summary:
|
5
5
|
Author: Binh Vu
|
6
6
|
Author-email: bvu687@gmail.com
|
@@ -9,13 +9,13 @@ Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.12
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
11
11
|
Requires-Dist: black (>=25.1.0,<26.0.0)
|
12
|
-
Requires-Dist: codegen-2 (>=2.
|
12
|
+
Requires-Dist: codegen-2 (>=2.11.0,<3.0.0)
|
13
13
|
Requires-Dist: isort (>=6.0.1,<7.0.0)
|
14
14
|
Requires-Dist: litestar (>=2.15.1,<3.0.0)
|
15
15
|
Requires-Dist: loguru (>=0.7.0,<0.8.0)
|
16
16
|
Requires-Dist: msgspec (>=0.19.0,<0.20.0)
|
17
17
|
Requires-Dist: serde2 (>=1.9.0,<2.0.0)
|
18
|
-
Requires-Dist: sqlalchemy (>=2.0.
|
18
|
+
Requires-Dist: sqlalchemy[asyncio] (>=2.0.41,<3.0.0)
|
19
19
|
Requires-Dist: typer (>=0.12.3,<0.13.0)
|
20
20
|
Project-URL: Repository, https://github.com/binh-vu/sera
|
21
21
|
Description-Content-Type: text/markdown
|
@@ -1,37 +1,40 @@
|
|
1
1
|
sera/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
sera/constants.py,sha256=mzAaMyIx8TJK0-RYYJ5I24C4s0Uvj26OLMJmBo0pxHI,123
|
3
|
+
sera/exports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
sera/exports/schema.py,sha256=3F5Kx3r0zOmiVpDf9vnzGhtdcuF-vfgMhBbp4Ko4fgw,4740
|
5
|
+
sera/exports/test.py,sha256=jK1EJmLGiy7eREpnY_68IIVRH43uH8S_u5Z7STPbXOM,2002
|
3
6
|
sera/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
7
|
sera/libs/api_helper.py,sha256=47y1kcwk3Xd2ZEMnUj_0OwCuUmgwOs5kYrE95BDVUn4,5411
|
5
|
-
sera/libs/base_orm.py,sha256=
|
6
|
-
sera/libs/base_service.py,sha256=
|
8
|
+
sera/libs/base_orm.py,sha256=5hOH_diUeaABm3cpE2-9u50VRqG1QW2osPQnvVHIhIA,3365
|
9
|
+
sera/libs/base_service.py,sha256=AX1WoTHte6Z_birkkfagkNE6BrCLTlTjQE4jEsKEaAY,5152
|
7
10
|
sera/libs/dag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
11
|
sera/libs/dag/_dag.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
12
|
sera/libs/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
sera/libs/middlewares/auth.py,sha256=
|
13
|
+
sera/libs/middlewares/auth.py,sha256=r6aix1ZBwxMd1Jv5hMCTB8a_gFOJQ6egvxIrf3DWEOs,2323
|
11
14
|
sera/libs/middlewares/uscp.py,sha256=H5umW8iEQSCdb_MJ5Im49kxg1E7TpxSg1p2_2A5zI1U,2600
|
12
15
|
sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
sera/make/__main__.py,sha256=
|
16
|
+
sera/make/__main__.py,sha256=bt-gDF8E026OWc2zqr9_a3paMOiDkFd3ybWn8ltL2g0,1448
|
14
17
|
sera/make/make_app.py,sha256=n9NtW73O3s_5Q31VHIRmnd-jEIcpDO7ksAsOdovde2s,5999
|
15
|
-
sera/make/make_python_api.py,sha256=
|
16
|
-
sera/make/make_python_model.py,sha256=
|
17
|
-
sera/make/make_python_services.py,sha256=
|
18
|
+
sera/make/make_python_api.py,sha256=sf-J5Pt1LTyM_H-SgXSAvKjEMDrRA6WnDpgDPbJG360,26896
|
19
|
+
sera/make/make_python_model.py,sha256=XWoNmfVNfdzF3lwEaItPZjo27ibqL9yjHA3a0vrHsnA,51708
|
20
|
+
sera/make/make_python_services.py,sha256=0ZpWLwQ7Nwfn8BXAikAB4JRpNknpSJyJgY5b1cjtxV4,2073
|
18
21
|
sera/make/make_typescript_model.py,sha256=ugDdSTw_1ayHLuL--92RQ8hf_D-dpJtnvmUZNxcwcDs,63687
|
19
22
|
sera/misc/__init__.py,sha256=Dh4uDq0D4N53h3zhvmwfa5a0TPVRSUvLzb0hkFuPirk,411
|
20
23
|
sera/misc/_formatter.py,sha256=aCGYL08l8f3aLODHxSocxBBwkRYEo3K1QzCDEn3suj0,1685
|
21
24
|
sera/misc/_utils.py,sha256=V5g4oLGHOhUCR75Kkcn1w01pAvGvaepK-T8Z3pIgHjI,1450
|
22
|
-
sera/models/__init__.py,sha256=
|
25
|
+
sera/models/__init__.py,sha256=VcC7HvqXuYrkgXwzs2vOH6LJPpzFBkeDvYVNrd3P-6E,855
|
23
26
|
sera/models/_class.py,sha256=Wf0e8x6-szG9TzoFkAlqj7_dG0SCICMBw_333n3paxk,2514
|
24
27
|
sera/models/_collection.py,sha256=ZnQEriKC4X88Zz48Kn1AVZKH-1_l8OgWa-zf2kcQOOE,1414
|
25
|
-
sera/models/_constraints.py,sha256=
|
26
|
-
sera/models/_datatype.py,sha256=
|
28
|
+
sera/models/_constraints.py,sha256=PsSOX94b9-73wZTcUif9KTzV9uTfoF0WN87g4GZXQmU,1827
|
29
|
+
sera/models/_datatype.py,sha256=y5kfim0G3gLhnGjiokFBr8leU1Y6as_Vw7oK-caOo68,7140
|
27
30
|
sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
|
28
31
|
sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
|
29
32
|
sera/models/_module.py,sha256=8QRSCubZmdDP9rL58rGAS6X5VCrkc1ZHvuMu1I1KrWk,5043
|
30
33
|
sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
|
31
|
-
sera/models/_parse.py,sha256=
|
32
|
-
sera/models/_property.py,sha256=
|
33
|
-
sera/models/_schema.py,sha256=
|
34
|
+
sera/models/_parse.py,sha256=q_YZ7PrHWIN85_WW-fPP7-2gLXlGWM2-EIdbYXuG7Xg,10052
|
35
|
+
sera/models/_property.py,sha256=4y9F58D6DoX25-6aWPBRiE72nCPQy0KWlGNDTZXSV-8,6038
|
36
|
+
sera/models/_schema.py,sha256=VxJEiqgVvbXgcSUK4UW6JnRcggk4nsooVSE6MyXmfNY,1636
|
34
37
|
sera/typing.py,sha256=Q4QMfbtfrCjC9tFfsZPhsAnbNX4lm4NHQ9lmjNXYdV0,772
|
35
|
-
sera_2-1.
|
36
|
-
sera_2-1.
|
37
|
-
sera_2-1.
|
38
|
+
sera_2-1.13.0.dist-info/METADATA,sha256=IFUs-nyqkQ4yDQeCL507_8not1XYcKeLFYrLxLg4lc8,867
|
39
|
+
sera_2-1.13.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
40
|
+
sera_2-1.13.0.dist-info/RECORD,,
|
File without changes
|