sera-2 1.20.14__py3-none-any.whl → 1.21.0__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/base_service.py CHANGED
@@ -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(
sera/misc/__init__.py CHANGED
@@ -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
  ]
sera/misc/_utils.py CHANGED
@@ -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]
@@ -311,6 +360,7 @@ def load_data_from_dir(
311
360
  logger.warning(
312
361
  "File {} does not exist, skipping loading for {}", file, tbl.__name__
313
362
  )
363
+ continue
314
364
 
315
365
  load_args.append(
316
366
  LoadTableDataArgs(
@@ -323,47 +373,17 @@ def load_data_from_dir(
323
373
  load_data(engine, create_db_and_tables, load_args, verbose)
324
374
 
325
375
 
326
- def identity(x: T) -> T:
327
- """Identity function that returns the input unchanged."""
328
- return x
329
-
330
-
331
- def get_classpath(type: Type | Callable) -> str:
332
- if type.__module__ == "builtins":
333
- return type.__qualname__
334
-
335
- if hasattr(type, "__qualname__"):
336
- return type.__module__ + "." + type.__qualname__
337
-
338
- # typically a class from the typing module
339
- if hasattr(type, "_name") and type._name is not None:
340
- path = type.__module__ + "." + type._name
341
- if path in TYPE_ALIASES:
342
- path = TYPE_ALIASES[path]
343
- elif hasattr(type, "__origin__") and hasattr(type.__origin__, "_name"):
344
- # found one case which is typing.Union
345
- path = type.__module__ + "." + type.__origin__._name
346
- else:
347
- raise NotImplementedError(type)
348
-
349
- return path
350
-
351
-
352
- def get_dbclass_deser_func(type: type[T]) -> Callable[[dict], T]:
353
- """Get a deserializer function for a class in models.db."""
354
- module, clsname = (
355
- get_classpath(type)
356
- .replace(".models.db.", ".models.data.")
357
- .rsplit(".", maxsplit=1)
358
- )
359
- StructType = getattr(importlib.import_module(module), f"Create{clsname}")
360
-
361
- def deser_func(obj: dict):
362
- record = msgspec.json.decode(orjson.dumps(obj), type=StructType)
363
- if hasattr(record, "_is_scp_updated"):
364
- # Skip updating system-controlled properties
365
- record._is_scp_updated = True
366
-
367
- return record.to_db()
368
-
369
- 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
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.20.14
3
+ Version: 1.21.0
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -7,9 +7,9 @@ sera/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  sera/libs/api_helper.py,sha256=lWg_-2N8cLU4jCV-lSebO2o1b2gI4Tal0ThUlYkN0nA,6689
8
8
  sera/libs/api_test_helper.py,sha256=3tRr8sLN4dBSrHgKAHMmyoENI0xh7K_JLel8AvujU7k,1323
9
9
  sera/libs/base_orm.py,sha256=5hOH_diUeaABm3cpE2-9u50VRqG1QW2osPQnvVHIhIA,3365
10
- sera/libs/base_service.py,sha256=V6wug1QA5KD0FsQU9r1XQmLUTB85E6oIR2e12Fi1IE0,6457
10
+ sera/libs/base_service.py,sha256=NY-PLiaS1SXs2Pp3yVsdGnpJGUxKbHb0qzCCguhgPP4,6432
11
11
  sera/libs/directed_computing_graph/__init__.py,sha256=xiF5_I1y9HtQ-cyq02iwkRYgEZvxBB8YIvysCHCLBco,1290
12
- sera/libs/directed_computing_graph/_dcg.py,sha256=AGTzKVSl-EsSOJlNKPOA1Io7pIxfq0SMXuumq1IExl0,14902
12
+ sera/libs/directed_computing_graph/_dcg.py,sha256=hn5IMoq3iYNy_NYyusSJ41vz8D_UTBJi22rbOLWvqv8,14928
13
13
  sera/libs/directed_computing_graph/_edge.py,sha256=iBq6cpLWWyuD99QWTHVEh8naWUJrR4WJJuq5iuCrwHo,1026
14
14
  sera/libs/directed_computing_graph/_flow.py,sha256=6v39yKPIDYrQ3KvFqjeAWs88-oQSnDTaED2F3LF2z_I,478
15
15
  sera/libs/directed_computing_graph/_fn_signature.py,sha256=73iPUITcRKW0-l6sqjwMSk_FZnJESaKOmUKDGHTOh9Q,1598
@@ -26,13 +26,13 @@ sera/make/make_python_api.py,sha256=aOm8QSiXNLe4akiOx_KKsDCwLVwetRzuxOgZaWqEj0w,
26
26
  sera/make/make_python_model.py,sha256=t_2RUaO6WL4b5FtYj-Ly70njmcGGYwoddbL_oim9378,63479
27
27
  sera/make/make_python_services.py,sha256=0ZpWLwQ7Nwfn8BXAikAB4JRpNknpSJyJgY5b1cjtxV4,2073
28
28
  sera/make/make_typescript_model.py,sha256=CZc_LMigy7e6tP-6bJ5gHZlHYeyRtU_CNR4s64x51xE,71369
29
- sera/misc/__init__.py,sha256=wj59TDqHMQUehdYChO179_NqjZmpqykfkoHaVOtNm_Y,651
29
+ sera/misc/__init__.py,sha256=IRsVxEb_P-KX4Nx_iBkm2gQAsH_wmwcRD1FUUTLYSGg,691
30
30
  sera/misc/_formatter.py,sha256=aCGYL08l8f3aLODHxSocxBBwkRYEo3K1QzCDEn3suj0,1685
31
- sera/misc/_utils.py,sha256=yuWnlFA1LbiVKSS4RgnD18EtGCVCzQBj0vRZjxrbcEc,11072
31
+ sera/misc/_utils.py,sha256=EgsWGN9jFf2TDb9PJW49w60C1w_BuMJWBCehhgZQP5M,11823
32
32
  sera/models/__init__.py,sha256=vJC5Kzo_N7wd16ocNPy1VvAZDGNiWeiAhWJ4ihATKvA,780
33
33
  sera/models/_class.py,sha256=1J4Bd_LanzhhDWwZFHWGtFYD7lupe_alaB3D02ebNDI,2862
34
34
  sera/models/_collection.py,sha256=nLlP85OfEhfj4pFAYxB5ZvtBN6EdPSWPJ5ZFh5SSEC4,2686
35
- sera/models/_constraints.py,sha256=RpWDU-TfCslXaMUaTG9utWbl5z8Z6nzvF_fhqlek6ew,1987
35
+ sera/models/_constraints.py,sha256=oRniaFXUsnQ_Q8lwXVcnBhvHG-Gw6eY31eIkFd1UXI4,2015
36
36
  sera/models/_datatype.py,sha256=_pmhCu3e1qQQuOio5joRzAkPOpIDoVRoDJ4_OrZl0Pw,11224
37
37
  sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
38
38
  sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
@@ -42,6 +42,6 @@ sera/models/_parse.py,sha256=9iaW-8Ajq8e440FrTJ-X9oivHJJ447kPU-ZqffFxSvw,12649
42
42
  sera/models/_property.py,sha256=9yMDxrmbyuF6-29lQjiq163Xzwbk75TlmGBpu0NLpkI,7485
43
43
  sera/models/_schema.py,sha256=VxJEiqgVvbXgcSUK4UW6JnRcggk4nsooVSE6MyXmfNY,1636
44
44
  sera/typing.py,sha256=m4rir-fB6Cgcm7_ZSXXcNdla2LJgq96WXxtTTrDaJno,1058
45
- sera_2-1.20.14.dist-info/METADATA,sha256=nCwKDJULbmdtdp-eXbklxPV1UHUUa1owIzuB8zLhpwo,937
46
- sera_2-1.20.14.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
47
- sera_2-1.20.14.dist-info/RECORD,,
45
+ sera_2-1.21.0.dist-info/METADATA,sha256=f4PIinx1GYO4XYQ1Oe2KdGVgtDqyZugjR8PR4YWUUJg,936
46
+ sera_2-1.21.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
47
+ sera_2-1.21.0.dist-info/RECORD,,