sera-2 1.2.1__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.
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from operator import is_
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(schema: Schema, target_pkg: Package):
24
- """Generate public classes for the API from the schema."""
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
- return PredefinedFn.map_list(
42
+ value = PredefinedFn.map_list(
34
43
  value,
35
- lambda item: expr.ExprFuncCall(
36
- PredefinedFn.attr_getter(
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
- return expr.ExprFuncCall(
44
- PredefinedFn.attr_getter(
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
- isinstance(prop, ObjectProperty)
61
- and prop.target.db is not None
62
- and prop.cardinality == Cardinality.MANY_TO_MANY
63
- ):
64
- # we have to use the associated object
65
- AssociationTable = f"{cls.name}{prop.target.name}"
66
- program.import_(
67
- app.models.db.path
68
- + f".{to_snake_case(AssociationTable)}.{AssociationTable}",
69
- True,
70
- )
71
- return PredefinedFn.map_list(
72
- value,
73
- lambda item: expr.ExprFuncCall(
74
- expr.ExprIdent(AssociationTable),
75
- [PredefinedFn.keyword_assignment(f"{prop.name}_id", item)],
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 make_create(program: Program, cls: Class):
122
+ def make_upsert(program: Program, cls: Class):
81
123
  program.import_("__future__.annotations", True)
82
124
  program.import_("msgspec", False)
83
- program.import_(
84
- app.models.db.path + f".{cls.get_pymodule_name()}.{cls.name}",
85
- True,
86
- alias=f"{cls.name}DB",
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.datatype.get_python_type()
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
- .datatype.get_python_type()
108
- .type
151
+ .get_data_model_datatype()
152
+ .get_python_type()
109
153
  )
110
154
  else:
111
- program.import_(
112
- f"{target_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}",
113
- is_import_attr=True,
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
- pytype = prop.target.name
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 = f"list[{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(f"{cls.name}DB"),
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(f"{cls.name}DB"),
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 make_update(program: Program, cls: Class):
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
- program.import_(
155
- app.models.db.path + f".{cls.get_pymodule_name()}.{cls.name}",
156
- True,
157
- alias=f"{cls.name}DB",
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.datatype.get_python_type()
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
- program.import_(
173
- f"{target_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}",
174
- is_import_attr=True,
175
- )
176
- if prop.cardinality.is_star_to_many():
177
- pytype = f"list[{prop.target.name}]"
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 = prop.target.name
180
- cls_ast(stmt.DefClassVarStatement(prop.name, pytype))
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
- stmt.PythonDecoratorStatement(
185
- expr.ExprFuncCall(expr.ExprIdent("classmethod"), [])
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: ast00.func(
188
- "from_db",
189
- [
190
- DeferredVar.simple("cls"),
191
- DeferredVar.simple("record", expr.ExprIdent(f"{cls.name}DB")),
192
- ],
193
- )(
194
- lambda ast10: ast10.return_(
195
- expr.ExprFuncCall(
196
- expr.ExprIdent("cls"),
197
- [
198
- from_db_type_conversion(expr.ExprIdent("record"), prop)
199
- for prop in cls.properties.values()
200
- if not prop.is_private
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
- make_create(program, cls)
281
+ make_upsert(program, cls)
210
282
  program.root.linebreak()
211
- make_update(program, cls)
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, target_pkg: Package, target_data_pkg: Package
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.baseorm.DictDataClassType", True)
336
+ program.import_("sera.libs.base_orm.DictDataclassType", True)
250
337
  type = f"Mapping[str, {custom_type.target.name}]"
251
- maptype = f"DictDataClassType({custom_type.target.name})"
338
+ maptype = f"DictDataclassType({custom_type.target.name})"
252
339
  else:
253
340
  program.import_("typing.Sequence", True)
254
- program.import_("sera.libs.baseorm.ListDataClassType", True)
341
+ program.import_("sera.libs.base_orm.ListDataclassType", True)
255
342
  type = f"Sequence[str, {custom_type.target.name}]"
256
- maptype = f"ListDataClassType({custom_type.target.name})"
343
+ maptype = f"ListDataclassType({custom_type.target.name})"
257
344
  else:
258
- program.import_("sera.libs.baseorm.DataClassType", True)
345
+ program.import_("sera.libs.base_orm.DataclassType", True)
259
346
  type = custom_type.target.name
260
- maptype = f"DataClassType({custom_type.target.name})"
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
- pytype = prop.datatype.get_sqlalchemy_type()
338
- if pytype.dep is not None:
339
- program.import_(pytype.dep, True)
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[{pytype.type}]"
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.orm.relationship", True)
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
- if idprop_pytype.dep is not None:
415
- program.import_(idprop_pytype.dep, True)
551
+ for dep in idprop_pytype.deps:
552
+ program.import_(dep, True)
416
553
 
417
- proptype = f"Mapped[{idprop_pytype.type}]"
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"{target_pkg.module(prop.target.get_pymodule_name()).path}.{prop.target.name}",
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}")