sera-2 1.4.9__py3-none-any.whl → 1.5.1__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/make/make_python_model.py +27 -3
- sera/make/make_typescript_model.py +18 -2
- sera/models/_constraints.py +38 -0
- sera/models/_multi_lingual_string.py +3 -0
- sera/models/_parse.py +10 -0
- sera/models/_property.py +4 -0
- {sera_2-1.4.9.dist-info → sera_2-1.5.1.dist-info}/METADATA +1 -1
- {sera_2-1.4.9.dist-info → sera_2-1.5.1.dist-info}/RECORD +9 -8
- {sera_2-1.4.9.dist-info → sera_2-1.5.1.dist-info}/WHEEL +0 -0
sera/make/make_python_model.py
CHANGED
@@ -142,7 +142,19 @@ def make_python_data_model(
|
|
142
142
|
pytype = prop.get_data_model_datatype().get_python_type()
|
143
143
|
if pytype.dep is not None:
|
144
144
|
program.import_(pytype.dep, True)
|
145
|
-
|
145
|
+
pytype_type = pytype.type
|
146
|
+
if len(prop.data.constraints) > 0:
|
147
|
+
# if the property has constraints, we need to figure out
|
148
|
+
program.import_("typing.Annotated", True)
|
149
|
+
if len(prop.data.constraints) == 1:
|
150
|
+
pytype_type = f"Annotated[%s, %s]" % (
|
151
|
+
pytype_type,
|
152
|
+
prop.data.constraints[0].get_msgspec_constraint(),
|
153
|
+
)
|
154
|
+
else:
|
155
|
+
raise NotImplementedError(prop.data.constraints)
|
156
|
+
|
157
|
+
cls_ast(stmt.DefClassVarStatement(prop.name, pytype_type))
|
146
158
|
elif isinstance(prop, ObjectProperty):
|
147
159
|
if prop.target.db is not None:
|
148
160
|
# if the target class is in the database, we expect the user to pass the foreign key for it.
|
@@ -568,6 +580,10 @@ def make_python_relational_object_property(
|
|
568
580
|
"onupdate",
|
569
581
|
expr.ExprConstant(prop.db.on_target_update.to_sqlalchemy()),
|
570
582
|
),
|
583
|
+
PredefinedFn.keyword_assignment(
|
584
|
+
"nullable",
|
585
|
+
expr.ExprConstant(prop.is_optional),
|
586
|
+
),
|
571
587
|
],
|
572
588
|
),
|
573
589
|
],
|
@@ -600,7 +616,7 @@ def make_python_relational_object_property(
|
|
600
616
|
.datatype.get_sqlalchemy_type()
|
601
617
|
.type
|
602
618
|
),
|
603
|
-
|
619
|
+
PredefinedFn.keyword_assignment(
|
604
620
|
"nullable",
|
605
621
|
expr.ExprConstant(prop.is_optional),
|
606
622
|
),
|
@@ -614,7 +630,15 @@ def make_python_relational_object_property(
|
|
614
630
|
else:
|
615
631
|
assert prop.db.is_embedded == "json"
|
616
632
|
# we create a custom field, the custom field mapping need to be defined in the base
|
617
|
-
propval = expr.ExprFuncCall(
|
633
|
+
propval = expr.ExprFuncCall(
|
634
|
+
expr.ExprIdent("mapped_column"),
|
635
|
+
[
|
636
|
+
PredefinedFn.keyword_assignment(
|
637
|
+
"nullable",
|
638
|
+
expr.ExprConstant(prop.is_optional),
|
639
|
+
),
|
640
|
+
],
|
641
|
+
)
|
618
642
|
custom_types.append(prop)
|
619
643
|
|
620
644
|
cls_ast(stmt.DefClassVarStatement(propname, proptype, propval))
|
@@ -706,7 +706,15 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
706
706
|
(expr.ExprIdent("datatype"), expr.ExprConstant(tstype.type)),
|
707
707
|
(
|
708
708
|
expr.ExprIdent("isList"),
|
709
|
-
expr.ExprConstant(prop.
|
709
|
+
expr.ExprConstant(prop.get_data_model_datatype().is_list),
|
710
|
+
),
|
711
|
+
(
|
712
|
+
expr.ExprIdent("isRequired"),
|
713
|
+
expr.ExprConstant(
|
714
|
+
# TODO: fix me.
|
715
|
+
prop.db is None
|
716
|
+
or not prop.db.is_nullable
|
717
|
+
),
|
710
718
|
),
|
711
719
|
]
|
712
720
|
else:
|
@@ -747,6 +755,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
747
755
|
expr.ExprIdent("isEmbedded"),
|
748
756
|
expr.ExprConstant(prop.target.db is not None),
|
749
757
|
),
|
758
|
+
(
|
759
|
+
expr.ExprIdent("isRequired"),
|
760
|
+
expr.ExprConstant(not prop.is_optional),
|
761
|
+
),
|
750
762
|
]
|
751
763
|
|
752
764
|
prop_defs.append(
|
@@ -761,7 +773,11 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
761
773
|
),
|
762
774
|
(
|
763
775
|
expr.ExprIdent("description"),
|
764
|
-
|
776
|
+
(
|
777
|
+
expr.ExprConstant(prop.description.to_dict())
|
778
|
+
if not prop.description.is_empty()
|
779
|
+
else expr.ExprConstant("undefined")
|
780
|
+
),
|
765
781
|
),
|
766
782
|
]
|
767
783
|
+ tsprop
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Literal
|
5
|
+
|
6
|
+
ConstraintName = Literal["phone_number", "email", "not_empty", "username", "password"]
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass
|
10
|
+
class Constraint:
|
11
|
+
name: ConstraintName
|
12
|
+
args: tuple
|
13
|
+
|
14
|
+
def get_msgspec_constraint(self) -> str:
|
15
|
+
if self.name == "phone_number":
|
16
|
+
# the UI will ensure to submit it in E.164 format
|
17
|
+
return r"msgspec.Meta(pattern=r'^\+[1-9]\d{1,14}$')"
|
18
|
+
elif self.name == "email":
|
19
|
+
return r"msgspec.Meta(min_length=3, max_length=254, pattern=r'^[^@]+@[^@]+\.[^@]+$')"
|
20
|
+
elif self.name == "not_empty":
|
21
|
+
return "msgspec.Meta(min_length=1)"
|
22
|
+
elif self.name == "username":
|
23
|
+
return (
|
24
|
+
"msgspec.Meta(min_length=3, max_length=32, pattern=r'^[a-zA-Z0-9_]+$')"
|
25
|
+
)
|
26
|
+
elif self.name == "password":
|
27
|
+
return "msgspec.Meta(min_length=8, max_length=32)"
|
28
|
+
|
29
|
+
raise NotImplementedError()
|
30
|
+
|
31
|
+
|
32
|
+
predefined_constraints: dict[ConstraintName, Constraint] = {
|
33
|
+
"phone_number": Constraint("phone_number", ()),
|
34
|
+
"email": Constraint("email", ()),
|
35
|
+
"not_empty": Constraint("not_empty", ()),
|
36
|
+
"username": Constraint("username", ()),
|
37
|
+
"password": Constraint("password", ()),
|
38
|
+
}
|
@@ -20,6 +20,9 @@ class MultiLingualString(str):
|
|
20
20
|
def has_lang(self, lang: str) -> bool:
|
21
21
|
return lang in self.lang2value
|
22
22
|
|
23
|
+
def is_empty(self):
|
24
|
+
return all(value == "" for value in self.lang2value.values())
|
25
|
+
|
23
26
|
@staticmethod
|
24
27
|
def en(label: str):
|
25
28
|
return MultiLingualString(lang2value={"en": label}, lang="en")
|
sera/models/_parse.py
CHANGED
@@ -7,6 +7,7 @@ from typing import Sequence
|
|
7
7
|
|
8
8
|
import serde.yaml
|
9
9
|
from sera.models._class import Class, ClassDBMapInfo, Index
|
10
|
+
from sera.models._constraints import Constraint, predefined_constraints
|
10
11
|
from sera.models._datatype import (
|
11
12
|
DataType,
|
12
13
|
predefined_datatypes,
|
@@ -99,6 +100,9 @@ def _parse_property(
|
|
99
100
|
data_attrs = PropDataAttrs(
|
100
101
|
is_private=_data.get("is_private", False),
|
101
102
|
datatype=_parse_datatype(_data["datatype"]) if "datatype" in _data else None,
|
103
|
+
constraints=[
|
104
|
+
_parse_constraint(constraint) for constraint in _data.get("constraints", [])
|
105
|
+
],
|
102
106
|
)
|
103
107
|
|
104
108
|
assert isinstance(prop, dict), prop
|
@@ -163,6 +167,12 @@ def _parse_multi_lingual_string(o: dict | str) -> MultiLingualString:
|
|
163
167
|
return MultiLingualString(lang2value=o, lang="en")
|
164
168
|
|
165
169
|
|
170
|
+
def _parse_constraint(constraint: str) -> Constraint:
|
171
|
+
if constraint not in predefined_constraints:
|
172
|
+
raise NotImplementedError(constraint)
|
173
|
+
return predefined_constraints[constraint]
|
174
|
+
|
175
|
+
|
166
176
|
def _parse_datatype(datatype: dict | str) -> DataType:
|
167
177
|
if isinstance(datatype, str):
|
168
178
|
if datatype.endswith("[]"):
|
sera/models/_property.py
CHANGED
@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
|
|
4
4
|
from enum import Enum
|
5
5
|
from typing import TYPE_CHECKING, Literal, Optional
|
6
6
|
|
7
|
+
from sera.models._constraints import Constraint
|
7
8
|
from sera.models._datatype import DataType
|
8
9
|
from sera.models._multi_lingual_string import MultiLingualString
|
9
10
|
|
@@ -66,6 +67,9 @@ class PropDataAttrs:
|
|
66
67
|
# whether this data model has a different data type than the one from the database
|
67
68
|
datatype: Optional[DataType] = None
|
68
69
|
|
70
|
+
# list of constraints applied to the data model's field
|
71
|
+
constraints: list[Constraint] = field(default_factory=list)
|
72
|
+
|
69
73
|
|
70
74
|
@dataclass(kw_only=True)
|
71
75
|
class Property:
|
@@ -8,21 +8,22 @@ sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
sera/make/__main__.py,sha256=EvskvcZ5BikA6c0QyUXMbHfKrjr44W0JTmW_-iXFEGg,1416
|
9
9
|
sera/make/make_app.py,sha256=4iXMmyvOSLvUWzZLrXWUg8jnkwOZyDH-AZVwKcQMfLY,5654
|
10
10
|
sera/make/make_python_api.py,sha256=RuJUm9z-4plBEtjobeOPr12o27OT-0tSeXI4ZlM3IY0,29433
|
11
|
-
sera/make/make_python_model.py,sha256=
|
11
|
+
sera/make/make_python_model.py,sha256=hKA5xnKsReibfHEsW3pXJgRgxLYZUhVPUKA_gEvdRJM,33150
|
12
12
|
sera/make/make_python_services.py,sha256=RsinYZdfkrTlTn9CT50VgqGs9w6IZawsJx-KEmqfnEY,2062
|
13
|
-
sera/make/make_typescript_model.py,sha256=
|
13
|
+
sera/make/make_typescript_model.py,sha256=x9OFcKx9oke-T9ZLG-6gSckXZEVvT4MJMA6VBqsdrRY,34013
|
14
14
|
sera/misc/__init__.py,sha256=G-vpwJtdS596Yle7W007vArHRwmK0QgGmLhzlqTiHMQ,311
|
15
15
|
sera/misc/_utils.py,sha256=V5g4oLGHOhUCR75Kkcn1w01pAvGvaepK-T8Z3pIgHjI,1450
|
16
16
|
sera/models/__init__.py,sha256=Kz3Cg7GIVzsM0qFG5ERB5gobLAxBy2SdxItIl73ui2g,759
|
17
17
|
sera/models/_class.py,sha256=Wf0e8x6-szG9TzoFkAlqj7_dG0SCICMBw_333n3paxk,2514
|
18
18
|
sera/models/_collection.py,sha256=ZnQEriKC4X88Zz48Kn1AVZKH-1_l8OgWa-zf2kcQOOE,1414
|
19
|
+
sera/models/_constraints.py,sha256=lZmCh6Py0UVMdhTR7zUOPPzGqJGbmDCzf7xH7yITcbQ,1278
|
19
20
|
sera/models/_datatype.py,sha256=uMxK_8wBLowaIMIAYCb3V17YmkzJrKKc5orjImzqWbA,5818
|
20
21
|
sera/models/_module.py,sha256=4xZO8Ey4La7JTiCRfU50pYw4V6ssECH6dGIuplodwQY,5215
|
21
|
-
sera/models/_multi_lingual_string.py,sha256=
|
22
|
-
sera/models/_parse.py,sha256=
|
23
|
-
sera/models/_property.py,sha256=
|
22
|
+
sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
|
23
|
+
sera/models/_parse.py,sha256=t43g-PpW_Fy1DeN8f1ys6bLVkoT5EngK-hTuDuN2Kac,7301
|
24
|
+
sera/models/_property.py,sha256=nRDyptwADR8ZIA-fpLryRY-pHbBAdKv0PwFfJFQ-d8s,5290
|
24
25
|
sera/models/_schema.py,sha256=1F_Ict1NLvDcQDUZwcvnRS5MtsOTv584y3ChymUeUYA,1046
|
25
26
|
sera/typing.py,sha256=QkzT07FtBwoiX2WHXDUrU1zSPLoRmKJgsXa2kLyLMxU,327
|
26
|
-
sera_2-1.
|
27
|
-
sera_2-1.
|
28
|
-
sera_2-1.
|
27
|
+
sera_2-1.5.1.dist-info/METADATA,sha256=GkB3rfwqgYfRTIAGo1AOaBdR1TGImkfsN3RNYfF6DjQ,856
|
28
|
+
sera_2-1.5.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
29
|
+
sera_2-1.5.1.dist-info/RECORD,,
|
File without changes
|