sera-2 1.2.0__py3-none-any.whl → 1.4.2__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/constants.py +4 -0
- sera/libs/base_orm.py +1 -1
- sera/libs/base_service.py +44 -8
- sera/make/__main__.py +14 -1
- sera/make/make_app.py +46 -16
- sera/make/make_python_api.py +63 -44
- sera/make/make_python_model.py +262 -120
- sera/make/make_typescript_model.py +846 -0
- sera/misc/__init__.py +4 -0
- sera/misc/_utils.py +12 -0
- sera/models/__init__.py +3 -1
- sera/models/_class.py +18 -1
- sera/models/_collection.py +10 -2
- sera/models/_datatype.py +150 -45
- sera/models/_module.py +37 -6
- sera/models/_parse.py +69 -18
- sera/models/_property.py +27 -3
- sera/typing.py +3 -0
- {sera_2-1.2.0.dist-info → sera_2-1.4.2.dist-info}/METADATA +3 -3
- sera_2-1.4.2.dist-info/RECORD +30 -0
- {sera_2-1.2.0.dist-info → sera_2-1.4.2.dist-info}/WHEEL +1 -1
- sera/.DS_Store +0 -0
- sera/make/.DS_Store +0 -0
- sera_2-1.2.0.dist-info/RECORD +0 -31
sera/make/make_python_model.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
from typing import Sequence
|
3
|
+
from typing import Callable, Sequence
|
5
4
|
|
6
5
|
from codegen.models import AST, DeferredVar, PredefinedFn, Program, expr, stmt
|
7
6
|
from sera.misc import (
|
@@ -16,12 +15,22 @@ from sera.models import (
|
|
16
15
|
DataProperty,
|
17
16
|
ObjectProperty,
|
18
17
|
Package,
|
18
|
+
PyTypeWithDep,
|
19
19
|
Schema,
|
20
20
|
)
|
21
|
+
from sera.typing import ObjectPath
|
21
22
|
|
22
23
|
|
23
|
-
def make_python_data_model(
|
24
|
-
|
24
|
+
def make_python_data_model(
|
25
|
+
schema: Schema, target_pkg: Package, reference_classes: dict[str, ObjectPath]
|
26
|
+
):
|
27
|
+
"""Generate public classes for the API from the schema.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
schema: The schema to generate the classes from.
|
31
|
+
target_pkg: The package to write the classes to.
|
32
|
+
reference_classes: A dictionary of class names to their references (e.g., the ones that are defined outside and used as referenced such as Tenant).
|
33
|
+
"""
|
25
34
|
app = target_pkg.app
|
26
35
|
|
27
36
|
def from_db_type_conversion(
|
@@ -30,23 +39,29 @@ def make_python_data_model(schema: Schema, target_pkg: Package):
|
|
30
39
|
value = PredefinedFn.attr_getter(record, expr.ExprIdent(prop.name))
|
31
40
|
if isinstance(prop, ObjectProperty) and prop.target.db is not None:
|
32
41
|
if prop.cardinality.is_star_to_many():
|
33
|
-
|
42
|
+
value = PredefinedFn.map_list(
|
34
43
|
value,
|
35
|
-
lambda item:
|
36
|
-
|
37
|
-
expr.ExprIdent(prop.target.name), expr.ExprIdent("from_db")
|
38
|
-
),
|
39
|
-
[PredefinedFn.attr_getter(item, expr.ExprIdent(prop.name))],
|
44
|
+
lambda item: PredefinedFn.attr_getter(
|
45
|
+
item, expr.ExprIdent(prop.name + "_id")
|
40
46
|
),
|
41
47
|
)
|
42
48
|
else:
|
43
|
-
|
44
|
-
|
45
|
-
expr.ExprIdent(prop.target.name), expr.ExprIdent("from_db")
|
46
|
-
),
|
47
|
-
[value],
|
49
|
+
value = PredefinedFn.attr_getter(
|
50
|
+
record, expr.ExprIdent(prop.name + "_id")
|
48
51
|
)
|
49
52
|
|
53
|
+
target_idprop = assert_not_null(prop.target.get_id_property())
|
54
|
+
conversion_fn = get_data_conversion(
|
55
|
+
target_idprop.datatype.get_python_type().type,
|
56
|
+
target_idprop.get_data_model_datatype().get_python_type().type,
|
57
|
+
)
|
58
|
+
value = conversion_fn(value)
|
59
|
+
elif isinstance(prop, DataProperty) and prop.is_diff_data_model_datatype():
|
60
|
+
value = get_data_conversion(
|
61
|
+
prop.datatype.get_python_type().type,
|
62
|
+
prop.get_data_model_datatype().get_python_type().type,
|
63
|
+
)(value)
|
64
|
+
|
50
65
|
return value
|
51
66
|
|
52
67
|
def to_db_type_conversion(
|
@@ -56,46 +71,75 @@ def make_python_data_model(schema: Schema, target_pkg: Package):
|
|
56
71
|
prop: DataProperty | ObjectProperty,
|
57
72
|
):
|
58
73
|
value = PredefinedFn.attr_getter(slf, expr.ExprIdent(prop.name))
|
59
|
-
if (
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
if isinstance(prop, ObjectProperty):
|
75
|
+
if (
|
76
|
+
prop.target.db is not None
|
77
|
+
and prop.cardinality == Cardinality.MANY_TO_MANY
|
78
|
+
):
|
79
|
+
# we have to use the associated object
|
80
|
+
# if this isn't a many-to-many relationship, we only keep the id, so no need to convert to the type.
|
81
|
+
AssociationTable = f"{cls.name}{prop.target.name}"
|
82
|
+
program.import_(
|
83
|
+
app.models.db.path
|
84
|
+
+ f".{to_snake_case(AssociationTable)}.{AssociationTable}",
|
85
|
+
True,
|
86
|
+
)
|
87
|
+
|
88
|
+
target_idprop = assert_not_null(prop.target.get_id_property())
|
89
|
+
conversion_fn = get_data_conversion(
|
90
|
+
target_idprop.get_data_model_datatype().get_python_type().type,
|
91
|
+
target_idprop.datatype.get_python_type().type,
|
92
|
+
)
|
93
|
+
|
94
|
+
return PredefinedFn.map_list(
|
95
|
+
value,
|
96
|
+
lambda item: expr.ExprFuncCall(
|
97
|
+
expr.ExprIdent(AssociationTable),
|
98
|
+
[
|
99
|
+
PredefinedFn.keyword_assignment(
|
100
|
+
f"{prop.name}_id", conversion_fn(item)
|
101
|
+
)
|
102
|
+
],
|
103
|
+
),
|
104
|
+
)
|
105
|
+
elif prop.target.db is None:
|
106
|
+
# if the target class is not in the database, we need to convert the value to the python type used in db.
|
107
|
+
# if the cardinality is many-to-many, we need to convert each item in the list.
|
108
|
+
if prop.cardinality.is_star_to_many():
|
109
|
+
value = PredefinedFn.map_list(
|
110
|
+
value, lambda item: expr.ExprMethodCall(item, "to_db", [])
|
111
|
+
)
|
112
|
+
else:
|
113
|
+
value = expr.ExprMethodCall(value, "to_db", [])
|
114
|
+
elif isinstance(prop, DataProperty) and prop.is_diff_data_model_datatype():
|
115
|
+
# convert the value to the python type used in db.
|
116
|
+
value = get_data_conversion(
|
117
|
+
prop.get_data_model_datatype().get_python_type().type,
|
118
|
+
prop.datatype.get_python_type().type,
|
119
|
+
)(value)
|
78
120
|
return value
|
79
121
|
|
80
|
-
def
|
122
|
+
def make_upsert(program: Program, cls: Class):
|
81
123
|
program.import_("__future__.annotations", True)
|
82
124
|
program.import_("msgspec", False)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
125
|
+
if cls.db is not None:
|
126
|
+
# if the class is stored in the database, we need to import the database module
|
127
|
+
program.import_(
|
128
|
+
app.models.db.path + f".{cls.get_pymodule_name()}.{cls.name}",
|
129
|
+
True,
|
130
|
+
alias=f"{cls.name}DB",
|
131
|
+
)
|
88
132
|
cls_ast = program.root.class_(
|
89
133
|
"Upsert" + cls.name, [expr.ExprIdent("msgspec.Struct")]
|
90
134
|
)
|
91
135
|
for prop in cls.properties.values():
|
92
136
|
# this is a create object, so users can create private field
|
93
137
|
# hence, we do not check for prop.is_private
|
94
|
-
# if prop.is_private:
|
138
|
+
# if prop.data.is_private:
|
95
139
|
# continue
|
96
140
|
|
97
141
|
if isinstance(prop, DataProperty):
|
98
|
-
pytype = prop.
|
142
|
+
pytype = prop.get_data_model_datatype().get_python_type()
|
99
143
|
if pytype.dep is not None:
|
100
144
|
program.import_(pytype.dep, True)
|
101
145
|
cls_ast(stmt.DefClassVarStatement(prop.name, pytype.type))
|
@@ -104,19 +148,21 @@ def make_python_data_model(schema: Schema, target_pkg: Package):
|
|
104
148
|
# if the target class is in the database, we expect the user to pass the foreign key for it.
|
105
149
|
pytype = (
|
106
150
|
assert_not_null(prop.target.get_id_property())
|
107
|
-
.
|
108
|
-
.
|
151
|
+
.get_data_model_datatype()
|
152
|
+
.get_python_type()
|
109
153
|
)
|
110
154
|
else:
|
111
|
-
|
112
|
-
f"{
|
113
|
-
|
155
|
+
pytype = PyTypeWithDep(
|
156
|
+
f"Upsert{prop.target.name}",
|
157
|
+
f"{target_pkg.module(prop.target.get_pymodule_name()).path}.Upsert{prop.target.name}",
|
114
158
|
)
|
115
|
-
|
159
|
+
|
160
|
+
if pytype.dep is not None:
|
161
|
+
program.import_(pytype.dep, True)
|
116
162
|
|
117
163
|
if prop.cardinality.is_star_to_many():
|
118
|
-
pytype =
|
119
|
-
cls_ast(stmt.DefClassVarStatement(prop.name, pytype))
|
164
|
+
pytype = pytype.as_list_type()
|
165
|
+
cls_ast(stmt.DefClassVarStatement(prop.name, pytype.type))
|
120
166
|
|
121
167
|
# has_to_db = True
|
122
168
|
# if any(prop for prop in cls.properties.values() if isinstance(prop, ObjectProperty) and prop.cardinality == Cardinality.MANY_TO_MANY):
|
@@ -128,11 +174,15 @@ def make_python_data_model(schema: Schema, target_pkg: Package):
|
|
128
174
|
[
|
129
175
|
DeferredVar.simple("self"),
|
130
176
|
],
|
131
|
-
return_type=expr.ExprIdent(
|
177
|
+
return_type=expr.ExprIdent(
|
178
|
+
f"{cls.name}DB" if cls.db is not None else cls.name
|
179
|
+
),
|
132
180
|
)(
|
133
181
|
lambda ast10: ast10.return_(
|
134
182
|
expr.ExprFuncCall(
|
135
|
-
expr.ExprIdent(
|
183
|
+
expr.ExprIdent(
|
184
|
+
f"{cls.name}DB" if cls.db is not None else cls.name
|
185
|
+
),
|
136
186
|
[
|
137
187
|
to_db_type_conversion(
|
138
188
|
program, expr.ExprIdent("self"), cls, prop
|
@@ -144,83 +194,120 @@ def make_python_data_model(schema: Schema, target_pkg: Package):
|
|
144
194
|
),
|
145
195
|
)
|
146
196
|
|
147
|
-
def
|
197
|
+
def make_normal(program: Program, cls: Class):
|
148
198
|
if not cls.is_public:
|
149
199
|
# skip classes that are not public
|
150
200
|
return
|
151
201
|
|
152
202
|
program.import_("__future__.annotations", True)
|
153
203
|
program.import_("msgspec", False)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
204
|
+
if cls.db is not None:
|
205
|
+
# if the class is stored in the database, we need to import the database module
|
206
|
+
program.import_(
|
207
|
+
app.models.db.path + f".{cls.get_pymodule_name()}.{cls.name}",
|
208
|
+
True,
|
209
|
+
alias=f"{cls.name}DB",
|
210
|
+
)
|
159
211
|
|
160
212
|
cls_ast = program.root.class_(cls.name, [expr.ExprIdent("msgspec.Struct")])
|
161
213
|
for prop in cls.properties.values():
|
162
|
-
if prop.is_private:
|
214
|
+
if prop.data.is_private:
|
163
215
|
# skip private fields as this is for APIs exchange
|
164
216
|
continue
|
165
217
|
|
166
218
|
if isinstance(prop, DataProperty):
|
167
|
-
pytype = prop.
|
219
|
+
pytype = prop.get_data_model_datatype().get_python_type()
|
168
220
|
if pytype.dep is not None:
|
169
221
|
program.import_(pytype.dep, True)
|
170
222
|
cls_ast(stmt.DefClassVarStatement(prop.name, pytype.type))
|
171
223
|
elif isinstance(prop, ObjectProperty):
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
224
|
+
if prop.target.db is not None:
|
225
|
+
pytype = (
|
226
|
+
assert_not_null(prop.target.get_id_property())
|
227
|
+
.get_data_model_datatype()
|
228
|
+
.get_python_type()
|
229
|
+
)
|
178
230
|
else:
|
179
|
-
pytype =
|
180
|
-
|
231
|
+
pytype = PyTypeWithDep(
|
232
|
+
prop.target.name,
|
233
|
+
f"{target_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}",
|
234
|
+
)
|
235
|
+
|
236
|
+
if pytype.dep is not None:
|
237
|
+
program.import_(pytype.dep, True)
|
238
|
+
|
239
|
+
if prop.cardinality.is_star_to_many():
|
240
|
+
pytype = pytype.as_list_type()
|
241
|
+
cls_ast(stmt.DefClassVarStatement(prop.name, pytype.type))
|
181
242
|
|
182
243
|
cls_ast(
|
183
244
|
stmt.LineBreak(),
|
184
|
-
|
185
|
-
|
245
|
+
(
|
246
|
+
stmt.PythonDecoratorStatement(
|
247
|
+
expr.ExprFuncCall(expr.ExprIdent("classmethod"), [])
|
248
|
+
)
|
249
|
+
if cls.db is not None
|
250
|
+
else None
|
186
251
|
),
|
187
|
-
lambda ast00:
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
expr.
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
252
|
+
lambda ast00: (
|
253
|
+
ast00.func(
|
254
|
+
"from_db",
|
255
|
+
[
|
256
|
+
DeferredVar.simple("cls"),
|
257
|
+
DeferredVar.simple("record", expr.ExprIdent(f"{cls.name}DB")),
|
258
|
+
],
|
259
|
+
)(
|
260
|
+
lambda ast10: ast10.return_(
|
261
|
+
expr.ExprFuncCall(
|
262
|
+
expr.ExprIdent("cls"),
|
263
|
+
[
|
264
|
+
from_db_type_conversion(expr.ExprIdent("record"), prop)
|
265
|
+
for prop in cls.properties.values()
|
266
|
+
if not prop.data.is_private
|
267
|
+
],
|
268
|
+
)
|
202
269
|
)
|
203
270
|
)
|
271
|
+
if cls.db is not None
|
272
|
+
else None
|
204
273
|
),
|
205
274
|
)
|
206
275
|
|
207
276
|
for cls in schema.topological_sort():
|
277
|
+
if cls.name in reference_classes:
|
278
|
+
continue
|
279
|
+
|
208
280
|
program = Program()
|
209
|
-
|
281
|
+
make_upsert(program, cls)
|
210
282
|
program.root.linebreak()
|
211
|
-
|
283
|
+
make_normal(program, cls)
|
212
284
|
target_pkg.module(cls.get_pymodule_name()).write(program)
|
213
285
|
|
214
286
|
|
215
287
|
def make_python_relational_model(
|
216
|
-
schema: Schema,
|
288
|
+
schema: Schema,
|
289
|
+
target_pkg: Package,
|
290
|
+
target_data_pkg: Package,
|
291
|
+
reference_classes: dict[str, ObjectPath],
|
217
292
|
):
|
218
293
|
"""Make python classes for relational database using SQLAlchemy.
|
219
294
|
|
220
295
|
The new classes is going be compatible with SQLAlchemy 2.
|
296
|
+
|
297
|
+
Args:
|
298
|
+
schema: The schema to generate the classes from.
|
299
|
+
target_pkg: The package to write the classes to.
|
300
|
+
target_data_pkg: The package to write the data classes to.
|
301
|
+
reference_classes: A dictionary of class names to their references (e.g., the ones that are defined outside and used as referenced such as Tenant).
|
221
302
|
"""
|
222
303
|
app = target_pkg.app
|
223
304
|
|
305
|
+
def get_property_name(prop: DataProperty | ObjectProperty):
|
306
|
+
if isinstance(prop, ObjectProperty):
|
307
|
+
if prop.target.db is not None:
|
308
|
+
return f"{prop.name}_id"
|
309
|
+
return prop.name
|
310
|
+
|
224
311
|
def make_base(custom_types: Sequence[ObjectProperty]):
|
225
312
|
"""Make a base class for our database."""
|
226
313
|
program = Program()
|
@@ -246,18 +333,18 @@ def make_python_relational_model(
|
|
246
333
|
if custom_type.cardinality.is_star_to_many():
|
247
334
|
if custom_type.is_map:
|
248
335
|
program.import_("typing.Mapping", True)
|
249
|
-
program.import_("sera.libs.
|
336
|
+
program.import_("sera.libs.base_orm.DictDataclassType", True)
|
250
337
|
type = f"Mapping[str, {custom_type.target.name}]"
|
251
|
-
maptype = f"
|
338
|
+
maptype = f"DictDataclassType({custom_type.target.name})"
|
252
339
|
else:
|
253
340
|
program.import_("typing.Sequence", True)
|
254
|
-
program.import_("sera.libs.
|
341
|
+
program.import_("sera.libs.base_orm.ListDataclassType", True)
|
255
342
|
type = f"Sequence[str, {custom_type.target.name}]"
|
256
|
-
maptype = f"
|
343
|
+
maptype = f"ListDataclassType({custom_type.target.name})"
|
257
344
|
else:
|
258
|
-
program.import_("sera.libs.
|
345
|
+
program.import_("sera.libs.base_orm.DataclassType", True)
|
259
346
|
type = custom_type.target.name
|
260
|
-
maptype = f"
|
347
|
+
maptype = f"DataclassType({custom_type.target.name})"
|
261
348
|
|
262
349
|
if custom_type.is_optional:
|
263
350
|
program.import_("typing.Optional", True)
|
@@ -305,7 +392,7 @@ def make_python_relational_model(
|
|
305
392
|
custom_types: list[ObjectProperty] = []
|
306
393
|
|
307
394
|
for cls in schema.topological_sort():
|
308
|
-
if cls.db is None:
|
395
|
+
if cls.db is None or cls.name in reference_classes:
|
309
396
|
# skip classes that are not stored in the database
|
310
397
|
continue
|
311
398
|
|
@@ -316,6 +403,40 @@ def make_python_relational_model(
|
|
316
403
|
program.import_("sqlalchemy.orm.Mapped", True)
|
317
404
|
program.import_(f"{target_pkg.path}.base.Base", True)
|
318
405
|
|
406
|
+
index_stmts = []
|
407
|
+
if len(cls.db.indices) > 0:
|
408
|
+
program.import_("sqlalchemy.Index", True)
|
409
|
+
index_stmts.append(
|
410
|
+
stmt.DefClassVarStatement(
|
411
|
+
"_table_args__",
|
412
|
+
None,
|
413
|
+
PredefinedFn.tuple(
|
414
|
+
[
|
415
|
+
expr.ExprFuncCall(
|
416
|
+
expr.ExprIdent("Index"),
|
417
|
+
[expr.ExprConstant(index.name)]
|
418
|
+
+ [
|
419
|
+
expr.ExprConstant(
|
420
|
+
get_property_name(cls.properties[prop])
|
421
|
+
)
|
422
|
+
for prop in index.columns
|
423
|
+
]
|
424
|
+
+ (
|
425
|
+
[
|
426
|
+
PredefinedFn.keyword_assignment(
|
427
|
+
"unique", expr.ExprConstant(index.unique)
|
428
|
+
)
|
429
|
+
]
|
430
|
+
if index.unique
|
431
|
+
else []
|
432
|
+
),
|
433
|
+
)
|
434
|
+
for index in cls.db.indices
|
435
|
+
]
|
436
|
+
),
|
437
|
+
)
|
438
|
+
)
|
439
|
+
|
319
440
|
cls_ast = program.root.class_(
|
320
441
|
cls.name, [expr.ExprIdent("MappedAsDataclass"), expr.ExprIdent("Base")]
|
321
442
|
)
|
@@ -325,6 +446,7 @@ def make_python_relational_model(
|
|
325
446
|
type=None,
|
326
447
|
value=expr.ExprConstant(cls.db.table_name),
|
327
448
|
),
|
449
|
+
*index_stmts,
|
328
450
|
stmt.LineBreak(),
|
329
451
|
)
|
330
452
|
|
@@ -334,14 +456,14 @@ def make_python_relational_model(
|
|
334
456
|
continue
|
335
457
|
|
336
458
|
if isinstance(prop, DataProperty):
|
337
|
-
|
338
|
-
|
339
|
-
program.import_(
|
459
|
+
sqltype = prop.datatype.get_sqlalchemy_type()
|
460
|
+
for dep in sqltype.deps:
|
461
|
+
program.import_(dep, True)
|
340
462
|
|
341
463
|
propname = prop.name
|
342
|
-
proptype = f"Mapped[{
|
464
|
+
proptype = f"Mapped[{sqltype.mapped_pytype}]"
|
343
465
|
|
344
|
-
propvalargs = []
|
466
|
+
propvalargs: list[expr.Expr] = [expr.ExprIdent(sqltype.type)]
|
345
467
|
if prop.db.is_primary_key:
|
346
468
|
propvalargs.append(
|
347
469
|
PredefinedFn.keyword_assignment(
|
@@ -354,12 +476,25 @@ def make_python_relational_model(
|
|
354
476
|
"autoincrement", expr.ExprConstant("auto")
|
355
477
|
)
|
356
478
|
)
|
479
|
+
else:
|
357
480
|
if prop.db.is_unique:
|
358
481
|
propvalargs.append(
|
359
482
|
PredefinedFn.keyword_assignment(
|
360
483
|
"unique", expr.ExprConstant(True)
|
361
484
|
)
|
362
485
|
)
|
486
|
+
elif prop.db.is_indexed:
|
487
|
+
propvalargs.append(
|
488
|
+
PredefinedFn.keyword_assignment(
|
489
|
+
"index", expr.ExprConstant(True)
|
490
|
+
)
|
491
|
+
)
|
492
|
+
if prop.db.is_nullable:
|
493
|
+
propvalargs.append(
|
494
|
+
PredefinedFn.keyword_assignment(
|
495
|
+
"nullable", expr.ExprConstant(True)
|
496
|
+
)
|
497
|
+
)
|
363
498
|
propval = expr.ExprFuncCall(
|
364
499
|
expr.ExprIdent("mapped_column"), propvalargs
|
365
500
|
)
|
@@ -369,6 +504,7 @@ def make_python_relational_model(
|
|
369
504
|
make_python_relational_object_property(
|
370
505
|
program=program,
|
371
506
|
target_pkg=target_pkg,
|
507
|
+
target_data_pkg=target_data_pkg,
|
372
508
|
cls_ast=cls_ast,
|
373
509
|
cls=cls,
|
374
510
|
prop=prop,
|
@@ -387,6 +523,7 @@ def make_python_relational_model(
|
|
387
523
|
def make_python_relational_object_property(
|
388
524
|
program: Program,
|
389
525
|
target_pkg: Package,
|
526
|
+
target_data_pkg: Package,
|
390
527
|
cls_ast: AST,
|
391
528
|
cls: Class,
|
392
529
|
prop: ObjectProperty,
|
@@ -395,7 +532,7 @@ def make_python_relational_object_property(
|
|
395
532
|
assert prop.db is not None
|
396
533
|
if prop.target.db is not None:
|
397
534
|
# if the target class is in the database, we generate a foreign key for it.
|
398
|
-
program.import_("sqlalchemy.
|
535
|
+
program.import_("sqlalchemy.ForeignKey", True)
|
399
536
|
|
400
537
|
if prop.cardinality == Cardinality.MANY_TO_MANY:
|
401
538
|
make_python_relational_object_property_many_to_many(
|
@@ -411,13 +548,14 @@ def make_python_relational_object_property(
|
|
411
548
|
idprop = prop.target.get_id_property()
|
412
549
|
assert idprop is not None
|
413
550
|
idprop_pytype = idprop.datatype.get_sqlalchemy_type()
|
414
|
-
|
415
|
-
program.import_(
|
551
|
+
for dep in idprop_pytype.deps:
|
552
|
+
program.import_(dep, True)
|
416
553
|
|
417
|
-
proptype = f"Mapped[{idprop_pytype.
|
554
|
+
proptype = f"Mapped[{idprop_pytype.mapped_pytype}]"
|
418
555
|
propval = expr.ExprFuncCall(
|
419
556
|
expr.ExprIdent("mapped_column"),
|
420
557
|
[
|
558
|
+
expr.ExprIdent(idprop_pytype.type),
|
421
559
|
expr.ExprFuncCall(
|
422
560
|
expr.ExprIdent("ForeignKey"),
|
423
561
|
[
|
@@ -440,7 +578,7 @@ def make_python_relational_object_property(
|
|
440
578
|
|
441
579
|
# if the target class is not in the database,
|
442
580
|
program.import_(
|
443
|
-
f"{
|
581
|
+
f"{target_data_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}",
|
444
582
|
is_import_attr=True,
|
445
583
|
)
|
446
584
|
propname = prop.name
|
@@ -474,6 +612,7 @@ def make_python_relational_object_property(
|
|
474
612
|
propvalargs,
|
475
613
|
)
|
476
614
|
else:
|
615
|
+
assert prop.db.is_embedded == "json"
|
477
616
|
# we create a custom field, the custom field mapping need to be defined in the base
|
478
617
|
propval = expr.ExprFuncCall(expr.ExprIdent("mapped_column"), [])
|
479
618
|
custom_types.append(prop)
|
@@ -527,21 +666,6 @@ def make_python_relational_object_property_many_to_many(
|
|
527
666
|
value=expr.ExprConstant(f"{clsdb.table_name}_{targetdb.table_name}"),
|
528
667
|
),
|
529
668
|
stmt.LineBreak(),
|
530
|
-
stmt.DefClassVarStatement(
|
531
|
-
"id",
|
532
|
-
"Mapped[int]",
|
533
|
-
expr.ExprFuncCall(
|
534
|
-
expr.ExprIdent("mapped_column"),
|
535
|
-
[
|
536
|
-
PredefinedFn.keyword_assignment(
|
537
|
-
"primary_key", expr.ExprConstant(True)
|
538
|
-
),
|
539
|
-
PredefinedFn.keyword_assignment(
|
540
|
-
"autoincrement", expr.ExprConstant("auto")
|
541
|
-
),
|
542
|
-
],
|
543
|
-
),
|
544
|
-
),
|
545
669
|
stmt.DefClassVarStatement(
|
546
670
|
to_snake_case(cls.name),
|
547
671
|
f"Mapped[{cls.name}]",
|
@@ -585,6 +709,9 @@ def make_python_relational_object_property_many_to_many(
|
|
585
709
|
),
|
586
710
|
],
|
587
711
|
),
|
712
|
+
PredefinedFn.keyword_assignment(
|
713
|
+
"primary_key", expr.ExprConstant(True)
|
714
|
+
),
|
588
715
|
],
|
589
716
|
),
|
590
717
|
),
|
@@ -627,6 +754,9 @@ def make_python_relational_object_property_many_to_many(
|
|
627
754
|
),
|
628
755
|
],
|
629
756
|
),
|
757
|
+
PredefinedFn.keyword_assignment(
|
758
|
+
"primary_key", expr.ExprConstant(True)
|
759
|
+
),
|
630
760
|
],
|
631
761
|
),
|
632
762
|
),
|
@@ -639,6 +769,8 @@ def make_python_relational_object_property_many_to_many(
|
|
639
769
|
# now we add the relationship to the source.
|
640
770
|
# we can configure it to be list, set, or dict depends on what we want.
|
641
771
|
program.import_(new_table_module.path + f".{new_table}", True)
|
772
|
+
program.import_("sqlalchemy.orm.relationship", True)
|
773
|
+
|
642
774
|
# program.import_("typing.TYPE_CHECKING", True)
|
643
775
|
# program.import_area.if_(expr.ExprIdent("TYPE_CHECKING"))(
|
644
776
|
# lambda ast00: ast00.import_(
|
@@ -666,3 +798,13 @@ def make_python_relational_object_property_many_to_many(
|
|
666
798
|
),
|
667
799
|
),
|
668
800
|
)
|
801
|
+
|
802
|
+
|
803
|
+
def get_data_conversion(
|
804
|
+
source_pytype: str, target_pytype: str
|
805
|
+
) -> Callable[[expr.Expr], expr.Expr]:
|
806
|
+
if source_pytype == target_pytype:
|
807
|
+
return lambda x: x
|
808
|
+
if source_pytype == "str" and target_pytype == "bytes":
|
809
|
+
return lambda x: expr.ExprMethodCall(x, "encode", [])
|
810
|
+
raise NotImplementedError(f"Cannot convert {source_pytype} to {target_pytype}")
|