sera-2 1.21.2__py3-none-any.whl → 1.24.1__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/libs/api_helper.py +1 -100
- sera/libs/api_test_helper.py +1 -1
- sera/libs/base_service.py +168 -80
- sera/libs/search_helper.py +359 -0
- sera/make/make_app.py +4 -5
- sera/make/make_python_api.py +65 -113
- sera/make/make_python_model.py +184 -17
- sera/make/make_python_services.py +3 -2
- sera/make/make_typescript_model.py +17 -337
- sera/make/ts_frontend/__init__.py +0 -0
- sera/make/ts_frontend/make_class_schema.py +369 -0
- sera/make/ts_frontend/make_enums.py +104 -0
- sera/make/ts_frontend/misc.py +38 -0
- sera/misc/__init__.py +2 -0
- sera/misc/_utils.py +17 -2
- sera/models/_class.py +2 -2
- sera/models/_collection.py +15 -11
- sera/models/_constraints.py +1 -1
- sera/models/_datatype.py +8 -29
- sera/models/_enum.py +3 -2
- sera/models/_module.py +7 -0
- sera/models/_parse.py +5 -1
- {sera_2-1.21.2.dist-info → sera_2-1.24.1.dist-info}/METADATA +2 -2
- {sera_2-1.21.2.dist-info → sera_2-1.24.1.dist-info}/RECORD +25 -20
- {sera_2-1.21.2.dist-info → sera_2-1.24.1.dist-info}/WHEEL +0 -0
@@ -1,12 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import re
|
4
|
-
from typing import Any, Callable
|
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
|
-
|
438
|
+
enum_value_name = next(
|
444
439
|
iter(schema.enums[tstype.type].values.values())
|
445
|
-
).
|
446
|
-
|
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 + "." +
|
443
|
+
tstype.type + "." + enum_value_name
|
450
444
|
)
|
451
445
|
else:
|
452
446
|
create_propvalue = tstype.get_default()
|
@@ -1194,311 +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
|
-
expr.ExprIdent("isRequired"),
|
1247
|
-
expr.ExprConstant(
|
1248
|
-
not prop.is_optional
|
1249
|
-
and prop.default_value is None
|
1250
|
-
and prop.default_factory is None
|
1251
|
-
),
|
1252
|
-
),
|
1253
|
-
]
|
1254
|
-
|
1255
|
-
norm_func = get_normalizer(tstype, import_helper)
|
1256
|
-
if norm_func is not None:
|
1257
|
-
# we have a normalizer for this type
|
1258
|
-
prop_normalizers.append((expr.ExprIdent(tspropname), norm_func))
|
1259
|
-
else:
|
1260
|
-
assert isinstance(prop, ObjectProperty)
|
1261
|
-
if prop.target.db is not None:
|
1262
|
-
# this class is stored in the database, we store the id instead
|
1263
|
-
tstype = (
|
1264
|
-
assert_not_null(prop.target.get_id_property())
|
1265
|
-
.get_data_model_datatype()
|
1266
|
-
.get_typescript_type()
|
1267
|
-
)
|
1268
|
-
else:
|
1269
|
-
# we are going to store the whole object
|
1270
|
-
tstype = TsTypeWithDep(
|
1271
|
-
type=prop.target.name,
|
1272
|
-
spectype=prop.target.name,
|
1273
|
-
deps=[
|
1274
|
-
f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
|
1275
|
-
],
|
1276
|
-
)
|
1277
|
-
|
1278
|
-
# we don't store the type itself, but just the name of the type
|
1279
|
-
# so not need to import the dependency
|
1280
|
-
# if tstype.dep is not None:
|
1281
|
-
# program.import_(
|
1282
|
-
# tstype.dep,
|
1283
|
-
# True,
|
1284
|
-
# )
|
1285
|
-
|
1286
|
-
tsprop = [
|
1287
|
-
(
|
1288
|
-
expr.ExprIdent("targetClass"),
|
1289
|
-
expr.ExprConstant(prop.target.name),
|
1290
|
-
),
|
1291
|
-
(
|
1292
|
-
expr.ExprIdent("datatype"),
|
1293
|
-
expr.ExprConstant(
|
1294
|
-
tstype.spectype
|
1295
|
-
if prop.target.db is not None
|
1296
|
-
else "undefined"
|
1297
|
-
),
|
1298
|
-
),
|
1299
|
-
(
|
1300
|
-
expr.ExprIdent("cardinality"),
|
1301
|
-
expr.ExprConstant(prop.cardinality.value),
|
1302
|
-
),
|
1303
|
-
(
|
1304
|
-
expr.ExprIdent("isEmbedded"),
|
1305
|
-
expr.ExprConstant(prop.target.db is None),
|
1306
|
-
),
|
1307
|
-
(
|
1308
|
-
expr.ExprIdent("isRequired"),
|
1309
|
-
expr.ExprConstant(not prop.is_optional),
|
1310
|
-
),
|
1311
|
-
]
|
1312
|
-
|
1313
|
-
prop_defs.append(
|
1314
|
-
(
|
1315
|
-
prop,
|
1316
|
-
expr.ExprIdent(tspropname),
|
1317
|
-
PredefinedFn.dict(
|
1318
|
-
[
|
1319
|
-
(expr.ExprIdent("name"), expr.ExprConstant(pypropname)),
|
1320
|
-
(expr.ExprIdent("tsName"), expr.ExprConstant(tspropname)),
|
1321
|
-
(
|
1322
|
-
expr.ExprIdent("updateFuncName"),
|
1323
|
-
expr.ExprConstant(f"update{to_pascal_case(prop.name)}"),
|
1324
|
-
),
|
1325
|
-
(
|
1326
|
-
expr.ExprIdent("label"),
|
1327
|
-
expr.ExprConstant(prop.label.to_dict()),
|
1328
|
-
),
|
1329
|
-
(
|
1330
|
-
expr.ExprIdent("description"),
|
1331
|
-
(
|
1332
|
-
expr.ExprConstant(prop.description.to_dict())
|
1333
|
-
if not prop.description.is_empty()
|
1334
|
-
else expr.ExprConstant("undefined")
|
1335
|
-
),
|
1336
|
-
),
|
1337
|
-
(
|
1338
|
-
expr.ExprIdent("constraints"),
|
1339
|
-
PredefinedFn.list(
|
1340
|
-
[
|
1341
|
-
expr.ExprConstant(
|
1342
|
-
constraint.get_typescript_constraint()
|
1343
|
-
)
|
1344
|
-
for constraint in prop.data.constraints
|
1345
|
-
]
|
1346
|
-
),
|
1347
|
-
),
|
1348
|
-
]
|
1349
|
-
+ tsprop
|
1350
|
-
),
|
1351
|
-
)
|
1352
|
-
)
|
1353
|
-
|
1354
|
-
for type in ["ObjectProperty", "DataProperty"]:
|
1355
|
-
program.import_(f"sera-db.{type}", True)
|
1356
|
-
if cls.db is not None:
|
1357
|
-
program.import_(f"sera-db.Schema", True)
|
1358
|
-
else:
|
1359
|
-
program.import_(f"sera-db.EmbeddedSchema", True)
|
1360
|
-
|
1361
|
-
program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
|
1362
|
-
program.import_(
|
1363
|
-
f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
|
1364
|
-
)
|
1365
|
-
program.import_(
|
1366
|
-
f"@.models.{pkg.dir.name}.Draft{cls.name}.draft{cls.name}Validators", True
|
1367
|
-
)
|
1368
|
-
if cls.db is not None:
|
1369
|
-
program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
|
1370
|
-
|
1371
|
-
program.root(
|
1372
|
-
stmt.LineBreak(),
|
1373
|
-
stmt.TypescriptStatement(
|
1374
|
-
f"export type {cls.name}SchemaType = "
|
1375
|
-
+ PredefinedFn.dict(
|
1376
|
-
(
|
1377
|
-
[
|
1378
|
-
(expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
|
1379
|
-
]
|
1380
|
-
if cls.db is not None
|
1381
|
-
else []
|
1382
|
-
)
|
1383
|
-
+ [
|
1384
|
-
(
|
1385
|
-
expr.ExprIdent("publicProperties"),
|
1386
|
-
expr.ExprIdent(
|
1387
|
-
" | ".join(
|
1388
|
-
[
|
1389
|
-
expr.ExprConstant(
|
1390
|
-
to_camel_case(prop.name) + "Id"
|
1391
|
-
if isinstance(prop, ObjectProperty)
|
1392
|
-
and prop.target.db is not None
|
1393
|
-
else to_camel_case(prop.name)
|
1394
|
-
).to_typescript()
|
1395
|
-
for prop in cls.properties.values()
|
1396
|
-
if not prop.data.is_private
|
1397
|
-
]
|
1398
|
-
)
|
1399
|
-
),
|
1400
|
-
),
|
1401
|
-
(
|
1402
|
-
expr.ExprIdent("allProperties"),
|
1403
|
-
expr.ExprIdent(
|
1404
|
-
f"{cls.name}SchemaType['publicProperties']"
|
1405
|
-
+ (
|
1406
|
-
" | "
|
1407
|
-
+ " | ".join(
|
1408
|
-
[
|
1409
|
-
expr.ExprConstant(
|
1410
|
-
to_camel_case(prop.name)
|
1411
|
-
).to_typescript()
|
1412
|
-
for prop in cls.properties.values()
|
1413
|
-
if prop.data.is_private
|
1414
|
-
]
|
1415
|
-
)
|
1416
|
-
if any(
|
1417
|
-
prop.data.is_private
|
1418
|
-
for prop in cls.properties.values()
|
1419
|
-
)
|
1420
|
-
else ""
|
1421
|
-
)
|
1422
|
-
),
|
1423
|
-
),
|
1424
|
-
(
|
1425
|
-
expr.ExprIdent("cls"),
|
1426
|
-
expr.ExprIdent(cls.name),
|
1427
|
-
),
|
1428
|
-
(
|
1429
|
-
expr.ExprIdent("draftCls"),
|
1430
|
-
expr.ExprIdent(f"Draft{cls.name}"),
|
1431
|
-
),
|
1432
|
-
]
|
1433
|
-
).to_typescript()
|
1434
|
-
+ ";",
|
1435
|
-
),
|
1436
|
-
stmt.LineBreak(),
|
1437
|
-
stmt.TypescriptStatement(
|
1438
|
-
f"const publicProperties: Record<{cls.name}SchemaType['publicProperties'], DataProperty | ObjectProperty> = "
|
1439
|
-
+ PredefinedFn.dict(
|
1440
|
-
[
|
1441
|
-
(prop_name, prop_def)
|
1442
|
-
for prop, prop_name, prop_def in prop_defs
|
1443
|
-
if not prop.data.is_private
|
1444
|
-
]
|
1445
|
-
).to_typescript()
|
1446
|
-
+ ";"
|
1447
|
-
),
|
1448
|
-
stmt.LineBreak(),
|
1449
|
-
stmt.TypescriptStatement(
|
1450
|
-
(
|
1451
|
-
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> = "
|
1452
|
-
if cls.db is not None
|
1453
|
-
else f"export const {cls.name}Schema: EmbeddedSchema<{cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties']> = "
|
1454
|
-
)
|
1455
|
-
+ PredefinedFn.dict(
|
1456
|
-
[
|
1457
|
-
(
|
1458
|
-
expr.ExprIdent("publicProperties"),
|
1459
|
-
expr.ExprIdent("publicProperties"),
|
1460
|
-
),
|
1461
|
-
(
|
1462
|
-
expr.ExprIdent("allProperties"),
|
1463
|
-
expr.ExprIdent(
|
1464
|
-
"{ ...publicProperties, "
|
1465
|
-
+ ", ".join(
|
1466
|
-
[
|
1467
|
-
f"{prop_name.to_typescript()}: {prop_def.to_typescript()}"
|
1468
|
-
for prop, prop_name, prop_def in prop_defs
|
1469
|
-
if prop.data.is_private
|
1470
|
-
]
|
1471
|
-
)
|
1472
|
-
+ "}"
|
1473
|
-
),
|
1474
|
-
),
|
1475
|
-
(
|
1476
|
-
expr.ExprIdent("validators"),
|
1477
|
-
expr.ExprIdent(f"draft{cls.name}Validators"),
|
1478
|
-
),
|
1479
|
-
(
|
1480
|
-
expr.ExprIdent("normalizers"),
|
1481
|
-
PredefinedFn.dict(prop_normalizers),
|
1482
|
-
),
|
1483
|
-
]
|
1484
|
-
+ (
|
1485
|
-
[
|
1486
|
-
(
|
1487
|
-
expr.ExprIdent("primaryKey"),
|
1488
|
-
expr.ExprConstant(
|
1489
|
-
assert_not_null(cls.get_id_property()).name
|
1490
|
-
),
|
1491
|
-
)
|
1492
|
-
]
|
1493
|
-
if cls.db is not None
|
1494
|
-
else []
|
1495
|
-
)
|
1496
|
-
).to_typescript()
|
1497
|
-
+ ";"
|
1498
|
-
),
|
1499
|
-
)
|
1500
|
-
pkg.module(cls.name + "Schema").write(program)
|
1501
|
-
|
1502
1191
|
def make_index(pkg: Package):
|
1503
1192
|
outmod = pkg.module("index")
|
1504
1193
|
if outmod.exists():
|
@@ -1554,7 +1243,7 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
1554
1243
|
make_draft(cls, pkg)
|
1555
1244
|
make_query_processor(cls, pkg)
|
1556
1245
|
make_table(cls, pkg)
|
1557
|
-
|
1246
|
+
make_class_schema(schema, cls, pkg)
|
1558
1247
|
|
1559
1248
|
make_index(pkg)
|
1560
1249
|
|
@@ -1618,14 +1307,19 @@ def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
|
|
1618
1307
|
if m is not None:
|
1619
1308
|
# This is an array type, add string to the inner type
|
1620
1309
|
inner_type = m.group(1)
|
1310
|
+
inner_spectype = assert_not_null(
|
1311
|
+
re.match(r"(\(?[a-zA-Z \|]+\)?)(\[\])", tstype.spectype)
|
1312
|
+
).group(1)
|
1621
1313
|
if "string" not in inner_type:
|
1622
1314
|
if inner_type.startswith("(") and inner_type.endswith(")"):
|
1623
1315
|
# Already has parentheses
|
1624
1316
|
inner_type = f"{inner_type[:-1]} | string)"
|
1317
|
+
inner_spectype = f"{inner_spectype[:-1]} | string)"
|
1625
1318
|
else:
|
1626
1319
|
# Need to add parentheses
|
1627
1320
|
inner_type = f"({inner_type} | string)"
|
1628
|
-
|
1321
|
+
inner_spectype = f"({inner_spectype} | string)"
|
1322
|
+
return TsTypeWithDep(inner_type + "[]", inner_spectype + "[]", tstype.deps)
|
1629
1323
|
|
1630
1324
|
m = re.match(r"^\(?[a-zA-Z \|]+\)?$", tstype.type)
|
1631
1325
|
if m is not None:
|
@@ -1633,31 +1327,17 @@ def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
|
|
1633
1327
|
if tstype.type.startswith("(") and tstype.type.endswith(")"):
|
1634
1328
|
# Already has parentheses
|
1635
1329
|
new_type = f"{tstype.type[:-1]} | string)"
|
1330
|
+
new_spectype = f"{tstype.spectype[:-1]} | string)"
|
1636
1331
|
else:
|
1637
1332
|
# Needs parentheses for clarity
|
1638
1333
|
new_type = f"({tstype.type} | string)"
|
1639
|
-
|
1334
|
+
new_spectype = f"({tstype.spectype} | string)"
|
1335
|
+
return TsTypeWithDep(new_type, new_spectype, tstype.deps)
|
1640
1336
|
return tstype
|
1641
1337
|
|
1642
1338
|
raise NotImplementedError(tstype.type)
|
1643
1339
|
|
1644
1340
|
|
1645
|
-
def get_normalizer(
|
1646
|
-
tstype: TsTypeWithDep, import_helper: ImportHelper
|
1647
|
-
) -> Optional[expr.ExprIdent]:
|
1648
|
-
if tstype.type == "number":
|
1649
|
-
return import_helper.use("normalizers.normalizeNumber")
|
1650
|
-
if tstype.type == "number | undefined":
|
1651
|
-
return import_helper.use("normalizers.normalizeOptionalNumber")
|
1652
|
-
if tstype.type == "Date":
|
1653
|
-
return import_helper.use("normalizers.normalizeDate")
|
1654
|
-
if tstype.type == "Date | undefined":
|
1655
|
-
return import_helper.use("normalizers.normalizeOptionalDate")
|
1656
|
-
|
1657
|
-
assert "number" not in tstype.type, tstype.type
|
1658
|
-
return None
|
1659
|
-
|
1660
|
-
|
1661
1341
|
def get_norm_func(
|
1662
1342
|
tstype: TsTypeWithDep, import_helper: ImportHelper
|
1663
1343
|
) -> Callable[[expr.Expr], expr.Expr]:
|
File without changes
|