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.
@@ -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
- cls_ast(stmt.DefClassVarStatement(prop.name, pytype.type))
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
- expr.PredefinedFn.keyword_assignment(
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(expr.ExprIdent("mapped_column"), [])
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.datatype.is_list),
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
- expr.ExprConstant(prop.description.to_dict()),
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.4.9
3
+ Version: 1.5.1
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -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=CHLZuFd15RypLq1EK1j-0pq1BwxX33wshcehcl3EVNI,32164
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=ZZZ56-cMfM-4lmRnpRfB3DYZSqu6661lu1WWRFrAt3Y,33305
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=cVoxge1SW68dVmOjDZJU93tUq2ccJse3kSPqxrNkmgc,1091
22
- sera/models/_parse.py,sha256=ne0nI_PLYNiWwwkLJzmVkmv9c60bGjARcNFwkSsdJyA,6910
23
- sera/models/_property.py,sha256=8q0UZznXLcYdXhcjhu_S8Ey7r3kqT8SFNvkn3GiW-ts,5117
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.4.9.dist-info/METADATA,sha256=4VXNPNaqKyeF5KQ-KO9yRYgfuqJ4ya__TKGtqBtt1-k,856
27
- sera_2-1.4.9.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
28
- sera_2-1.4.9.dist-info/RECORD,,
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