sera-2 1.23.0__tar.gz → 1.24.1__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 (52) hide show
  1. {sera_2-1.23.0 → sera_2-1.24.1}/PKG-INFO +1 -1
  2. {sera_2-1.23.0 → sera_2-1.24.1}/pyproject.toml +1 -1
  3. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/make_app.py +4 -5
  4. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/make_typescript_model.py +8 -347
  5. sera_2-1.24.1/sera/make/ts_frontend/__init__.py +0 -0
  6. sera_2-1.24.1/sera/make/ts_frontend/make_class_schema.py +369 -0
  7. sera_2-1.24.1/sera/make/ts_frontend/make_enums.py +104 -0
  8. sera_2-1.24.1/sera/make/ts_frontend/misc.py +38 -0
  9. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_enum.py +2 -1
  10. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_parse.py +5 -1
  11. {sera_2-1.23.0 → sera_2-1.24.1}/README.md +0 -0
  12. {sera_2-1.23.0 → sera_2-1.24.1}/sera/__init__.py +0 -0
  13. {sera_2-1.23.0 → sera_2-1.24.1}/sera/constants.py +0 -0
  14. {sera_2-1.23.0 → sera_2-1.24.1}/sera/exports/__init__.py +0 -0
  15. {sera_2-1.23.0 → sera_2-1.24.1}/sera/exports/schema.py +0 -0
  16. {sera_2-1.23.0 → sera_2-1.24.1}/sera/exports/test.py +0 -0
  17. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/__init__.py +0 -0
  18. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/api_helper.py +0 -0
  19. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/api_test_helper.py +0 -0
  20. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/base_orm.py +0 -0
  21. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/base_service.py +0 -0
  22. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/__init__.py +0 -0
  23. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_dcg.py +0 -0
  24. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_edge.py +0 -0
  25. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_flow.py +0 -0
  26. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_fn_signature.py +0 -0
  27. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_node.py +0 -0
  28. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_runtime.py +0 -0
  29. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/directed_computing_graph/_type_conversion.py +0 -0
  30. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/middlewares/__init__.py +0 -0
  31. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/middlewares/auth.py +0 -0
  32. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/middlewares/uscp.py +0 -0
  33. {sera_2-1.23.0 → sera_2-1.24.1}/sera/libs/search_helper.py +0 -0
  34. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/__init__.py +0 -0
  35. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/__main__.py +0 -0
  36. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/make_python_api.py +0 -0
  37. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/make_python_model.py +0 -0
  38. {sera_2-1.23.0 → sera_2-1.24.1}/sera/make/make_python_services.py +0 -0
  39. {sera_2-1.23.0 → sera_2-1.24.1}/sera/misc/__init__.py +0 -0
  40. {sera_2-1.23.0 → sera_2-1.24.1}/sera/misc/_formatter.py +0 -0
  41. {sera_2-1.23.0 → sera_2-1.24.1}/sera/misc/_utils.py +0 -0
  42. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/__init__.py +0 -0
  43. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_class.py +0 -0
  44. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_collection.py +0 -0
  45. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_constraints.py +0 -0
  46. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_datatype.py +0 -0
  47. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_default.py +0 -0
  48. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_module.py +0 -0
  49. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_multi_lingual_string.py +0 -0
  50. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_property.py +0 -0
  51. {sera_2-1.23.0 → sera_2-1.24.1}/sera/models/_schema.py +0 -0
  52. {sera_2-1.23.0 → sera_2-1.24.1}/sera/typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.23.0
3
+ Version: 1.24.1
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sera-2"
3
- version = "1.23.0"
3
+ version = "1.24.1"
4
4
  description = ""
5
5
  authors = ["Binh Vu <bvu687@gmail.com>"]
6
6
  readme = "README.md"
@@ -7,6 +7,7 @@ from typing import Annotated
7
7
 
8
8
  from codegen.models import DeferredVar, PredefinedFn, Program, expr, stmt
9
9
  from loguru import logger
10
+
10
11
  from sera.make.make_python_api import make_python_api
11
12
  from sera.make.make_python_model import (
12
13
  make_python_data_model,
@@ -14,10 +15,8 @@ from sera.make.make_python_model import (
14
15
  make_python_relational_model,
15
16
  )
16
17
  from sera.make.make_python_services import make_python_service_structure
17
- from sera.make.make_typescript_model import (
18
- make_typescript_data_model,
19
- make_typescript_enum,
20
- )
18
+ from sera.make.make_typescript_model import make_typescript_data_model
19
+ from sera.make.ts_frontend.make_enums import make_typescript_enums
21
20
  from sera.misc import Formatter
22
21
  from sera.models import App, DataCollection, parse_schema
23
22
  from sera.typing import Language
@@ -170,7 +169,7 @@ def make_app(
170
169
  # generate services
171
170
  make_python_service_structure(app, collections)
172
171
  elif language == Language.Typescript:
173
- make_typescript_enum(schema, app.models)
172
+ make_typescript_enums(schema, app.models)
174
173
  make_typescript_data_model(schema, app.models)
175
174
 
176
175
  Formatter.get_instance().process()
@@ -1,12 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from typing import Any, Callable, Optional
4
+ from typing import Any, Callable
5
5
 
6
6
  from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
7
7
  from codegen.models.var import DeferredVar
8
8
  from loguru import logger
9
9
 
10
+ from sera.make.ts_frontend.make_class_schema import make_class_schema
11
+ from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
10
12
  from sera.misc import (
11
13
  assert_isinstance,
12
14
  assert_not_null,
@@ -26,13 +28,6 @@ from sera.models import (
26
28
  )
27
29
  from sera.typing import is_set
28
30
 
29
- TS_GLOBAL_IDENTS = {
30
- "normalizers.normalizeNumber": "sera-db.normalizers",
31
- "normalizers.normalizeOptionalNumber": "sera-db.normalizers",
32
- "normalizers.normalizeDate": "sera-db.normalizers",
33
- "normalizers.normalizeOptionalDate": "sera-db.normalizers",
34
- }
35
-
36
31
 
37
32
  def make_typescript_data_model(schema: Schema, target_pkg: Package):
38
33
  """Generate TypeScript data model from the schema. The data model aligns with the public data model in Python, not the database model."""
@@ -440,13 +435,12 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
440
435
  if tstype.type in idprop_aliases:
441
436
  create_propvalue = idprop_aliases[tstype.type].get_default()
442
437
  elif tstype.type in schema.enums:
443
- enum_value = next(
438
+ enum_value_name = next(
444
439
  iter(schema.enums[tstype.type].values.values())
445
- ).value
446
- # TODO: handle enum value integer
447
- assert isinstance(enum_value, str)
440
+ ).name
441
+ assert isinstance(enum_value_name, str), enum_value_name
448
442
  create_propvalue = expr.ExprIdent(
449
- tstype.type + "." + enum_value
443
+ tstype.type + "." + enum_value_name
450
444
  )
451
445
  else:
452
446
  create_propvalue = tstype.get_default()
@@ -1194,323 +1188,6 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1194
1188
 
1195
1189
  outmod.write(program)
1196
1190
 
1197
- def make_definition(cls: Class, pkg: Package):
1198
- """Make schema definition for the class in frontend so that we can generate components"""
1199
- if not cls.is_public:
1200
- # skip classes that are not public
1201
- return
1202
-
1203
- program = Program()
1204
- prop_defs: list[tuple[DataProperty | ObjectProperty, expr.Expr, expr.Expr]] = []
1205
- prop_normalizers: list[tuple[expr.Expr, expr.Expr]] = []
1206
-
1207
- import_helper = ImportHelper(program, TS_GLOBAL_IDENTS)
1208
-
1209
- for prop in cls.properties.values():
1210
- # we must include private properties that are needed during upsert for our forms.
1211
- # if prop.data.is_private:
1212
- # # skip private fields as this is for APIs exchange
1213
- # continue
1214
- tspropname = to_camel_case(prop.name)
1215
- pypropname = prop.name
1216
- if isinstance(prop, ObjectProperty) and prop.target.db is not None:
1217
- # this is a database object, we append id to the property name
1218
- tspropname = tspropname + "Id"
1219
- pypropname = prop.name + "_id"
1220
-
1221
- tsprop = {}
1222
-
1223
- if isinstance(prop, DataProperty):
1224
- tstype = prop.get_data_model_datatype().get_typescript_type()
1225
- # for schema definition, we need to use the original type, not the type alias
1226
- # if prop.name == idprop.name:
1227
- # # use id type alias
1228
- # tstype = TsTypeWithDep(f"{cls.name}Id")
1229
- for dep in tstype.deps:
1230
- program.import_(dep, True)
1231
- tsprop = [
1232
- (
1233
- expr.ExprIdent("datatype"),
1234
- (
1235
- expr.ExprConstant(tstype.spectype)
1236
- if tstype.type not in schema.enums
1237
- else expr.ExprConstant("enum")
1238
- ),
1239
- ),
1240
- *(
1241
- [(expr.ExprIdent("enumType"), expr.ExprIdent(tstype.type))]
1242
- if tstype.type in schema.enums
1243
- else []
1244
- ),
1245
- *(
1246
- [
1247
- (
1248
- expr.ExprIdent("foreignKeyTarget"),
1249
- expr.ExprConstant(prop.db.foreign_key.name),
1250
- )
1251
- ]
1252
- if prop.db is not None
1253
- and prop.db.is_primary_key
1254
- and prop.db.foreign_key is not None
1255
- else []
1256
- ),
1257
- (
1258
- expr.ExprIdent("isRequired"),
1259
- expr.ExprConstant(
1260
- not prop.is_optional
1261
- and prop.default_value is None
1262
- and prop.default_factory is None
1263
- ),
1264
- ),
1265
- ]
1266
-
1267
- norm_func = get_normalizer(tstype, import_helper)
1268
- if norm_func is not None:
1269
- # we have a normalizer for this type
1270
- prop_normalizers.append((expr.ExprIdent(tspropname), norm_func))
1271
- else:
1272
- assert isinstance(prop, ObjectProperty)
1273
- if prop.target.db is not None:
1274
- # this class is stored in the database, we store the id instead
1275
- tstype = (
1276
- assert_not_null(prop.target.get_id_property())
1277
- .get_data_model_datatype()
1278
- .get_typescript_type()
1279
- )
1280
- else:
1281
- # we are going to store the whole object
1282
- tstype = TsTypeWithDep(
1283
- type=prop.target.name,
1284
- spectype=prop.target.name,
1285
- deps=[
1286
- f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
1287
- ],
1288
- )
1289
-
1290
- # we don't store the type itself, but just the name of the type
1291
- # so not need to import the dependency
1292
- # if tstype.dep is not None:
1293
- # program.import_(
1294
- # tstype.dep,
1295
- # True,
1296
- # )
1297
-
1298
- tsprop = [
1299
- (
1300
- expr.ExprIdent("targetClass"),
1301
- expr.ExprConstant(prop.target.name),
1302
- ),
1303
- (
1304
- expr.ExprIdent("datatype"),
1305
- expr.ExprConstant(
1306
- tstype.spectype
1307
- if prop.target.db is not None
1308
- else "undefined"
1309
- ),
1310
- ),
1311
- (
1312
- expr.ExprIdent("cardinality"),
1313
- expr.ExprConstant(prop.cardinality.value),
1314
- ),
1315
- (
1316
- expr.ExprIdent("isEmbedded"),
1317
- expr.ExprConstant(prop.target.db is None),
1318
- ),
1319
- (
1320
- expr.ExprIdent("isRequired"),
1321
- expr.ExprConstant(not prop.is_optional),
1322
- ),
1323
- ]
1324
-
1325
- prop_defs.append(
1326
- (
1327
- prop,
1328
- expr.ExprIdent(tspropname),
1329
- PredefinedFn.dict(
1330
- [
1331
- (expr.ExprIdent("name"), expr.ExprConstant(pypropname)),
1332
- (expr.ExprIdent("tsName"), expr.ExprConstant(tspropname)),
1333
- (
1334
- expr.ExprIdent("updateFuncName"),
1335
- expr.ExprConstant(f"update{to_pascal_case(prop.name)}"),
1336
- ),
1337
- (
1338
- expr.ExprIdent("label"),
1339
- expr.ExprConstant(prop.label.to_dict()),
1340
- ),
1341
- (
1342
- expr.ExprIdent("description"),
1343
- (
1344
- expr.ExprConstant(prop.description.to_dict())
1345
- if not prop.description.is_empty()
1346
- else expr.ExprConstant("undefined")
1347
- ),
1348
- ),
1349
- (
1350
- expr.ExprIdent("constraints"),
1351
- PredefinedFn.list(
1352
- [
1353
- expr.ExprConstant(
1354
- constraint.get_typescript_constraint()
1355
- )
1356
- for constraint in prop.data.constraints
1357
- ]
1358
- ),
1359
- ),
1360
- ]
1361
- + tsprop
1362
- ),
1363
- )
1364
- )
1365
-
1366
- for type in ["ObjectProperty", "DataProperty"]:
1367
- program.import_(f"sera-db.{type}", True)
1368
- if cls.db is not None:
1369
- program.import_(f"sera-db.Schema", True)
1370
- else:
1371
- program.import_(f"sera-db.EmbeddedSchema", True)
1372
-
1373
- program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
1374
- program.import_(
1375
- f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
1376
- )
1377
- program.import_(
1378
- f"@.models.{pkg.dir.name}.Draft{cls.name}.draft{cls.name}Validators", True
1379
- )
1380
- if cls.db is not None:
1381
- program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
1382
-
1383
- program.root(
1384
- stmt.LineBreak(),
1385
- stmt.TypescriptStatement(
1386
- f"export type {cls.name}SchemaType = "
1387
- + PredefinedFn.dict(
1388
- (
1389
- [
1390
- (expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
1391
- ]
1392
- if cls.db is not None
1393
- else []
1394
- )
1395
- + [
1396
- (
1397
- expr.ExprIdent("publicProperties"),
1398
- expr.ExprIdent(
1399
- " | ".join(
1400
- [
1401
- expr.ExprConstant(
1402
- to_camel_case(prop.name) + "Id"
1403
- if isinstance(prop, ObjectProperty)
1404
- and prop.target.db is not None
1405
- else to_camel_case(prop.name)
1406
- ).to_typescript()
1407
- for prop in cls.properties.values()
1408
- if not prop.data.is_private
1409
- ]
1410
- )
1411
- ),
1412
- ),
1413
- (
1414
- expr.ExprIdent("allProperties"),
1415
- expr.ExprIdent(
1416
- f"{cls.name}SchemaType['publicProperties']"
1417
- + (
1418
- " | "
1419
- + " | ".join(
1420
- [
1421
- expr.ExprConstant(
1422
- to_camel_case(prop.name)
1423
- ).to_typescript()
1424
- for prop in cls.properties.values()
1425
- if prop.data.is_private
1426
- ]
1427
- )
1428
- if any(
1429
- prop.data.is_private
1430
- for prop in cls.properties.values()
1431
- )
1432
- else ""
1433
- )
1434
- ),
1435
- ),
1436
- (
1437
- expr.ExprIdent("cls"),
1438
- expr.ExprIdent(cls.name),
1439
- ),
1440
- (
1441
- expr.ExprIdent("draftCls"),
1442
- expr.ExprIdent(f"Draft{cls.name}"),
1443
- ),
1444
- ]
1445
- ).to_typescript()
1446
- + ";",
1447
- ),
1448
- stmt.LineBreak(),
1449
- stmt.TypescriptStatement(
1450
- f"const publicProperties: Record<{cls.name}SchemaType['publicProperties'], DataProperty | ObjectProperty> = "
1451
- + PredefinedFn.dict(
1452
- [
1453
- (prop_name, prop_def)
1454
- for prop, prop_name, prop_def in prop_defs
1455
- if not prop.data.is_private
1456
- ]
1457
- ).to_typescript()
1458
- + ";"
1459
- ),
1460
- stmt.LineBreak(),
1461
- stmt.TypescriptStatement(
1462
- (
1463
- f"export const {cls.name}Schema: Schema<{cls.name}SchemaType['id'], {cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties'], {cls.name}SchemaType> = "
1464
- if cls.db is not None
1465
- else f"export const {cls.name}Schema: EmbeddedSchema<{cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties']> = "
1466
- )
1467
- + PredefinedFn.dict(
1468
- [
1469
- (
1470
- expr.ExprIdent("publicProperties"),
1471
- expr.ExprIdent("publicProperties"),
1472
- ),
1473
- (
1474
- expr.ExprIdent("allProperties"),
1475
- expr.ExprIdent(
1476
- "{ ...publicProperties, "
1477
- + ", ".join(
1478
- [
1479
- f"{prop_name.to_typescript()}: {prop_def.to_typescript()}"
1480
- for prop, prop_name, prop_def in prop_defs
1481
- if prop.data.is_private
1482
- ]
1483
- )
1484
- + "}"
1485
- ),
1486
- ),
1487
- (
1488
- expr.ExprIdent("validators"),
1489
- expr.ExprIdent(f"draft{cls.name}Validators"),
1490
- ),
1491
- (
1492
- expr.ExprIdent("normalizers"),
1493
- PredefinedFn.dict(prop_normalizers),
1494
- ),
1495
- ]
1496
- + (
1497
- [
1498
- (
1499
- expr.ExprIdent("primaryKey"),
1500
- expr.ExprConstant(
1501
- assert_not_null(cls.get_id_property()).name
1502
- ),
1503
- )
1504
- ]
1505
- if cls.db is not None
1506
- else []
1507
- )
1508
- ).to_typescript()
1509
- + ";"
1510
- ),
1511
- )
1512
- pkg.module(cls.name + "Schema").write(program)
1513
-
1514
1191
  def make_index(pkg: Package):
1515
1192
  outmod = pkg.module("index")
1516
1193
  if outmod.exists():
@@ -1566,7 +1243,7 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1566
1243
  make_draft(cls, pkg)
1567
1244
  make_query_processor(cls, pkg)
1568
1245
  make_table(cls, pkg)
1569
- make_definition(cls, pkg)
1246
+ make_class_schema(schema, cls, pkg)
1570
1247
 
1571
1248
  make_index(pkg)
1572
1249
 
@@ -1661,22 +1338,6 @@ def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
1661
1338
  raise NotImplementedError(tstype.type)
1662
1339
 
1663
1340
 
1664
- def get_normalizer(
1665
- tstype: TsTypeWithDep, import_helper: ImportHelper
1666
- ) -> Optional[expr.ExprIdent]:
1667
- if tstype.type == "number":
1668
- return import_helper.use("normalizers.normalizeNumber")
1669
- if tstype.type == "number | undefined":
1670
- return import_helper.use("normalizers.normalizeOptionalNumber")
1671
- if tstype.type == "Date":
1672
- return import_helper.use("normalizers.normalizeDate")
1673
- if tstype.type == "Date | undefined":
1674
- return import_helper.use("normalizers.normalizeOptionalDate")
1675
-
1676
- assert "number" not in tstype.type, tstype.type
1677
- return None
1678
-
1679
-
1680
1341
  def get_norm_func(
1681
1342
  tstype: TsTypeWithDep, import_helper: ImportHelper
1682
1343
  ) -> Callable[[expr.Expr], expr.Expr]:
File without changes
@@ -0,0 +1,369 @@
1
+ from __future__ import annotations
2
+
3
+ from codegen.models import ImportHelper, PredefinedFn, Program, expr, stmt
4
+
5
+ from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
6
+ from sera.misc import assert_not_null, to_camel_case, to_pascal_case
7
+ from sera.models import (
8
+ Class,
9
+ DataProperty,
10
+ Enum,
11
+ ObjectProperty,
12
+ Package,
13
+ Schema,
14
+ TsTypeWithDep,
15
+ )
16
+ from sera.typing import is_set
17
+
18
+
19
+ def make_class_schema(schema: Schema, cls: Class, pkg: Package):
20
+ """Make schema definition for the class in frontend so that components can use this information select
21
+ appropriate components to display or edit the data.
22
+
23
+ Args:
24
+ schema: The overall schema of the application, which contains all classes & enums
25
+ cls: The class that we want to generate the schema
26
+ pkg: The output package (directory) for the class in the `@.models` package. For example, if the
27
+ class is `User`, then the package would be `src/models/user`.
28
+
29
+ Returns:
30
+ This function do not return anything as it writes the schema directly to a file.
31
+ """
32
+ if not cls.is_public:
33
+ # skip classes that are not public
34
+ return
35
+
36
+ program = Program()
37
+ prop_defs: list[tuple[DataProperty | ObjectProperty, expr.Expr, expr.Expr]] = []
38
+ prop_normalizers: list[tuple[expr.Expr, expr.Expr]] = []
39
+
40
+ import_helper = ImportHelper(program, TS_GLOBAL_IDENTS)
41
+
42
+ for prop in cls.properties.values():
43
+ # we must include private properties that are needed during upsert for our forms.
44
+ # if prop.data.is_private:
45
+ # # skip private fields as this is for APIs exchange
46
+ # continue
47
+ tspropname = to_camel_case(prop.name)
48
+ pypropname = prop.name
49
+ if isinstance(prop, ObjectProperty) and prop.target.db is not None:
50
+ # this is a database object, we append id to the property name
51
+ tspropname = tspropname + "Id"
52
+ pypropname = prop.name + "_id"
53
+
54
+ tsprop = {}
55
+
56
+ if isinstance(prop, DataProperty):
57
+ tstype = prop.get_data_model_datatype().get_typescript_type()
58
+ # for schema definition, we need to use the original type, not the type alias
59
+ # if prop.name == idprop.name:
60
+ # # use id type alias
61
+ # tstype = TsTypeWithDep(f"{cls.name}Id")
62
+ for dep in tstype.deps:
63
+ program.import_(dep, True)
64
+ tsprop = [
65
+ (
66
+ expr.ExprIdent("datatype"),
67
+ (
68
+ expr.ExprConstant(tstype.spectype)
69
+ if tstype.type not in schema.enums
70
+ else expr.ExprConstant("enum")
71
+ ),
72
+ ),
73
+ *(
74
+ [
75
+ (
76
+ expr.ExprIdent("enumType"),
77
+ export_enum_info(program, schema.enums[tstype.type]),
78
+ )
79
+ ]
80
+ if tstype.type in schema.enums
81
+ else []
82
+ ),
83
+ *(
84
+ [
85
+ (
86
+ expr.ExprIdent("foreignKeyTarget"),
87
+ expr.ExprConstant(prop.db.foreign_key.name),
88
+ )
89
+ ]
90
+ if prop.db is not None
91
+ and prop.db.is_primary_key
92
+ and prop.db.foreign_key is not None
93
+ else []
94
+ ),
95
+ (
96
+ expr.ExprIdent("isRequired"),
97
+ expr.ExprConstant(
98
+ not prop.is_optional
99
+ and prop.default_value is None
100
+ and prop.default_factory is None
101
+ ),
102
+ ),
103
+ ]
104
+
105
+ norm_func = get_normalizer(tstype, import_helper)
106
+ if norm_func is not None:
107
+ # we have a normalizer for this type
108
+ prop_normalizers.append((expr.ExprIdent(tspropname), norm_func))
109
+ else:
110
+ assert isinstance(prop, ObjectProperty)
111
+ if prop.target.db is not None:
112
+ # this class is stored in the database, we store the id instead
113
+ tstype = (
114
+ assert_not_null(prop.target.get_id_property())
115
+ .get_data_model_datatype()
116
+ .get_typescript_type()
117
+ )
118
+ else:
119
+ # we are going to store the whole object
120
+ tstype = TsTypeWithDep(
121
+ type=prop.target.name,
122
+ spectype=prop.target.name,
123
+ deps=[
124
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
125
+ ],
126
+ )
127
+
128
+ # we don't store the type itself, but just the name of the type
129
+ # so not need to import the dependency
130
+ # if tstype.dep is not None:
131
+ # program.import_(
132
+ # tstype.dep,
133
+ # True,
134
+ # )
135
+
136
+ tsprop = [
137
+ (
138
+ expr.ExprIdent("targetClass"),
139
+ expr.ExprConstant(prop.target.name),
140
+ ),
141
+ (
142
+ expr.ExprIdent("datatype"),
143
+ expr.ExprConstant(
144
+ tstype.spectype if prop.target.db is not None else "undefined"
145
+ ),
146
+ ),
147
+ (
148
+ expr.ExprIdent("cardinality"),
149
+ expr.ExprConstant(prop.cardinality.value),
150
+ ),
151
+ (
152
+ expr.ExprIdent("isEmbedded"),
153
+ expr.ExprConstant(prop.target.db is None),
154
+ ),
155
+ (
156
+ expr.ExprIdent("isRequired"),
157
+ expr.ExprConstant(not prop.is_optional),
158
+ ),
159
+ ]
160
+
161
+ prop_defs.append(
162
+ (
163
+ prop,
164
+ expr.ExprIdent(tspropname),
165
+ PredefinedFn.dict(
166
+ [
167
+ (expr.ExprIdent("name"), expr.ExprConstant(pypropname)),
168
+ (expr.ExprIdent("tsName"), expr.ExprConstant(tspropname)),
169
+ (
170
+ expr.ExprIdent("updateFuncName"),
171
+ expr.ExprConstant(f"update{to_pascal_case(prop.name)}"),
172
+ ),
173
+ (
174
+ expr.ExprIdent("label"),
175
+ expr.ExprConstant(prop.label.to_dict()),
176
+ ),
177
+ (
178
+ expr.ExprIdent("description"),
179
+ (
180
+ expr.ExprConstant(prop.description.to_dict())
181
+ if not prop.description.is_empty()
182
+ else expr.ExprConstant("undefined")
183
+ ),
184
+ ),
185
+ (
186
+ expr.ExprIdent("constraints"),
187
+ PredefinedFn.list(
188
+ [
189
+ expr.ExprConstant(
190
+ constraint.get_typescript_constraint()
191
+ )
192
+ for constraint in prop.data.constraints
193
+ ]
194
+ ),
195
+ ),
196
+ ]
197
+ + tsprop
198
+ ),
199
+ )
200
+ )
201
+
202
+ for type in ["ObjectProperty", "DataProperty"]:
203
+ program.import_(f"sera-db.{type}", True)
204
+ if cls.db is not None:
205
+ program.import_(f"sera-db.Schema", True)
206
+ else:
207
+ program.import_(f"sera-db.EmbeddedSchema", True)
208
+
209
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
210
+ program.import_(f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True)
211
+ program.import_(
212
+ f"@.models.{pkg.dir.name}.Draft{cls.name}.draft{cls.name}Validators", True
213
+ )
214
+ if cls.db is not None:
215
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
216
+
217
+ program.root(
218
+ stmt.LineBreak(),
219
+ stmt.TypescriptStatement(
220
+ f"export type {cls.name}SchemaType = "
221
+ + PredefinedFn.dict(
222
+ (
223
+ [
224
+ (expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
225
+ ]
226
+ if cls.db is not None
227
+ else []
228
+ )
229
+ + [
230
+ (
231
+ expr.ExprIdent("publicProperties"),
232
+ expr.ExprIdent(
233
+ " | ".join(
234
+ [
235
+ expr.ExprConstant(
236
+ to_camel_case(prop.name) + "Id"
237
+ if isinstance(prop, ObjectProperty)
238
+ and prop.target.db is not None
239
+ else to_camel_case(prop.name)
240
+ ).to_typescript()
241
+ for prop in cls.properties.values()
242
+ if not prop.data.is_private
243
+ ]
244
+ )
245
+ ),
246
+ ),
247
+ (
248
+ expr.ExprIdent("allProperties"),
249
+ expr.ExprIdent(
250
+ f"{cls.name}SchemaType['publicProperties']"
251
+ + (
252
+ " | "
253
+ + " | ".join(
254
+ [
255
+ expr.ExprConstant(
256
+ to_camel_case(prop.name)
257
+ ).to_typescript()
258
+ for prop in cls.properties.values()
259
+ if prop.data.is_private
260
+ ]
261
+ )
262
+ if any(
263
+ prop.data.is_private
264
+ for prop in cls.properties.values()
265
+ )
266
+ else ""
267
+ )
268
+ ),
269
+ ),
270
+ (
271
+ expr.ExprIdent("cls"),
272
+ expr.ExprIdent(cls.name),
273
+ ),
274
+ (
275
+ expr.ExprIdent("draftCls"),
276
+ expr.ExprIdent(f"Draft{cls.name}"),
277
+ ),
278
+ ]
279
+ ).to_typescript()
280
+ + ";",
281
+ ),
282
+ stmt.LineBreak(),
283
+ stmt.TypescriptStatement(
284
+ f"const publicProperties: Record<{cls.name}SchemaType['publicProperties'], DataProperty | ObjectProperty> = "
285
+ + PredefinedFn.dict(
286
+ [
287
+ (prop_name, prop_def)
288
+ for prop, prop_name, prop_def in prop_defs
289
+ if not prop.data.is_private
290
+ ]
291
+ ).to_typescript()
292
+ + ";"
293
+ ),
294
+ stmt.LineBreak(),
295
+ stmt.TypescriptStatement(
296
+ (
297
+ f"export const {cls.name}Schema: Schema<{cls.name}SchemaType['id'], {cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties'], {cls.name}SchemaType> = "
298
+ if cls.db is not None
299
+ else f"export const {cls.name}Schema: EmbeddedSchema<{cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties']> = "
300
+ )
301
+ + PredefinedFn.dict(
302
+ [
303
+ (
304
+ expr.ExprIdent("publicProperties"),
305
+ expr.ExprIdent("publicProperties"),
306
+ ),
307
+ (
308
+ expr.ExprIdent("allProperties"),
309
+ expr.ExprIdent(
310
+ "{ ...publicProperties, "
311
+ + ", ".join(
312
+ [
313
+ f"{prop_name.to_typescript()}: {prop_def.to_typescript()}"
314
+ for prop, prop_name, prop_def in prop_defs
315
+ if prop.data.is_private
316
+ ]
317
+ )
318
+ + "}"
319
+ ),
320
+ ),
321
+ (
322
+ expr.ExprIdent("validators"),
323
+ expr.ExprIdent(f"draft{cls.name}Validators"),
324
+ ),
325
+ (
326
+ expr.ExprIdent("normalizers"),
327
+ PredefinedFn.dict(prop_normalizers),
328
+ ),
329
+ ]
330
+ + (
331
+ [
332
+ (
333
+ expr.ExprIdent("primaryKey"),
334
+ expr.ExprConstant(
335
+ assert_not_null(cls.get_id_property()).name
336
+ ),
337
+ )
338
+ ]
339
+ if cls.db is not None
340
+ else []
341
+ )
342
+ ).to_typescript()
343
+ + ";"
344
+ ),
345
+ )
346
+ pkg.module(cls.name + "Schema").write(program)
347
+
348
+
349
+ def export_enum_info(program: Program, enum: Enum) -> expr.Expr:
350
+ """Export enum information to
351
+
352
+ ```
353
+ {
354
+ type: <EnumType>,
355
+ label: { [value]: MultiLingualString },
356
+ description: { [value]: MultiLingualString }
357
+ }
358
+ ```
359
+ """
360
+ for key in ["Label", "Description"]:
361
+ program.import_(f"@.models.enums.{enum.name}{key}", True)
362
+
363
+ return PredefinedFn.dict(
364
+ [
365
+ (expr.ExprIdent("type"), expr.ExprIdent(enum.name)),
366
+ (expr.ExprIdent("label"), expr.ExprIdent(enum.name + "Label")),
367
+ (expr.ExprIdent("description"), expr.ExprIdent(enum.name + "Description")),
368
+ ]
369
+ )
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Any, Callable
5
+
6
+ from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
7
+ from codegen.models.var import DeferredVar
8
+ from loguru import logger
9
+
10
+ from sera.make.ts_frontend.make_class_schema import make_class_schema
11
+ from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
12
+ from sera.misc import (
13
+ assert_isinstance,
14
+ assert_not_null,
15
+ identity,
16
+ to_camel_case,
17
+ to_pascal_case,
18
+ to_snake_case,
19
+ )
20
+ from sera.models import (
21
+ Class,
22
+ DataProperty,
23
+ Enum,
24
+ ObjectProperty,
25
+ Package,
26
+ Schema,
27
+ TsTypeWithDep,
28
+ )
29
+ from sera.typing import is_set
30
+
31
+
32
+ def make_typescript_enums(schema: Schema, target_pkg: Package):
33
+ """Make typescript enum for the schema"""
34
+ enum_pkg = target_pkg.pkg("enums")
35
+
36
+ for enum in schema.enums.values():
37
+ make_enum(enum, enum_pkg)
38
+
39
+ program = Program()
40
+ for enum in schema.enums.values():
41
+ program.import_(f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}", True)
42
+ program.import_(
43
+ f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}Label", True
44
+ )
45
+ program.import_(
46
+ f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}Description", True
47
+ )
48
+
49
+ program.root(
50
+ stmt.LineBreak(),
51
+ stmt.TypescriptStatement(
52
+ "export { "
53
+ + ", ".join([enum.name for enum in schema.enums.values()])
54
+ + ","
55
+ + ", ".join([enum.name + "Label" for enum in schema.enums.values()])
56
+ + ","
57
+ + ", ".join([enum.name + "Description" for enum in schema.enums.values()])
58
+ + "};"
59
+ ),
60
+ )
61
+ enum_pkg.module("index").write(program)
62
+
63
+
64
+ def make_enum(enum: Enum, pkg: Package):
65
+ program = Program()
66
+ program.root(
67
+ stmt.LineBreak(),
68
+ lambda ast: ast.class_like("enum", enum.name)(
69
+ *[
70
+ stmt.DefEnumValueStatement(
71
+ name=value.name,
72
+ value=expr.ExprConstant(value.value),
73
+ )
74
+ for value in enum.values.values()
75
+ ]
76
+ ),
77
+ stmt.LineBreak(),
78
+ stmt.TypescriptStatement(
79
+ f"export const {enum.name}Label = "
80
+ + PredefinedFn.dict(
81
+ [
82
+ (
83
+ expr.ExprConstant(value.value),
84
+ expr.ExprConstant(value.label.to_dict()),
85
+ )
86
+ for value in enum.values.values()
87
+ ]
88
+ ).to_typescript()
89
+ ),
90
+ stmt.LineBreak(),
91
+ stmt.TypescriptStatement(
92
+ f"export const {enum.name}Description = "
93
+ + PredefinedFn.dict(
94
+ [
95
+ (
96
+ expr.ExprConstant(value.value),
97
+ expr.ExprConstant(value.description.to_dict()),
98
+ )
99
+ for value in enum.values.values()
100
+ ]
101
+ ).to_typescript()
102
+ ),
103
+ )
104
+ pkg.module(enum.get_tsmodule_name()).write(program)
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
6
+
7
+ from sera.models import (
8
+ Class,
9
+ DataProperty,
10
+ Enum,
11
+ ObjectProperty,
12
+ Package,
13
+ Schema,
14
+ TsTypeWithDep,
15
+ )
16
+
17
+ TS_GLOBAL_IDENTS = {
18
+ "normalizers.normalizeNumber": "sera-db.normalizers",
19
+ "normalizers.normalizeOptionalNumber": "sera-db.normalizers",
20
+ "normalizers.normalizeDate": "sera-db.normalizers",
21
+ "normalizers.normalizeOptionalDate": "sera-db.normalizers",
22
+ }
23
+
24
+
25
+ def get_normalizer(
26
+ tstype: TsTypeWithDep, import_helper: ImportHelper
27
+ ) -> Optional[expr.ExprIdent]:
28
+ if tstype.type == "number":
29
+ return import_helper.use("normalizers.normalizeNumber")
30
+ if tstype.type == "number | undefined":
31
+ return import_helper.use("normalizers.normalizeOptionalNumber")
32
+ if tstype.type == "Date":
33
+ return import_helper.use("normalizers.normalizeDate")
34
+ if tstype.type == "Date | undefined":
35
+ return import_helper.use("normalizers.normalizeOptionalDate")
36
+
37
+ assert "number" not in tstype.type, tstype.type
38
+ return None
@@ -11,6 +11,7 @@ from sera.models._multi_lingual_string import MultiLingualString
11
11
  class EnumValue:
12
12
  name: str
13
13
  value: str | int
14
+ label: MultiLingualString
14
15
  description: MultiLingualString
15
16
 
16
17
 
@@ -44,7 +45,7 @@ class Enum:
44
45
 
45
46
  def get_tsmodule_name(self) -> str:
46
47
  """Get the typescript module name of this enum as if there is a typescript module created to store this enum only."""
47
- return to_kebab_case(self.name)
48
+ return self.name
48
49
 
49
50
  def is_str_enum(self) -> bool:
50
51
  """Check if this enum is a string enum."""
@@ -91,13 +91,17 @@ def _parse_enum(schema: Schema, enum_name: str, enum: dict) -> Enum:
91
91
  for k, v in enum.items():
92
92
  if isinstance(v, (str, int)):
93
93
  values[k] = EnumValue(
94
- name=k, value=v, description=MultiLingualString.en("")
94
+ name=k,
95
+ value=v,
96
+ label=MultiLingualString.en(""),
97
+ description=MultiLingualString.en(""),
95
98
  )
96
99
  else:
97
100
  try:
98
101
  values[k] = EnumValue(
99
102
  name=k,
100
103
  value=v["value"],
104
+ label=_parse_multi_lingual_string(v.get("label", "")),
101
105
  description=_parse_multi_lingual_string(v.get("desc", "")),
102
106
  )
103
107
  except KeyError as e:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes