moose-lib 0.4.211__tar.gz → 0.4.213__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.
- {moose_lib-0.4.211 → moose_lib-0.4.213}/PKG-INFO +2 -1
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/dmv2.py +157 -1
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/internal.py +30 -2
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib.egg-info/PKG-INFO +2 -1
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib.egg-info/requires.txt +1 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/setup.py +1 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/README.md +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/__init__.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/blocks.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/clients/__init__.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/clients/redis_client.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/commons.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/data_models.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/dmv2-serializer.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/main.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/query_param.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/streaming/__init__.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/streaming/streaming_function_runner.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib/tasks.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib.egg-info/SOURCES.txt +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib.egg-info/dependency_links.txt +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/moose_lib.egg-info/top_level.txt +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/setup.cfg +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/tests/__init__.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/tests/conftest.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/tests/test_moose.py +0 -0
- {moose_lib-0.4.211 → moose_lib-0.4.213}/tests/test_redis_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: moose_lib
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.213
|
4
4
|
Home-page: https://www.fiveonefour.com/moose
|
5
5
|
Author: Fiveonefour Labs Inc.
|
6
6
|
Author-email: support@fiveonefour.com
|
@@ -11,6 +11,7 @@ Requires-Dist: pydantic==2.10.6
|
|
11
11
|
Requires-Dist: temporalio==1.9.0
|
12
12
|
Requires-Dist: kafka-python-ng==2.2.2
|
13
13
|
Requires-Dist: redis==6.2.0
|
14
|
+
Requires-Dist: humanfriendly==10.0
|
14
15
|
Dynamic: author
|
15
16
|
Dynamic: author-email
|
16
17
|
Dynamic: description
|
@@ -10,7 +10,7 @@ of data infrastructure using Python and Pydantic models.
|
|
10
10
|
"""
|
11
11
|
import dataclasses
|
12
12
|
import datetime
|
13
|
-
from typing import Any, Generic, Optional, TypeVar, Callable, Union, Literal
|
13
|
+
from typing import Any, Generic, Optional, TypeVar, Callable, Union, Literal, Awaitable
|
14
14
|
from pydantic import BaseModel, ConfigDict, AliasGenerator
|
15
15
|
from pydantic.alias_generators import to_camel
|
16
16
|
from pydantic.fields import FieldInfo
|
@@ -23,11 +23,24 @@ _streams: dict[str, "Stream"] = {}
|
|
23
23
|
_ingest_apis: dict[str, "IngestApi"] = {}
|
24
24
|
_egress_apis: dict[str, "ConsumptionApi"] = {}
|
25
25
|
_sql_resources: dict[str, "SqlResource"] = {}
|
26
|
+
_workflows: dict[str, "Workflow"] = {}
|
26
27
|
|
27
28
|
T = TypeVar('T', bound=BaseModel)
|
28
29
|
U = TypeVar('U', bound=BaseModel)
|
30
|
+
T_none = TypeVar('T_none', bound=Union[BaseModel, None])
|
31
|
+
U_none = TypeVar('U_none', bound=Union[BaseModel, None])
|
29
32
|
type ZeroOrMany[T] = Union[T, list[T], None]
|
30
33
|
|
34
|
+
type TaskRunFunc[T_none, U_none] = Union[
|
35
|
+
# Case 1: No input, no output
|
36
|
+
Callable[[], None],
|
37
|
+
# Case 2: No input, with output
|
38
|
+
Callable[[], Union[U_none, Awaitable[U_none]]],
|
39
|
+
# Case 3: With input, no output
|
40
|
+
Callable[[T_none], None],
|
41
|
+
# Case 4: With input, with output
|
42
|
+
Callable[[T_none], Union[U_none, Awaitable[U_none]]]
|
43
|
+
]
|
31
44
|
|
32
45
|
class Columns(Generic[T]):
|
33
46
|
"""Provides runtime checked column name access for Moose resources.
|
@@ -810,3 +823,146 @@ class MaterializedView(SqlResource, BaseTypedResource, Generic[T]):
|
|
810
823
|
|
811
824
|
self.target_table = target_table
|
812
825
|
self.config = options
|
826
|
+
|
827
|
+
|
828
|
+
@dataclasses.dataclass
|
829
|
+
class TaskConfig(Generic[T_none, U_none]):
|
830
|
+
"""Configuration for a Task.
|
831
|
+
|
832
|
+
Attributes:
|
833
|
+
run: The handler function that executes the task logic.
|
834
|
+
on_complete: Optional list of tasks to run after this task completes.
|
835
|
+
timeout: Optional timeout string (e.g. "5m", "1h").
|
836
|
+
retries: Optional number of retry attempts.
|
837
|
+
"""
|
838
|
+
run: TaskRunFunc[T_none, U_none]
|
839
|
+
on_complete: Optional[list["Task[U_none, Any]"]] = None
|
840
|
+
timeout: Optional[str] = None
|
841
|
+
retries: Optional[int] = None
|
842
|
+
|
843
|
+
|
844
|
+
class Task(TypedMooseResource, Generic[T_none, U_none]):
|
845
|
+
"""Represents a task that can be executed as part of a workflow.
|
846
|
+
|
847
|
+
Tasks are the basic unit of work in a workflow, with typed input and output.
|
848
|
+
They can be chained together using the on_complete configuration.
|
849
|
+
|
850
|
+
Args:
|
851
|
+
name: The name of the task.
|
852
|
+
config: Configuration specifying the task's behavior.
|
853
|
+
t: The Pydantic model defining the task's input schema
|
854
|
+
(passed via `Task[InputModel, OutputModel](...)`).
|
855
|
+
OutputModel can be None for tasks that don't return a value.
|
856
|
+
|
857
|
+
Attributes:
|
858
|
+
config (TaskConfig[T, U]): The configuration for this task.
|
859
|
+
columns (Columns[T]): Helper for accessing input field names safely.
|
860
|
+
name (str): The name of the task.
|
861
|
+
model_type (type[T]): The Pydantic model associated with this task's input.
|
862
|
+
"""
|
863
|
+
config: TaskConfig[T_none, U_none]
|
864
|
+
|
865
|
+
def __init__(self, name: str, config: TaskConfig[T_none, U_none], **kwargs):
|
866
|
+
super().__init__()
|
867
|
+
self._set_type(name, self._get_type(kwargs))
|
868
|
+
self.config = config
|
869
|
+
|
870
|
+
@classmethod
|
871
|
+
def _get_type(cls, keyword_args: dict):
|
872
|
+
t = keyword_args.get('t')
|
873
|
+
if t is None:
|
874
|
+
raise ValueError(f"Use `{cls.__name__}[T, U](name='...')` to supply both input and output types")
|
875
|
+
if not isinstance(t, tuple) or len(t) != 2:
|
876
|
+
raise ValueError(f"Use `{cls.__name__}[T, U](name='...')` to supply both input and output types")
|
877
|
+
|
878
|
+
input_type, output_type = t
|
879
|
+
if input_type is not None and (not isinstance(input_type, type) or not issubclass(input_type, BaseModel)):
|
880
|
+
raise ValueError(f"Input type {input_type} is not a Pydantic model or None")
|
881
|
+
if output_type is not None and (not isinstance(output_type, type) or not issubclass(output_type, BaseModel)):
|
882
|
+
raise ValueError(f"Output type {output_type} is not a Pydantic model or None")
|
883
|
+
return t
|
884
|
+
|
885
|
+
def _set_type(self, name: str, t: tuple[type[T_none], type[U_none]]):
|
886
|
+
input_type, output_type = t
|
887
|
+
self._t = input_type
|
888
|
+
self._u = output_type
|
889
|
+
self.name = name
|
890
|
+
|
891
|
+
|
892
|
+
@dataclasses.dataclass
|
893
|
+
class WorkflowConfig:
|
894
|
+
"""Configuration for a workflow.
|
895
|
+
|
896
|
+
Attributes:
|
897
|
+
starting_task: The first task to execute in the workflow.
|
898
|
+
retries: Optional number of retry attempts for the entire workflow.
|
899
|
+
timeout: Optional timeout string for the entire workflow.
|
900
|
+
schedule: Optional cron-like schedule string for recurring execution.
|
901
|
+
"""
|
902
|
+
starting_task: Task[Any, Any]
|
903
|
+
retries: Optional[int] = None
|
904
|
+
timeout: Optional[str] = None
|
905
|
+
schedule: Optional[str] = None
|
906
|
+
|
907
|
+
|
908
|
+
class Workflow:
|
909
|
+
"""Represents a workflow composed of one or more tasks.
|
910
|
+
|
911
|
+
Workflows define a sequence of tasks to be executed, with optional
|
912
|
+
scheduling, retries, and timeouts at the workflow level.
|
913
|
+
|
914
|
+
Args:
|
915
|
+
name: The name of the workflow.
|
916
|
+
config: Configuration specifying the workflow's behavior.
|
917
|
+
|
918
|
+
Attributes:
|
919
|
+
name (str): The name of the workflow.
|
920
|
+
config (WorkflowConfig): The configuration for this workflow.
|
921
|
+
"""
|
922
|
+
def __init__(self, name: str, config: WorkflowConfig):
|
923
|
+
self.name = name
|
924
|
+
self.config = config
|
925
|
+
# Register the workflow in the internal registry
|
926
|
+
_workflows[name] = self
|
927
|
+
|
928
|
+
def get_task_names(self) -> list[str]:
|
929
|
+
"""Get a list of all task names in this workflow.
|
930
|
+
|
931
|
+
Returns:
|
932
|
+
list[str]: List of task names in the workflow, including all child tasks
|
933
|
+
"""
|
934
|
+
def collect_task_names(task: Task) -> list[str]:
|
935
|
+
names = [task.name]
|
936
|
+
if task.config.on_complete:
|
937
|
+
for child in task.config.on_complete:
|
938
|
+
names.extend(collect_task_names(child))
|
939
|
+
return names
|
940
|
+
|
941
|
+
return collect_task_names(self.config.starting_task)
|
942
|
+
|
943
|
+
def get_task(self, task_name: str) -> Optional[Task]:
|
944
|
+
"""Find a task in this workflow by name.
|
945
|
+
|
946
|
+
Args:
|
947
|
+
task_name: The name of the task to find
|
948
|
+
|
949
|
+
Returns:
|
950
|
+
Optional[Task]: The task if found, None otherwise
|
951
|
+
"""
|
952
|
+
def find_task(task: Task) -> Optional[Task]:
|
953
|
+
if task.name == task_name:
|
954
|
+
return task
|
955
|
+
if task.config.on_complete:
|
956
|
+
for child in task.config.on_complete:
|
957
|
+
found = find_task(child)
|
958
|
+
if found:
|
959
|
+
return found
|
960
|
+
return None
|
961
|
+
|
962
|
+
return find_task(self.config.starting_task)
|
963
|
+
|
964
|
+
def _get_workflows() -> dict[str, Workflow]:
|
965
|
+
return _workflows
|
966
|
+
|
967
|
+
def _get_workflow(name: str) -> Optional[Workflow]:
|
968
|
+
return _workflows.get(name)
|
@@ -11,7 +11,7 @@ from typing import Literal, Optional, List, Any
|
|
11
11
|
from pydantic import BaseModel, ConfigDict, AliasGenerator
|
12
12
|
import json
|
13
13
|
from .data_models import Column, _to_columns
|
14
|
-
from moose_lib.dmv2 import _tables, _streams, _ingest_apis, _egress_apis, SqlResource, _sql_resources
|
14
|
+
from moose_lib.dmv2 import _tables, _streams, _ingest_apis, _egress_apis, SqlResource, _sql_resources, _workflows
|
15
15
|
from moose_lib.dmv2 import OlapTable, View, MaterializedView
|
16
16
|
from pydantic.alias_generators import to_camel
|
17
17
|
from pydantic.json_schema import JsonSchemaValue
|
@@ -131,6 +131,22 @@ class EgressApiConfig(BaseModel):
|
|
131
131
|
version: Optional[str] = None
|
132
132
|
metadata: Optional[dict] = None
|
133
133
|
|
134
|
+
class WorkflowJson(BaseModel):
|
135
|
+
"""Internal representation of a workflow configuration for serialization.
|
136
|
+
|
137
|
+
Attributes:
|
138
|
+
name: Name of the workflow.
|
139
|
+
retries: Optional number of retry attempts for the entire workflow.
|
140
|
+
timeout: Optional timeout string for the entire workflow.
|
141
|
+
schedule: Optional cron-like schedule string for recurring execution.
|
142
|
+
"""
|
143
|
+
model_config = model_config
|
144
|
+
|
145
|
+
name: str
|
146
|
+
retries: Optional[int] = None
|
147
|
+
timeout: Optional[str] = None
|
148
|
+
schedule: Optional[str] = None
|
149
|
+
|
134
150
|
class InfrastructureSignatureJson(BaseModel):
|
135
151
|
"""Represents the unique signature of an infrastructure component (Table, Topic, etc.).
|
136
152
|
|
@@ -175,6 +191,7 @@ class InfrastructureMap(BaseModel):
|
|
175
191
|
ingest_apis: Dictionary mapping ingest API names to their configurations.
|
176
192
|
egress_apis: Dictionary mapping egress API names to their configurations.
|
177
193
|
sql_resources: Dictionary mapping SQL resource names to their configurations.
|
194
|
+
workflows: Dictionary mapping workflow names to their configurations.
|
178
195
|
"""
|
179
196
|
model_config = model_config
|
180
197
|
|
@@ -183,6 +200,7 @@ class InfrastructureMap(BaseModel):
|
|
183
200
|
ingest_apis: dict[str, IngestApiConfig]
|
184
201
|
egress_apis: dict[str, EgressApiConfig]
|
185
202
|
sql_resources: dict[str, SqlResourceConfig]
|
203
|
+
workflows: dict[str, WorkflowJson]
|
186
204
|
|
187
205
|
|
188
206
|
def _map_sql_resource_ref(r: Any) -> InfrastructureSignatureJson:
|
@@ -233,6 +251,7 @@ def to_infra_map() -> dict:
|
|
233
251
|
ingest_apis = {}
|
234
252
|
egress_apis = {}
|
235
253
|
sql_resources = {}
|
254
|
+
workflows = {}
|
236
255
|
|
237
256
|
for name, table in _tables.items():
|
238
257
|
engine = table.config.engine
|
@@ -308,12 +327,21 @@ def to_infra_map() -> dict:
|
|
308
327
|
metadata=getattr(resource, "metadata", None),
|
309
328
|
)
|
310
329
|
|
330
|
+
for name, workflow in _workflows.items():
|
331
|
+
workflows[name] = WorkflowJson(
|
332
|
+
name=workflow.name,
|
333
|
+
retries=workflow.config.retries,
|
334
|
+
timeout=workflow.config.timeout,
|
335
|
+
schedule=workflow.config.schedule,
|
336
|
+
)
|
337
|
+
|
311
338
|
infra_map = InfrastructureMap(
|
312
339
|
tables=tables,
|
313
340
|
topics=topics,
|
314
341
|
ingest_apis=ingest_apis,
|
315
342
|
egress_apis=egress_apis,
|
316
|
-
sql_resources=sql_resources
|
343
|
+
sql_resources=sql_resources,
|
344
|
+
workflows=workflows
|
317
345
|
)
|
318
346
|
|
319
347
|
return infra_map.model_dump(by_alias=True)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: moose_lib
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.213
|
4
4
|
Home-page: https://www.fiveonefour.com/moose
|
5
5
|
Author: Fiveonefour Labs Inc.
|
6
6
|
Author-email: support@fiveonefour.com
|
@@ -11,6 +11,7 @@ Requires-Dist: pydantic==2.10.6
|
|
11
11
|
Requires-Dist: temporalio==1.9.0
|
12
12
|
Requires-Dist: kafka-python-ng==2.2.2
|
13
13
|
Requires-Dist: redis==6.2.0
|
14
|
+
Requires-Dist: humanfriendly==10.0
|
14
15
|
Dynamic: author
|
15
16
|
Dynamic: author-email
|
16
17
|
Dynamic: description
|
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
|