sera-2 1.20.16__tar.gz → 1.21.0__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.
- {sera_2-1.20.16 → sera_2-1.21.0}/PKG-INFO +1 -1
- {sera_2-1.20.16 → sera_2-1.21.0}/pyproject.toml +1 -1
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/base_service.py +0 -2
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_dcg.py +2 -2
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/misc/__init__.py +2 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/misc/_utils.py +64 -45
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_constraints.py +3 -3
- {sera_2-1.20.16 → sera_2-1.21.0}/README.md +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/constants.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/exports/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/exports/schema.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/exports/test.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/api_helper.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/api_test_helper.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/base_orm.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_edge.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_flow.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_fn_signature.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_node.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_runtime.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_type_conversion.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/middlewares/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/middlewares/auth.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/middlewares/uscp.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/__main__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_app.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_python_api.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_python_model.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_python_services.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_typescript_model.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/misc/_formatter.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/__init__.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_class.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_collection.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_datatype.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_default.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_enum.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_module.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_multi_lingual_string.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_parse.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_property.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_schema.py +0 -0
- {sera_2-1.20.16 → sera_2-1.21.0}/sera/typing.py +0 -0
@@ -128,8 +128,6 @@ class BaseAsyncService(Generic[ID, R]):
|
|
128
128
|
func.to_tsvector(getattr(self.orm_cls, field)).match(value)
|
129
129
|
)
|
130
130
|
|
131
|
-
print(">>>", q)
|
132
|
-
|
133
131
|
cq = select(func.count()).select_from(q.subquery())
|
134
132
|
rq = q.limit(limit).offset(offset)
|
135
133
|
records = self._process_result(await session.execute(rq)).scalars().all()
|
@@ -201,7 +201,7 @@ class DirectedComputingGraph:
|
|
201
201
|
|
202
202
|
Args:
|
203
203
|
input: A dictionary mapping function identifiers to their input arguments.
|
204
|
-
output: A set of function identifiers that should be
|
204
|
+
output: A set of function identifiers that their output should be captured.
|
205
205
|
context: An optional context that can be a dictionary of functions or a single function.
|
206
206
|
"""
|
207
207
|
assert all(
|
@@ -307,7 +307,7 @@ class DirectedComputingGraph:
|
|
307
307
|
|
308
308
|
Args:
|
309
309
|
input: A dictionary mapping function identifiers to their input arguments.
|
310
|
-
output: A set of function identifiers that should be
|
310
|
+
output: A set of function identifiers that their output should be captured.
|
311
311
|
context: An optional context that can be a dictionary of functions or a single function.
|
312
312
|
"""
|
313
313
|
assert all(
|
@@ -9,6 +9,7 @@ from sera.misc._utils import (
|
|
9
9
|
identity,
|
10
10
|
load_data,
|
11
11
|
load_data_from_dir,
|
12
|
+
replay_events,
|
12
13
|
to_camel_case,
|
13
14
|
to_pascal_case,
|
14
15
|
to_snake_case,
|
@@ -30,4 +31,5 @@ __all__ = [
|
|
30
31
|
"LoadTableDataArgs",
|
31
32
|
"RelTableIndex",
|
32
33
|
"load_data_from_dir",
|
34
|
+
"replay_events",
|
33
35
|
]
|
@@ -4,9 +4,9 @@ import importlib
|
|
4
4
|
import inspect
|
5
5
|
import re
|
6
6
|
from collections import defaultdict
|
7
|
-
from dataclasses import dataclass
|
8
7
|
from pathlib import Path
|
9
8
|
from typing import (
|
9
|
+
TYPE_CHECKING,
|
10
10
|
Any,
|
11
11
|
Callable,
|
12
12
|
Iterable,
|
@@ -27,6 +27,9 @@ from sqlalchemy import Engine, text
|
|
27
27
|
from sqlalchemy.orm import Session
|
28
28
|
from tqdm import tqdm
|
29
29
|
|
30
|
+
if TYPE_CHECKING:
|
31
|
+
from sera.libs.directed_computing_graph import DirectedComputingGraph
|
32
|
+
|
30
33
|
T = TypeVar("T")
|
31
34
|
|
32
35
|
TYPE_ALIASES = {"typing.List": "list", "typing.Dict": "dict", "typing.Set": "set"}
|
@@ -138,6 +141,52 @@ def filter_duplication(
|
|
138
141
|
return new_lst
|
139
142
|
|
140
143
|
|
144
|
+
def identity(x: T) -> T:
|
145
|
+
"""Identity function that returns the input unchanged."""
|
146
|
+
return x
|
147
|
+
|
148
|
+
|
149
|
+
def get_classpath(type: Type | Callable) -> str:
|
150
|
+
if type.__module__ == "builtins":
|
151
|
+
return type.__qualname__
|
152
|
+
|
153
|
+
if hasattr(type, "__qualname__"):
|
154
|
+
return type.__module__ + "." + type.__qualname__
|
155
|
+
|
156
|
+
# typically a class from the typing module
|
157
|
+
if hasattr(type, "_name") and type._name is not None:
|
158
|
+
path = type.__module__ + "." + type._name
|
159
|
+
if path in TYPE_ALIASES:
|
160
|
+
path = TYPE_ALIASES[path]
|
161
|
+
elif hasattr(type, "__origin__") and hasattr(type.__origin__, "_name"):
|
162
|
+
# found one case which is typing.Union
|
163
|
+
path = type.__module__ + "." + type.__origin__._name
|
164
|
+
else:
|
165
|
+
raise NotImplementedError(type)
|
166
|
+
|
167
|
+
return path
|
168
|
+
|
169
|
+
|
170
|
+
def get_dbclass_deser_func(type: type[T]) -> Callable[[dict], T]:
|
171
|
+
"""Get a deserializer function for a class in models.db."""
|
172
|
+
module, clsname = (
|
173
|
+
get_classpath(type)
|
174
|
+
.replace(".models.db.", ".models.data.")
|
175
|
+
.rsplit(".", maxsplit=1)
|
176
|
+
)
|
177
|
+
StructType = getattr(importlib.import_module(module), f"Create{clsname}")
|
178
|
+
|
179
|
+
def deser_func(obj: dict):
|
180
|
+
record = msgspec.json.decode(orjson.dumps(obj), type=StructType)
|
181
|
+
if hasattr(record, "_is_scp_updated"):
|
182
|
+
# Skip updating system-controlled properties
|
183
|
+
record._is_scp_updated = True
|
184
|
+
|
185
|
+
return record.to_db()
|
186
|
+
|
187
|
+
return deser_func
|
188
|
+
|
189
|
+
|
141
190
|
class LoadTableDataArgs(TypedDict, total=False):
|
142
191
|
table: type
|
143
192
|
tables: Sequence[type]
|
@@ -324,47 +373,17 @@ def load_data_from_dir(
|
|
324
373
|
load_data(engine, create_db_and_tables, load_args, verbose)
|
325
374
|
|
326
375
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
path = type.__module__ + "." + type._name
|
342
|
-
if path in TYPE_ALIASES:
|
343
|
-
path = TYPE_ALIASES[path]
|
344
|
-
elif hasattr(type, "__origin__") and hasattr(type.__origin__, "_name"):
|
345
|
-
# found one case which is typing.Union
|
346
|
-
path = type.__module__ + "." + type.__origin__._name
|
347
|
-
else:
|
348
|
-
raise NotImplementedError(type)
|
349
|
-
|
350
|
-
return path
|
351
|
-
|
352
|
-
|
353
|
-
def get_dbclass_deser_func(type: type[T]) -> Callable[[dict], T]:
|
354
|
-
"""Get a deserializer function for a class in models.db."""
|
355
|
-
module, clsname = (
|
356
|
-
get_classpath(type)
|
357
|
-
.replace(".models.db.", ".models.data.")
|
358
|
-
.rsplit(".", maxsplit=1)
|
359
|
-
)
|
360
|
-
StructType = getattr(importlib.import_module(module), f"Create{clsname}")
|
361
|
-
|
362
|
-
def deser_func(obj: dict):
|
363
|
-
record = msgspec.json.decode(orjson.dumps(obj), type=StructType)
|
364
|
-
if hasattr(record, "_is_scp_updated"):
|
365
|
-
# Skip updating system-controlled properties
|
366
|
-
record._is_scp_updated = True
|
367
|
-
|
368
|
-
return record.to_db()
|
369
|
-
|
370
|
-
return deser_func
|
376
|
+
async def replay_events(
|
377
|
+
engine: AsyncEngine, dcg: DirectedComputingGraph, tables: Sequence[type]
|
378
|
+
):
|
379
|
+
"""Replay the events in the DirectedComputingGraph. This is useful to re-run the workflows
|
380
|
+
that computes derived data after initial data loading.
|
381
|
+
"""
|
382
|
+
async with AsyncSession(engine, expire_on_commit=False) as session:
|
383
|
+
for tbl in tables:
|
384
|
+
innode = f"{tbl.__tablename__}.create"
|
385
|
+
for record in tqdm(
|
386
|
+
session.execute(select(tbl)).scalars(),
|
387
|
+
desc=f"Replaying events for {tbl.__tablename__}",
|
388
|
+
):
|
389
|
+
await dcg.execute_async(input={innode: record})
|
@@ -10,8 +10,8 @@ ConstraintName = Literal[
|
|
10
10
|
"not_empty",
|
11
11
|
"username",
|
12
12
|
"password",
|
13
|
-
"whole_number",
|
14
13
|
"positive_number",
|
14
|
+
"non_negative_number",
|
15
15
|
]
|
16
16
|
|
17
17
|
|
@@ -34,7 +34,7 @@ class Constraint:
|
|
34
34
|
)
|
35
35
|
elif self.name == "password":
|
36
36
|
return "msgspec.Meta(min_length=8, max_length=40)"
|
37
|
-
elif self.name == "
|
37
|
+
elif self.name == "non_negative_number":
|
38
38
|
return "msgspec.Meta(ge=0)"
|
39
39
|
elif self.name == "positive_number":
|
40
40
|
return "msgspec.Meta(gt=0)"
|
@@ -58,7 +58,7 @@ predefined_constraints: dict[ConstraintName, Constraint] = {
|
|
58
58
|
"not_empty": Constraint("not_empty", ()),
|
59
59
|
"username": Constraint("username", ()),
|
60
60
|
"password": Constraint("password", ()),
|
61
|
-
"
|
61
|
+
"non_negative_number": Constraint("non_negative_number", ()),
|
62
62
|
"positive_number": Constraint("positive_number", ()),
|
63
63
|
"url": Constraint("url", ()),
|
64
64
|
}
|
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
|
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
|
File without changes
|
File without changes
|