sera-2 1.26.3__tar.gz → 1.26.4__tar.gz

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.
Files changed (57) hide show
  1. {sera_2-1.26.3 → sera_2-1.26.4}/PKG-INFO +3 -3
  2. {sera_2-1.26.3 → sera_2-1.26.4}/pyproject.toml +1 -1
  3. sera_2-1.26.4/sera/make/make_typescript_model.py +465 -0
  4. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/ts_frontend/make_class_schema.py +0 -1
  5. sera_2-1.26.4/sera/make/ts_frontend/make_draft_model.py +811 -0
  6. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/ts_frontend/make_query.py +33 -15
  7. sera_2-1.26.3/sera/make/make_typescript_model.py +0 -1319
  8. {sera_2-1.26.3 → sera_2-1.26.4}/README.md +0 -0
  9. {sera_2-1.26.3 → sera_2-1.26.4}/sera/__init__.py +0 -0
  10. {sera_2-1.26.3 → sera_2-1.26.4}/sera/constants.py +0 -0
  11. {sera_2-1.26.3 → sera_2-1.26.4}/sera/exports/__init__.py +0 -0
  12. {sera_2-1.26.3 → sera_2-1.26.4}/sera/exports/schema.py +0 -0
  13. {sera_2-1.26.3 → sera_2-1.26.4}/sera/exports/test.py +0 -0
  14. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/__init__.py +0 -0
  15. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/api_helper.py +0 -0
  16. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/api_test_helper.py +0 -0
  17. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/base_orm.py +0 -0
  18. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/base_service.py +0 -0
  19. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/__init__.py +0 -0
  20. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_dcg.py +0 -0
  21. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_edge.py +0 -0
  22. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_flow.py +0 -0
  23. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_fn_signature.py +0 -0
  24. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_node.py +0 -0
  25. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_runtime.py +0 -0
  26. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/directed_computing_graph/_type_conversion.py +0 -0
  27. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/middlewares/__init__.py +0 -0
  28. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/middlewares/auth.py +0 -0
  29. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/middlewares/uscp.py +0 -0
  30. {sera_2-1.26.3 → sera_2-1.26.4}/sera/libs/search_helper.py +0 -0
  31. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/__init__.py +0 -0
  32. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/__main__.py +0 -0
  33. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/make_app.py +0 -0
  34. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/make_python_api.py +0 -0
  35. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/make_python_model.py +0 -0
  36. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/make_python_services.py +0 -0
  37. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/py_backend/__init__.py +0 -0
  38. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/py_backend/misc.py +0 -0
  39. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/ts_frontend/__init__.py +0 -0
  40. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/ts_frontend/make_enums.py +0 -0
  41. {sera_2-1.26.3 → sera_2-1.26.4}/sera/make/ts_frontend/misc.py +0 -0
  42. {sera_2-1.26.3 → sera_2-1.26.4}/sera/misc/__init__.py +0 -0
  43. {sera_2-1.26.3 → sera_2-1.26.4}/sera/misc/_formatter.py +0 -0
  44. {sera_2-1.26.3 → sera_2-1.26.4}/sera/misc/_utils.py +0 -0
  45. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/__init__.py +0 -0
  46. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_class.py +0 -0
  47. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_collection.py +0 -0
  48. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_constraints.py +0 -0
  49. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_datatype.py +0 -0
  50. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_default.py +0 -0
  51. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_enum.py +0 -0
  52. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_module.py +0 -0
  53. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_multi_lingual_string.py +0 -0
  54. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_parse.py +0 -0
  55. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_property.py +0 -0
  56. {sera_2-1.26.3 → sera_2-1.26.4}/sera/models/_schema.py +0 -0
  57. {sera_2-1.26.3 → sera_2-1.26.4}/sera/typing.py +0 -0
@@ -1,14 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: sera-2
3
- Version: 1.26.3
3
+ Version: 1.26.4
4
4
  Summary:
5
- Home-page: https://github.com/binh-vu/sera
6
5
  Author: Binh Vu
7
6
  Author-email: bvu687@gmail.com
8
7
  Requires-Python: >=3.12,<4.0
9
8
  Classifier: Programming Language :: Python :: 3
10
9
  Classifier: Programming Language :: Python :: 3.12
11
10
  Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
12
  Requires-Dist: black (==25.1.0)
13
13
  Requires-Dist: codegen-2 (>=2.15.1,<3.0.0)
14
14
  Requires-Dist: graph-wrapper (>=1.7.3,<2.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sera-2"
3
- version = "1.26.3"
3
+ version = "1.26.4"
4
4
  description = ""
5
5
  authors = ["Binh Vu <bvu687@gmail.com>"]
6
6
  readme = "README.md"
@@ -0,0 +1,465 @@
1
+ from __future__ import annotations
2
+
3
+ from codegen.models import PredefinedFn, Program, expr, stmt
4
+ from codegen.models.var import DeferredVar
5
+ from loguru import logger
6
+
7
+ from sera.make.ts_frontend.make_class_schema import make_class_schema
8
+ from sera.make.ts_frontend.make_draft_model import make_draft
9
+ from sera.make.ts_frontend.make_query import make_query
10
+ from sera.misc import assert_isinstance, assert_not_null, to_camel_case, to_snake_case
11
+ from sera.models import (
12
+ Class,
13
+ DataProperty,
14
+ Enum,
15
+ ObjectProperty,
16
+ Package,
17
+ Schema,
18
+ TsTypeWithDep,
19
+ )
20
+
21
+
22
+ def make_typescript_data_model(schema: Schema, target_pkg: Package):
23
+ """Generate TypeScript data model from the schema. The data model aligns with the public data model in Python, not the database model."""
24
+ app = target_pkg.app
25
+
26
+ # mapping from type alias of idprop to its real type
27
+ idprop_aliases = {}
28
+ for cls in schema.classes.values():
29
+ idprop = cls.get_id_property()
30
+ if idprop is not None:
31
+ idprop_aliases[f"{cls.name}Id"] = (
32
+ idprop.get_data_model_datatype().get_typescript_type()
33
+ )
34
+
35
+ def get_normal_deser_args(
36
+ prop: DataProperty | ObjectProperty,
37
+ ) -> expr.Expr:
38
+ """Extract the value from the data record from the server response to set to the class property in the client."""
39
+ handle_optional = lambda value: expr.ExprTernary(
40
+ expr.ExprNotEqual(value, expr.ExprConstant(None)),
41
+ value,
42
+ expr.ExprConstant("undefined"),
43
+ )
44
+
45
+ if isinstance(prop, DataProperty):
46
+ value = PredefinedFn.attr_getter(
47
+ expr.ExprIdent("data"), expr.ExprIdent(prop.name)
48
+ )
49
+ if prop.is_optional:
50
+ value = handle_optional(value)
51
+ value.true_expr = (
52
+ prop.datatype.get_typescript_type().get_json_deser_func(
53
+ value.true_expr
54
+ )
55
+ )
56
+ else:
57
+ value = prop.datatype.get_typescript_type().get_json_deser_func(value)
58
+
59
+ return value
60
+
61
+ assert isinstance(prop, ObjectProperty)
62
+ if prop.target.db is not None:
63
+ value = PredefinedFn.attr_getter(
64
+ expr.ExprIdent("data"), expr.ExprIdent(prop.name + "_id")
65
+ )
66
+ if prop.is_optional:
67
+ value = handle_optional(value)
68
+ return value
69
+ else:
70
+ if prop.cardinality.is_star_to_many():
71
+ # optional type for a list is simply an empty list, we don't need to check for None
72
+ value = PredefinedFn.map_list(
73
+ PredefinedFn.attr_getter(
74
+ expr.ExprIdent("data"),
75
+ expr.ExprIdent(prop.name),
76
+ ),
77
+ lambda item: expr.ExprMethodCall(
78
+ expr.ExprIdent(
79
+ assert_isinstance(prop, ObjectProperty).target.name
80
+ ),
81
+ "deser",
82
+ [item],
83
+ ),
84
+ )
85
+ return value
86
+ else:
87
+ value = expr.ExprFuncCall(
88
+ PredefinedFn.attr_getter(
89
+ expr.ExprIdent(prop.target.name),
90
+ expr.ExprIdent("deser"),
91
+ ),
92
+ [
93
+ PredefinedFn.attr_getter(
94
+ expr.ExprIdent("data"),
95
+ expr.ExprIdent(prop.name),
96
+ )
97
+ ],
98
+ )
99
+ if prop.is_optional:
100
+ value = handle_optional(value)
101
+ return value
102
+
103
+ def make_normal(cls: Class, pkg: Package):
104
+ """Make a data model for the normal Python data model"""
105
+ if not cls.is_public:
106
+ # skip classes that are not public
107
+ return
108
+
109
+ idprop = cls.get_id_property()
110
+ program = Program()
111
+ program.import_(
112
+ f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
113
+ )
114
+
115
+ prop_defs = []
116
+ prop_constructor_assigns = []
117
+ deser_args = []
118
+
119
+ for prop in cls.properties.values():
120
+ if prop.data.is_private:
121
+ # skip private fields as this is for APIs exchange
122
+ continue
123
+
124
+ propname = to_camel_case(prop.name)
125
+
126
+ if isinstance(prop, DataProperty):
127
+ tstype = prop.get_data_model_datatype().get_typescript_type()
128
+ for dep in tstype.deps:
129
+ program.import_(dep, True)
130
+
131
+ if idprop is not None and prop.name == idprop.name:
132
+ # use id type alias
133
+ tstype = TsTypeWithDep(
134
+ type=f"{cls.name}Id", spectype=tstype.spectype
135
+ )
136
+
137
+ if prop.is_optional:
138
+ # convert type to optional
139
+ tstype = tstype.as_optional_type()
140
+
141
+ deser_args.append(
142
+ (
143
+ expr.ExprIdent(propname),
144
+ get_normal_deser_args(prop),
145
+ )
146
+ )
147
+ else:
148
+ assert isinstance(prop, ObjectProperty)
149
+ if prop.target.db is not None:
150
+ # this class is stored in the database, we store the id instead
151
+ propname = propname + "Id"
152
+ tstype = TsTypeWithDep(
153
+ type=f"{prop.target.name}Id",
154
+ spectype=assert_not_null(prop.target.get_id_property())
155
+ .get_data_model_datatype()
156
+ .get_typescript_type()
157
+ .spectype,
158
+ deps=(
159
+ [
160
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
161
+ ]
162
+ if prop.target.name != cls.name
163
+ else []
164
+ ),
165
+ )
166
+ if prop.cardinality.is_star_to_many():
167
+ tstype = tstype.as_list_type()
168
+ elif prop.is_optional:
169
+ # convert type to optional only if it isn't a list
170
+ tstype = tstype.as_optional_type()
171
+ deser_args.append(
172
+ (
173
+ expr.ExprIdent(propname),
174
+ get_normal_deser_args(prop),
175
+ )
176
+ )
177
+ else:
178
+ # we are going to store the whole object
179
+ tstype = TsTypeWithDep(
180
+ type=prop.target.name,
181
+ spectype=prop.target.name,
182
+ deps=[
183
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
184
+ ],
185
+ )
186
+ if prop.cardinality.is_star_to_many():
187
+ tstype = tstype.as_list_type()
188
+ deser_args.append(
189
+ (
190
+ expr.ExprIdent(propname),
191
+ get_normal_deser_args(prop),
192
+ )
193
+ )
194
+ else:
195
+ if prop.is_optional:
196
+ # convert type to optional only if it isn't a list
197
+ tstype = tstype.as_optional_type()
198
+ deser_args.append(
199
+ (
200
+ expr.ExprIdent(propname),
201
+ get_normal_deser_args(prop),
202
+ )
203
+ )
204
+
205
+ for dep in tstype.deps:
206
+ program.import_(
207
+ dep,
208
+ True,
209
+ )
210
+
211
+ prop_defs.append(stmt.DefClassVarStatement(propname, tstype.type))
212
+ prop_constructor_assigns.append(
213
+ stmt.AssignStatement(
214
+ PredefinedFn.attr_getter(
215
+ expr.ExprIdent("this"),
216
+ expr.ExprIdent(propname),
217
+ ),
218
+ expr.ExprIdent("args." + propname),
219
+ )
220
+ )
221
+
222
+ program.root(
223
+ stmt.LineBreak(),
224
+ (
225
+ stmt.TypescriptStatement(
226
+ f"export type {cls.name}Id = {idprop.get_data_model_datatype().get_typescript_type().type};"
227
+ )
228
+ if idprop is not None
229
+ else None
230
+ ),
231
+ stmt.LineBreak(),
232
+ lambda ast00: ast00.class_like(
233
+ "interface",
234
+ cls.name + "ConstructorArgs",
235
+ )(*prop_defs),
236
+ stmt.LineBreak(),
237
+ lambda ast10: ast10.class_(cls.name)(
238
+ *prop_defs,
239
+ stmt.LineBreak(),
240
+ lambda ast11: ast11.func(
241
+ "constructor",
242
+ [
243
+ DeferredVar.simple(
244
+ "args", expr.ExprIdent(cls.name + "ConstructorArgs")
245
+ ),
246
+ ],
247
+ )(*prop_constructor_assigns),
248
+ stmt.LineBreak(),
249
+ lambda ast12: ast12.func(
250
+ "className",
251
+ [],
252
+ expr.ExprIdent("string"),
253
+ is_static=True,
254
+ modifiers=["get"],
255
+ comment="Name of the class in the Schema",
256
+ )(
257
+ stmt.ReturnStatement(expr.ExprConstant(cls.name)),
258
+ ),
259
+ stmt.LineBreak(),
260
+ lambda ast12: ast12.func(
261
+ "deser",
262
+ [
263
+ DeferredVar.simple("data", expr.ExprIdent("any")),
264
+ ],
265
+ expr.ExprIdent(cls.name),
266
+ is_static=True,
267
+ comment="Deserialize the data from the server to create a new instance of the class",
268
+ )(
269
+ lambda ast: ast.return_(
270
+ expr.ExprNewInstance(
271
+ expr.ExprIdent(cls.name), [PredefinedFn.dict(deser_args)]
272
+ )
273
+ )
274
+ ),
275
+ stmt.LineBreak(),
276
+ lambda ast13: ast13.func(
277
+ "toDraft",
278
+ [],
279
+ expr.ExprIdent(f"Draft{cls.name}"),
280
+ comment="Convert the class instance to a draft for editing",
281
+ )(
282
+ stmt.ReturnStatement(
283
+ expr.ExprMethodCall(
284
+ expr.ExprIdent(f"Draft{cls.name}"),
285
+ "update",
286
+ [expr.ExprIdent("this")],
287
+ )
288
+ ),
289
+ ),
290
+ ),
291
+ )
292
+
293
+ pkg.module(cls.name).write(program)
294
+
295
+ def make_table(cls: Class, pkg: Package):
296
+ if not cls.is_public or cls.db is None:
297
+ # skip classes that are not public and not stored in the database
298
+ return
299
+
300
+ outmod = pkg.module(cls.name + "Table")
301
+ if outmod.exists():
302
+ # skip if the module already exists
303
+ logger.info(f"Module {outmod.path} already exists, skip")
304
+ return
305
+
306
+ program = Program()
307
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
308
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
309
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}Query.query", True)
310
+ program.import_(
311
+ f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
312
+ )
313
+ program.import_("sera-db.Table", True)
314
+ program.import_("sera-db.DB", True)
315
+
316
+ program.root(
317
+ stmt.LineBreak(),
318
+ lambda ast00: ast00.class_(
319
+ f"{cls.name}Table",
320
+ [expr.ExprIdent(f"Table<{cls.name}Id, {cls.name}, Draft{cls.name}>")],
321
+ )(
322
+ lambda ast01: ast01.func(
323
+ "constructor",
324
+ [
325
+ DeferredVar.simple(
326
+ "db",
327
+ expr.ExprIdent("DB"),
328
+ )
329
+ ],
330
+ )(
331
+ stmt.SingleExprStatement(
332
+ expr.ExprFuncCall(
333
+ expr.ExprIdent("super"),
334
+ [
335
+ PredefinedFn.dict(
336
+ [
337
+ (
338
+ expr.ExprIdent("cls"),
339
+ expr.ExprIdent(cls.name),
340
+ ),
341
+ (
342
+ expr.ExprIdent("remoteURL"),
343
+ expr.ExprConstant(
344
+ f"/api/{to_snake_case(cls.name).replace('_', '-')}"
345
+ ),
346
+ ),
347
+ (
348
+ expr.ExprIdent("db"),
349
+ expr.ExprIdent("db"),
350
+ ),
351
+ (
352
+ expr.ExprIdent("queryProcessor"),
353
+ expr.ExprIdent("query"),
354
+ ),
355
+ ]
356
+ )
357
+ ],
358
+ )
359
+ )
360
+ ),
361
+ ),
362
+ )
363
+
364
+ outmod.write(program)
365
+
366
+ def make_index(pkg: Package):
367
+ outmod = pkg.module("index")
368
+ if outmod.exists():
369
+ # skip if the module already exists
370
+ logger.info(f"Module {outmod.path} already exists, skip")
371
+ return
372
+
373
+ export_types = []
374
+ export_iso_types = [] # isolatedModules required separate export type clause
375
+
376
+ program = Program()
377
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
378
+ export_types.append(cls.name)
379
+ if cls.db is not None:
380
+ # only import the id if this class is stored in the database
381
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
382
+ export_iso_types.append(f"{cls.name}Id")
383
+
384
+ program.import_(
385
+ f"@.models.{pkg.dir.name}.{cls.name}Schema.{cls.name}Schema", True
386
+ )
387
+ program.import_(
388
+ f"@.models.{pkg.dir.name}.{cls.name}Query.{cls.name}Query", True
389
+ )
390
+ export_types.append(f"{cls.name}Schema")
391
+ export_iso_types.append(f"{cls.name}Query")
392
+ program.import_(
393
+ f"@.models.{pkg.dir.name}.{cls.name}Schema.{cls.name}SchemaType", True
394
+ )
395
+ export_iso_types.append(f"{cls.name}SchemaType")
396
+
397
+ program.import_(
398
+ f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
399
+ )
400
+ export_types.append(f"Draft{cls.name}")
401
+ if cls.db is not None:
402
+ program.import_(
403
+ f"@.models.{pkg.dir.name}.{cls.name}Table.{cls.name}Table", True
404
+ )
405
+ export_types.append(f"{cls.name}Table")
406
+
407
+ program.root(
408
+ stmt.LineBreak(),
409
+ stmt.TypescriptStatement("export { %s };" % (", ".join(export_types))),
410
+ (
411
+ stmt.TypescriptStatement(
412
+ "export type { %s };" % (", ".join(export_iso_types))
413
+ )
414
+ ),
415
+ )
416
+
417
+ outmod.write(program)
418
+
419
+ for cls in schema.topological_sort():
420
+ pkg = target_pkg.pkg(cls.get_tsmodule_name())
421
+ make_normal(cls, pkg)
422
+ make_draft(schema, cls, pkg, idprop_aliases)
423
+ make_query(schema, cls, pkg)
424
+ make_table(cls, pkg)
425
+ make_class_schema(schema, cls, pkg)
426
+
427
+ make_index(pkg)
428
+
429
+
430
+ def make_typescript_enum(schema: Schema, target_pkg: Package):
431
+ """Make typescript enum for the schema"""
432
+ enum_pkg = target_pkg.pkg("enums")
433
+
434
+ def make_enum(enum: Enum, pkg: Package):
435
+ program = Program()
436
+ program.root(
437
+ stmt.LineBreak(),
438
+ lambda ast: ast.class_like("enum", enum.name)(
439
+ *[
440
+ stmt.DefEnumValueStatement(
441
+ name=value.name,
442
+ value=expr.ExprConstant(value.value),
443
+ )
444
+ for value in enum.values.values()
445
+ ]
446
+ ),
447
+ )
448
+ pkg.module(enum.get_tsmodule_name()).write(program)
449
+
450
+ for enum in schema.enums.values():
451
+ make_enum(enum, enum_pkg)
452
+
453
+ program = Program()
454
+ for enum in schema.enums.values():
455
+ program.import_(f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}", True)
456
+
457
+ program.root(
458
+ stmt.LineBreak(),
459
+ stmt.TypescriptStatement(
460
+ "export { "
461
+ + ", ".join([enum.name for enum in schema.enums.values()])
462
+ + "};"
463
+ ),
464
+ )
465
+ enum_pkg.module("index").write(program)
@@ -13,7 +13,6 @@ from sera.models import (
13
13
  Schema,
14
14
  TsTypeWithDep,
15
15
  )
16
- from sera.typing import is_set
17
16
 
18
17
 
19
18
  def make_class_schema(schema: Schema, cls: Class, pkg: Package):