moose-lib 0.4.211__py3-none-any.whl → 0.4.213__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.
moose_lib/dmv2.py CHANGED
@@ -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)
moose_lib/internal.py CHANGED
@@ -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.211
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
@@ -3,8 +3,8 @@ moose_lib/blocks.py,sha256=_wdvC2NC_Y3MMEnB71WTgWbeQ--zPNHk19xjToJW0C0,3185
3
3
  moose_lib/commons.py,sha256=BV5X78MuOWHiZV9bsWSN69JIvzTNWUi-gnuMiAtaO8A,2489
4
4
  moose_lib/data_models.py,sha256=R6do1eQqHK6AZ4GTP5tOPtSZaltjZurfx9_Asji7Dwc,8529
5
5
  moose_lib/dmv2-serializer.py,sha256=CL_Pvvg8tJOT8Qk6hywDNzY8MYGhMVdTOw8arZi3jng,49
6
- moose_lib/dmv2.py,sha256=jSqN9ST7xhYCqUP4kJLDxIHMCsRvbkiTJAd27kb6Nfk,31803
7
- moose_lib/internal.py,sha256=gREvC3XxBFN4i7JL5uMj0riCu_JUO2YyiMZvCokg1ME,13101
6
+ moose_lib/dmv2.py,sha256=87TbxF1cJLUA6vOpPs_zez9Y9obfxadwe1t4KgqrO-U,37599
7
+ moose_lib/internal.py,sha256=nNb1e-grgJ3jpo4H_4_JsKrmH6wL202zWsaVCmvk3yE,14085
8
8
  moose_lib/main.py,sha256=In-u7yA1FsLDeP_2bhIgBtHY_BkXaZqDwf7BxwyC21c,8471
9
9
  moose_lib/query_param.py,sha256=AB5BKu610Ji-h1iYGMBZKfnEFqt85rS94kzhDwhWJnc,6288
10
10
  moose_lib/tasks.py,sha256=6MXA0j7nhvQILAJVTQHCAsquwrSOi2zAevghAc_7kXs,1554
@@ -16,7 +16,7 @@ tests/__init__.py,sha256=0Gh4yzPkkC3TzBGKhenpMIxJcRhyrrCfxLSfpTZnPMQ,53
16
16
  tests/conftest.py,sha256=ZVJNbnr4DwbcqkTmePW6U01zAzE6QD0kNAEZjPG1f4s,169
17
17
  tests/test_moose.py,sha256=mBsx_OYWmL8ppDzL_7Bd7xR6qf_i3-pCIO3wm2iQNaA,2136
18
18
  tests/test_redis_client.py,sha256=d9_MLYsJ4ecVil_jPB2gW3Q5aWnavxmmjZg2uYI3LVo,3256
19
- moose_lib-0.4.211.dist-info/METADATA,sha256=agj4yuAWI5wQByL5C5aaL4jWs8HWyqICf_TrSe0ocWg,603
20
- moose_lib-0.4.211.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- moose_lib-0.4.211.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
22
- moose_lib-0.4.211.dist-info/RECORD,,
19
+ moose_lib-0.4.213.dist-info/METADATA,sha256=T1Cywpt6LEEhL8OFyFb0wy15WKteaV7NHIBG83Pqgng,638
20
+ moose_lib-0.4.213.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ moose_lib-0.4.213.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
22
+ moose_lib-0.4.213.dist-info/RECORD,,