sera-2 1.16.0__py3-none-any.whl → 1.17.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/schema.py CHANGED
@@ -54,6 +54,20 @@ def export_tbls(schema: Schema, outfile: Path):
54
54
  "columns": [prop.name],
55
55
  }
56
56
  )
57
+
58
+ if prop.db.foreign_key is not None:
59
+ idprop = assert_not_null(prop.db.foreign_key.get_id_property())
60
+ out["relations"].append(
61
+ {
62
+ "table": cls.name,
63
+ "columns": [prop.name],
64
+ "cardinality": "zero_or_one",
65
+ "parent_table": prop.db.foreign_key.name,
66
+ "parent_columns": [idprop_name],
67
+ "parent_cardinality": "zero_or_one",
68
+ "def": f"FOREIGN KEY ({prop.name}) REFERENCES {prop.db.foreign_key.name}({idprop_name})",
69
+ }
70
+ )
57
71
  else:
58
72
  if prop.cardinality == Cardinality.MANY_TO_MANY:
59
73
  # For many-to-many relationships, we need to create a join table
@@ -1079,12 +1079,10 @@ def make_python_relational_model(
1079
1079
 
1080
1080
  target_pkg.module("base").write(program)
1081
1081
 
1082
- custom_types: list[ObjectProperty] = []
1083
-
1084
- for cls in schema.topological_sort():
1082
+ def make_orm(cls: Class):
1085
1083
  if cls.db is None or cls.name in reference_classes:
1086
1084
  # skip classes that are not stored in the database
1087
- continue
1085
+ return
1088
1086
 
1089
1087
  program = Program()
1090
1088
  program.import_("__future__.annotations", True)
@@ -1093,6 +1091,11 @@ def make_python_relational_model(
1093
1091
  program.import_("sqlalchemy.orm.Mapped", True)
1094
1092
  program.import_(f"{target_pkg.path}.base.Base", True)
1095
1093
 
1094
+ ident_manager = ImportHelper(
1095
+ program,
1096
+ GLOBAL_IDENTS,
1097
+ )
1098
+
1096
1099
  index_stmts = []
1097
1100
  if len(cls.db.indices) > 0:
1098
1101
  program.import_("sqlalchemy.Index", True)
@@ -1159,6 +1162,32 @@ def make_python_relational_model(
1159
1162
  proptype = f"Mapped[{sqltype.mapped_pytype}]"
1160
1163
 
1161
1164
  propvalargs: list[expr.Expr] = [expr.ExprIdent(sqltype.type)]
1165
+ if prop.db.foreign_key is not None:
1166
+ assert (
1167
+ prop.db.foreign_key.db is not None
1168
+ ), f"Foreign key {prop.db.foreign_key.name} must have a database mapping"
1169
+ foreign_key_idprop = prop.db.foreign_key.get_id_property()
1170
+ assert (
1171
+ foreign_key_idprop is not None
1172
+ ), f"Foreign key {prop.db.foreign_key.name} must have an id property"
1173
+ propvalargs.append(
1174
+ expr.ExprFuncCall(
1175
+ ident_manager.use("ForeignKey"),
1176
+ [
1177
+ expr.ExprConstant(
1178
+ f"{prop.db.foreign_key.db.table_name}.{foreign_key_idprop.name}"
1179
+ ),
1180
+ PredefinedFn.keyword_assignment(
1181
+ "ondelete",
1182
+ expr.ExprConstant("CASCADE"),
1183
+ ),
1184
+ PredefinedFn.keyword_assignment(
1185
+ "onupdate",
1186
+ expr.ExprConstant("CASCADE"),
1187
+ ),
1188
+ ],
1189
+ )
1190
+ )
1162
1191
  if prop.db.is_primary_key:
1163
1192
  propvalargs.append(
1164
1193
  PredefinedFn.keyword_assignment(
@@ -1208,6 +1237,11 @@ def make_python_relational_model(
1208
1237
 
1209
1238
  target_pkg.module(cls.get_pymodule_name()).write(program)
1210
1239
 
1240
+ custom_types: list[ObjectProperty] = []
1241
+
1242
+ for cls in schema.topological_sort():
1243
+ make_orm(cls)
1244
+
1211
1245
  # make a base class that implements the mapping for custom types
1212
1246
  custom_types = filter_duplication(
1213
1247
  custom_types, lambda p: (p.target.name, p.cardinality, p.is_optional, p.is_map)
sera/models/_parse.py CHANGED
@@ -160,6 +160,7 @@ def _parse_property(
160
160
  is_indexed=db.get("is_indexed", False)
161
161
  or db.get("is_unique", False)
162
162
  or db.get("is_primary_key", False),
163
+ foreign_key=schema.classes.get(db.get("foreign_key")),
163
164
  )
164
165
  if "db" in prop
165
166
  else None
sera/models/_property.py CHANGED
@@ -150,6 +150,12 @@ class DataPropDBInfo:
150
150
  is_unique: bool = False
151
151
  # whether this property is indexed or not
152
152
  is_indexed: bool = False
153
+ # this is used in conjunction with is_primary_key = True for the case of
154
+ # extending a table with frequently updated properties. The value for the `foreign_key`
155
+ # will be a target class. The cardinality is one-to-one, on target class deletion,
156
+ # this class will be deleted as well (because it's an extended table of the target class).
157
+ # on source (this class) deletion, the target class will not be deleted.
158
+ foreign_key: Optional[Class] = None
153
159
 
154
160
 
155
161
  @dataclass(kw_only=True)
sera/typing.py CHANGED
@@ -37,4 +37,5 @@ GLOBAL_IDENTS = {
37
37
  "AsyncSession": "sqlalchemy.ext.asyncio.AsyncSession",
38
38
  "ASGIConnection": "litestar.connection.ASGIConnection",
39
39
  "UNSET": "sera.typing.UNSET",
40
+ "ForeignKey": "sqlalchemy.ForeignKey",
40
41
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.16.0
3
+ Version: 1.17.0
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -1,7 +1,7 @@
1
1
  sera/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sera/constants.py,sha256=mzAaMyIx8TJK0-RYYJ5I24C4s0Uvj26OLMJmBo0pxHI,123
3
3
  sera/exports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- sera/exports/schema.py,sha256=EAFKwULu8Nmb3IUBMCyt7M6YM4TGPo8yRRRu_KxBTxs,7045
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
7
  sera/libs/api_helper.py,sha256=47y1kcwk3Xd2ZEMnUj_0OwCuUmgwOs5kYrE95BDVUn4,5411
@@ -17,7 +17,7 @@ sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  sera/make/__main__.py,sha256=HRfOR53p351h6KblVvYm3DLhDIfEtk6R0kjl78_S_S8,1453
18
18
  sera/make/make_app.py,sha256=n9NtW73O3s_5Q31VHIRmnd-jEIcpDO7ksAsOdovde2s,5999
19
19
  sera/make/make_python_api.py,sha256=iXGbKQ3IJvsY1ur_fhurr_THFNnH66E3Wl85o0emUbw,26853
20
- sera/make/make_python_model.py,sha256=cRb-fuHX0WH7XPAAliu6lycC0iEjE5kcKg4bBU40GwQ,61275
20
+ sera/make/make_python_model.py,sha256=Nc4vDGgM8icgWBqzNnMgEkLadf5EsZwbbHs3WLW9_co,62778
21
21
  sera/make/make_python_services.py,sha256=0ZpWLwQ7Nwfn8BXAikAB4JRpNknpSJyJgY5b1cjtxV4,2073
22
22
  sera/make/make_typescript_model.py,sha256=1ouYFCeqOlwEzsGBiXUn4VZtLJjJW7GSacdOSlQzhjI,67012
23
23
  sera/misc/__init__.py,sha256=mPKkik00j3tO_m45VPDJBjm8K85NpymRPl36Kh4hBn8,473
@@ -32,10 +32,10 @@ sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
32
32
  sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
33
33
  sera/models/_module.py,sha256=I-GfnTgAa-5R87qTAvEzOt-VVEGeFBBwubGCgUkXVSw,5159
34
34
  sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
35
- sera/models/_parse.py,sha256=MaGZty29lsVUFumWqNanIjAwptBNOMbVZMXLZ2A9_2g,12317
36
- sera/models/_property.py,sha256=2rSLs9JjSesrxrEugE7krYaBQOivKU882I8mZN94FlI,7017
35
+ sera/models/_parse.py,sha256=ciTLzCkO0q6xA1R_rHbnYJYK3Duo2oh56WeuwxXwJaI,12392
36
+ sera/models/_property.py,sha256=9yMDxrmbyuF6-29lQjiq163Xzwbk75TlmGBpu0NLpkI,7485
37
37
  sera/models/_schema.py,sha256=VxJEiqgVvbXgcSUK4UW6JnRcggk4nsooVSE6MyXmfNY,1636
38
- sera/typing.py,sha256=Fl4-UzLJu1GdLLk_g87fA7nT9wQGelNnGzag6dg_0gs,980
39
- sera_2-1.16.0.dist-info/METADATA,sha256=wZvclB-3wd_K5sN9Q64s832aMVXueS3p2xbDJKCplnk,852
40
- sera_2-1.16.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
41
- sera_2-1.16.0.dist-info/RECORD,,
38
+ sera/typing.py,sha256=o_DKfSvs8JpNRQ0kdaTc3BbfdkvibY3uY4tJRt-n2fQ,1023
39
+ sera_2-1.17.0.dist-info/METADATA,sha256=aIaXid2dkyX8P9nty-1eFHFBuH0Cpy34vOGDi1wTFkI,852
40
+ sera_2-1.17.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
41
+ sera_2-1.17.0.dist-info/RECORD,,