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.
Files changed (47) hide show
  1. {sera_2-1.20.16 → sera_2-1.21.0}/PKG-INFO +1 -1
  2. {sera_2-1.20.16 → sera_2-1.21.0}/pyproject.toml +1 -1
  3. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/base_service.py +0 -2
  4. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_dcg.py +2 -2
  5. {sera_2-1.20.16 → sera_2-1.21.0}/sera/misc/__init__.py +2 -0
  6. {sera_2-1.20.16 → sera_2-1.21.0}/sera/misc/_utils.py +64 -45
  7. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_constraints.py +3 -3
  8. {sera_2-1.20.16 → sera_2-1.21.0}/README.md +0 -0
  9. {sera_2-1.20.16 → sera_2-1.21.0}/sera/__init__.py +0 -0
  10. {sera_2-1.20.16 → sera_2-1.21.0}/sera/constants.py +0 -0
  11. {sera_2-1.20.16 → sera_2-1.21.0}/sera/exports/__init__.py +0 -0
  12. {sera_2-1.20.16 → sera_2-1.21.0}/sera/exports/schema.py +0 -0
  13. {sera_2-1.20.16 → sera_2-1.21.0}/sera/exports/test.py +0 -0
  14. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/__init__.py +0 -0
  15. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/api_helper.py +0 -0
  16. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/api_test_helper.py +0 -0
  17. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/base_orm.py +0 -0
  18. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/__init__.py +0 -0
  19. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_edge.py +0 -0
  20. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_flow.py +0 -0
  21. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_fn_signature.py +0 -0
  22. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_node.py +0 -0
  23. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_runtime.py +0 -0
  24. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/directed_computing_graph/_type_conversion.py +0 -0
  25. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/middlewares/__init__.py +0 -0
  26. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/middlewares/auth.py +0 -0
  27. {sera_2-1.20.16 → sera_2-1.21.0}/sera/libs/middlewares/uscp.py +0 -0
  28. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/__init__.py +0 -0
  29. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/__main__.py +0 -0
  30. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_app.py +0 -0
  31. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_python_api.py +0 -0
  32. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_python_model.py +0 -0
  33. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_python_services.py +0 -0
  34. {sera_2-1.20.16 → sera_2-1.21.0}/sera/make/make_typescript_model.py +0 -0
  35. {sera_2-1.20.16 → sera_2-1.21.0}/sera/misc/_formatter.py +0 -0
  36. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/__init__.py +0 -0
  37. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_class.py +0 -0
  38. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_collection.py +0 -0
  39. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_datatype.py +0 -0
  40. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_default.py +0 -0
  41. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_enum.py +0 -0
  42. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_module.py +0 -0
  43. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_multi_lingual_string.py +0 -0
  44. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_parse.py +0 -0
  45. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_property.py +0 -0
  46. {sera_2-1.20.16 → sera_2-1.21.0}/sera/models/_schema.py +0 -0
  47. {sera_2-1.20.16 → sera_2-1.21.0}/sera/typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.20.16
3
+ Version: 1.21.0
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.20.16"
3
+ version = "1.21.0"
4
4
  description = ""
5
5
  authors = ["Binh Vu <bvu687@gmail.com>"]
6
6
  readme = "README.md"
@@ -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 executed.
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 executed.
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 identity(x: T) -> T:
328
- """Identity function that returns the input unchanged."""
329
- return x
330
-
331
-
332
- def get_classpath(type: Type | Callable) -> str:
333
- if type.__module__ == "builtins":
334
- return type.__qualname__
335
-
336
- if hasattr(type, "__qualname__"):
337
- return type.__module__ + "." + type.__qualname__
338
-
339
- # typically a class from the typing module
340
- if hasattr(type, "_name") and type._name is not None:
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 == "whole_number":
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
- "whole_number": Constraint("whole_number", ()),
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