moose-lib 0.6.148.dev3442438466__py3-none-any.whl → 0.6.283__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/__init__.py +34 -3
- moose_lib/blocks.py +416 -52
- moose_lib/clients/redis_client.py +26 -14
- moose_lib/commons.py +37 -30
- moose_lib/config/config_file.py +5 -1
- moose_lib/config/runtime.py +73 -34
- moose_lib/data_models.py +331 -61
- moose_lib/dmv2/__init__.py +69 -73
- moose_lib/dmv2/_registry.py +2 -1
- moose_lib/dmv2/_source_capture.py +37 -0
- moose_lib/dmv2/consumption.py +55 -32
- moose_lib/dmv2/ingest_api.py +9 -2
- moose_lib/dmv2/ingest_pipeline.py +35 -16
- moose_lib/dmv2/life_cycle.py +3 -1
- moose_lib/dmv2/materialized_view.py +24 -14
- moose_lib/dmv2/moose_model.py +165 -0
- moose_lib/dmv2/olap_table.py +299 -151
- moose_lib/dmv2/registry.py +18 -3
- moose_lib/dmv2/sql_resource.py +16 -8
- moose_lib/dmv2/stream.py +75 -23
- moose_lib/dmv2/types.py +14 -8
- moose_lib/dmv2/view.py +13 -6
- moose_lib/dmv2/web_app.py +11 -6
- moose_lib/dmv2/web_app_helpers.py +5 -1
- moose_lib/dmv2/workflow.py +37 -9
- moose_lib/internal.py +340 -56
- moose_lib/main.py +87 -56
- moose_lib/query_builder.py +18 -5
- moose_lib/query_param.py +54 -20
- moose_lib/secrets.py +122 -0
- moose_lib/streaming/streaming_function_runner.py +233 -117
- moose_lib/utilities/sql.py +0 -1
- {moose_lib-0.6.148.dev3442438466.dist-info → moose_lib-0.6.283.dist-info}/METADATA +18 -1
- moose_lib-0.6.283.dist-info/RECORD +63 -0
- tests/__init__.py +1 -1
- tests/conftest.py +6 -5
- tests/test_backward_compatibility.py +85 -0
- tests/test_cluster_validation.py +85 -0
- tests/test_codec.py +75 -0
- tests/test_column_formatting.py +80 -0
- tests/test_fixedstring.py +43 -0
- tests/test_iceberg_config.py +105 -0
- tests/test_int_types.py +211 -0
- tests/test_kafka_config.py +141 -0
- tests/test_materialized.py +74 -0
- tests/test_metadata.py +37 -0
- tests/test_moose.py +21 -30
- tests/test_moose_model.py +153 -0
- tests/test_olap_table_moosemodel.py +89 -0
- tests/test_olap_table_versioning.py +52 -58
- tests/test_query_builder.py +97 -9
- tests/test_redis_client.py +10 -3
- tests/test_s3queue_config.py +211 -110
- tests/test_secrets.py +239 -0
- tests/test_simple_aggregate.py +42 -40
- tests/test_web_app.py +11 -5
- moose_lib-0.6.148.dev3442438466.dist-info/RECORD +0 -47
- {moose_lib-0.6.148.dev3442438466.dist-info → moose_lib-0.6.283.dist-info}/WHEEL +0 -0
- {moose_lib-0.6.148.dev3442438466.dist-info → moose_lib-0.6.283.dist-info}/top_level.txt +0 -0
moose_lib/dmv2/__init__.py
CHANGED
|
@@ -15,6 +15,10 @@ from .types import (
|
|
|
15
15
|
ZeroOrMany,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
from .moose_model import (
|
|
19
|
+
MooseModel,
|
|
20
|
+
)
|
|
21
|
+
|
|
18
22
|
from .olap_table import (
|
|
19
23
|
OlapConfig,
|
|
20
24
|
OlapTable,
|
|
@@ -114,88 +118,80 @@ from .registry import (
|
|
|
114
118
|
|
|
115
119
|
__all__ = [
|
|
116
120
|
# Types
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
"BaseTypedResource",
|
|
122
|
+
"TypedMooseResource",
|
|
123
|
+
"Columns",
|
|
124
|
+
"MooseModel",
|
|
125
|
+
"T",
|
|
126
|
+
"U",
|
|
127
|
+
"T_none",
|
|
128
|
+
"U_none",
|
|
129
|
+
"ZeroOrMany",
|
|
126
130
|
# OLAP Tables
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
"OlapConfig",
|
|
132
|
+
"OlapTable",
|
|
133
|
+
"InsertOptions",
|
|
131
134
|
# Streams
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
"StreamConfig",
|
|
136
|
+
"TransformConfig",
|
|
137
|
+
"ConsumerConfig",
|
|
138
|
+
"Stream",
|
|
139
|
+
"DeadLetterModel",
|
|
140
|
+
"DeadLetterQueue",
|
|
141
|
+
"SubjectLatest",
|
|
142
|
+
"SubjectVersion",
|
|
143
|
+
"SchemaById",
|
|
144
|
+
"KafkaSchemaConfig",
|
|
143
145
|
# Ingestion
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
"IngestConfig",
|
|
147
|
+
"IngestConfigWithDestination",
|
|
148
|
+
"IngestPipelineConfig",
|
|
149
|
+
"IngestApi",
|
|
150
|
+
"IngestPipeline",
|
|
150
151
|
# Consumption
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
"ApiConfig",
|
|
153
|
+
"Api",
|
|
154
|
+
"get_moose_base_url",
|
|
155
|
+
"set_moose_base_url",
|
|
155
156
|
# Backward compatibility aliases (deprecated)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
"ConsumptionApi",
|
|
158
|
+
"EgressConfig",
|
|
159
159
|
# SQL
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
"SqlResource",
|
|
161
|
+
"View",
|
|
162
|
+
"MaterializedViewOptions",
|
|
163
|
+
"MaterializedView",
|
|
165
164
|
# Workflow
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
165
|
+
"TaskContext",
|
|
166
|
+
"TaskConfig",
|
|
167
|
+
"Task",
|
|
168
|
+
"WorkflowConfig",
|
|
169
|
+
"Workflow",
|
|
172
170
|
# Lifecycle
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
"LifeCycle",
|
|
175
172
|
# WebApp
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
"WebApp",
|
|
174
|
+
"WebAppConfig",
|
|
175
|
+
"WebAppMetadata",
|
|
176
|
+
"ApiUtil",
|
|
177
|
+
"get_moose_utils",
|
|
178
|
+
"get_moose_dependency",
|
|
183
179
|
# Registry
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
180
|
+
"get_tables",
|
|
181
|
+
"get_table",
|
|
182
|
+
"get_streams",
|
|
183
|
+
"get_stream",
|
|
184
|
+
"get_ingest_apis",
|
|
185
|
+
"get_ingest_api",
|
|
186
|
+
"get_apis",
|
|
187
|
+
"get_api",
|
|
188
|
+
"get_sql_resources",
|
|
189
|
+
"get_sql_resource",
|
|
190
|
+
"get_workflows",
|
|
191
|
+
"get_workflow",
|
|
192
|
+
"get_web_apps",
|
|
193
|
+
"get_web_app",
|
|
198
194
|
# Backward compatibility aliases (deprecated)
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
"get_consumption_apis",
|
|
196
|
+
"get_consumption_api",
|
|
201
197
|
]
|
moose_lib/dmv2/_registry.py
CHANGED
|
@@ -4,6 +4,7 @@ Internal registry dictionaries for Moose Data Model v2 (dmv2) resources.
|
|
|
4
4
|
This module maintains the raw dictionaries that store all registered resources.
|
|
5
5
|
It has no imports from other dmv2 modules to avoid circular dependencies.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from typing import Dict, Any
|
|
8
9
|
|
|
9
10
|
# Global registries for all resource types
|
|
@@ -17,4 +18,4 @@ _api_name_aliases: Dict[str, Any] = {}
|
|
|
17
18
|
_api_path_map: Dict[str, Any] = {}
|
|
18
19
|
_sql_resources: Dict[str, Any] = {}
|
|
19
20
|
_workflows: Dict[str, Any] = {}
|
|
20
|
-
_web_apps: Dict[str, Any] = {}
|
|
21
|
+
_web_apps: Dict[str, Any] = {}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for capturing source file information from the call stack.
|
|
3
|
+
|
|
4
|
+
This module provides functions to extract source file paths from Python's
|
|
5
|
+
call stack, filtering out internal library paths.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import inspect
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_source_file_from_stack() -> Optional[str]:
|
|
13
|
+
"""Extract the source file path from the call stack, skipping internal modules.
|
|
14
|
+
|
|
15
|
+
Returns the first file path that is not from internal moose_lib modules,
|
|
16
|
+
site-packages, or special Python frames.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The absolute path to the user's source file, or None if not found.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
# Get the current call stack
|
|
23
|
+
stack = inspect.stack()
|
|
24
|
+
# Start from index 1 to skip this function itself
|
|
25
|
+
for frame_info in stack[1:]:
|
|
26
|
+
filename = frame_info.filename
|
|
27
|
+
# Skip internal modules and site-packages
|
|
28
|
+
if (
|
|
29
|
+
"site-packages" not in filename
|
|
30
|
+
and "moose_lib" not in filename
|
|
31
|
+
and "<" not in filename # Skip special frames like <frozen importlib>
|
|
32
|
+
):
|
|
33
|
+
return filename
|
|
34
|
+
except Exception:
|
|
35
|
+
# If anything goes wrong, just return None
|
|
36
|
+
pass
|
|
37
|
+
return None
|
moose_lib/dmv2/consumption.py
CHANGED
|
@@ -4,6 +4,7 @@ API definitions for Moose Data Model v2 (dmv2).
|
|
|
4
4
|
This module provides classes for defining and configuring APIs
|
|
5
5
|
that allow querying data through user-defined functions.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import os
|
|
8
9
|
from typing import Any, Callable, Optional, Tuple, Generic
|
|
9
10
|
|
|
@@ -17,13 +18,14 @@ from ._registry import _apis, _api_name_aliases, _api_path_map
|
|
|
17
18
|
# Global base URL configuration
|
|
18
19
|
_global_base_url: Optional[str] = None
|
|
19
20
|
|
|
21
|
+
|
|
20
22
|
def _generate_api_key(name: str, version: Optional[str] = None) -> str:
|
|
21
23
|
return f"{name}:{version}" if version else name
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def set_moose_base_url(url: str) -> None:
|
|
25
27
|
"""Set the global base URL for API calls.
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
Args:
|
|
28
30
|
url: The base URL to use for API calls
|
|
29
31
|
"""
|
|
@@ -33,14 +35,14 @@ def set_moose_base_url(url: str) -> None:
|
|
|
33
35
|
|
|
34
36
|
def get_moose_base_url() -> Optional[str]:
|
|
35
37
|
"""Get the configured base URL from global setting or environment variable.
|
|
36
|
-
|
|
38
|
+
|
|
37
39
|
Returns:
|
|
38
40
|
The base URL if configured, None otherwise
|
|
39
41
|
"""
|
|
40
42
|
# Priority: programmatically set > environment variable
|
|
41
43
|
if _global_base_url:
|
|
42
44
|
return _global_base_url
|
|
43
|
-
return os.getenv(
|
|
45
|
+
return os.getenv("MOOSE_BASE_URL")
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
class ApiConfig(BaseModel):
|
|
@@ -51,6 +53,7 @@ class ApiConfig(BaseModel):
|
|
|
51
53
|
path: Optional custom path for the API endpoint. If not specified, defaults to the API name.
|
|
52
54
|
metadata: Optional metadata for the API.
|
|
53
55
|
"""
|
|
56
|
+
|
|
54
57
|
version: Optional[str] = None
|
|
55
58
|
path: Optional[str] = None
|
|
56
59
|
metadata: Optional[dict] = None
|
|
@@ -78,6 +81,7 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
78
81
|
model_type (type[T]): The Pydantic model for the input/query parameters.
|
|
79
82
|
return_type (type[U]): The Pydantic model for the response body.
|
|
80
83
|
"""
|
|
84
|
+
|
|
81
85
|
config: ApiConfig
|
|
82
86
|
query_function: Callable[..., U]
|
|
83
87
|
_u: type[U]
|
|
@@ -85,7 +89,9 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
85
89
|
def __class_getitem__(cls, items):
|
|
86
90
|
# Handle two type parameters
|
|
87
91
|
if not isinstance(items, tuple) or len(items) != 2:
|
|
88
|
-
raise ValueError(
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Use `{cls.__name__}[T, U](name='...')` to supply both input and output types"
|
|
94
|
+
)
|
|
89
95
|
input_type, output_type = items
|
|
90
96
|
|
|
91
97
|
def curried_constructor(*args, **kwargs):
|
|
@@ -93,19 +99,23 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
93
99
|
|
|
94
100
|
return curried_constructor
|
|
95
101
|
|
|
96
|
-
def __init__(
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
name: str,
|
|
105
|
+
query_function: Callable[..., U],
|
|
106
|
+
config: ApiConfig = None,
|
|
107
|
+
version: str = None,
|
|
108
|
+
**kwargs,
|
|
109
|
+
):
|
|
97
110
|
super().__init__()
|
|
98
111
|
self._set_type(name, self._get_type(kwargs))
|
|
99
|
-
|
|
112
|
+
|
|
100
113
|
# Handle config and version parameters properly
|
|
101
114
|
if config is not None:
|
|
102
115
|
# If config is provided, use it as base
|
|
103
116
|
if version is not None:
|
|
104
117
|
# If version is also provided, update the config's version
|
|
105
|
-
self.config = ApiConfig(
|
|
106
|
-
version=version,
|
|
107
|
-
metadata=config.metadata
|
|
108
|
-
)
|
|
118
|
+
self.config = ApiConfig(version=version, metadata=config.metadata)
|
|
109
119
|
else:
|
|
110
120
|
# Use the provided config as-is
|
|
111
121
|
self.config = config
|
|
@@ -117,7 +127,7 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
117
127
|
self.config = ApiConfig()
|
|
118
128
|
|
|
119
129
|
self.query_function = query_function
|
|
120
|
-
self.metadata = getattr(self.config,
|
|
130
|
+
self.metadata = getattr(self.config, "metadata", {}) or {}
|
|
121
131
|
key = _generate_api_key(name, self.config.version)
|
|
122
132
|
|
|
123
133
|
# Check for duplicate registration
|
|
@@ -143,13 +153,15 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
143
153
|
else:
|
|
144
154
|
# For versioned APIs, check if version is already in the path
|
|
145
155
|
path_ends_with_version = (
|
|
146
|
-
self.config.path.endswith(f"/{self.config.version}")
|
|
147
|
-
self.config.path == self.config.version
|
|
148
|
-
(
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
self.config.path.endswith(f"/{self.config.version}")
|
|
157
|
+
or self.config.path == self.config.version
|
|
158
|
+
or (
|
|
159
|
+
self.config.path.endswith(self.config.version)
|
|
160
|
+
and len(self.config.path) > len(self.config.version)
|
|
161
|
+
and self.config.path[-(len(self.config.version) + 1)] == "/"
|
|
162
|
+
)
|
|
151
163
|
)
|
|
152
|
-
|
|
164
|
+
|
|
153
165
|
if path_ends_with_version:
|
|
154
166
|
# Path already contains version, check for collision
|
|
155
167
|
if self.config.path in _api_path_map:
|
|
@@ -161,8 +173,10 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
161
173
|
_api_path_map[self.config.path] = self
|
|
162
174
|
else:
|
|
163
175
|
# Path doesn't contain version, register with version appended
|
|
164
|
-
path_with_version =
|
|
165
|
-
|
|
176
|
+
path_with_version = (
|
|
177
|
+
f"{self.config.path.rstrip('/')}/{self.config.version}"
|
|
178
|
+
)
|
|
179
|
+
|
|
166
180
|
# Check for collision on versioned path
|
|
167
181
|
if path_with_version in _api_path_map:
|
|
168
182
|
existing = _api_path_map[path_with_version]
|
|
@@ -171,7 +185,7 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
171
185
|
f'this path is already used by API "{existing.name}"'
|
|
172
186
|
)
|
|
173
187
|
_api_path_map[path_with_version] = self
|
|
174
|
-
|
|
188
|
+
|
|
175
189
|
# Also register the unversioned path if not already claimed
|
|
176
190
|
# (This is intentionally more permissive - first API gets the unversioned path)
|
|
177
191
|
if self.config.path not in _api_path_map:
|
|
@@ -209,9 +223,11 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
209
223
|
|
|
210
224
|
@classmethod
|
|
211
225
|
def _get_type(cls, keyword_args: dict):
|
|
212
|
-
t = keyword_args.get(
|
|
226
|
+
t = keyword_args.get("t")
|
|
213
227
|
if not isinstance(t, tuple) or len(t) != 2:
|
|
214
|
-
raise ValueError(
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"Use `{cls.__name__}[T, U](name='...')` to supply both input and output types"
|
|
230
|
+
)
|
|
215
231
|
|
|
216
232
|
input_type, output_type = t
|
|
217
233
|
if not isinstance(input_type, type) or not issubclass(input_type, BaseModel):
|
|
@@ -237,21 +253,22 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
237
253
|
A dictionary representing the JSON schema.
|
|
238
254
|
"""
|
|
239
255
|
from pydantic.type_adapter import TypeAdapter
|
|
256
|
+
|
|
240
257
|
return TypeAdapter(self.return_type()).json_schema(
|
|
241
|
-
ref_template=
|
|
258
|
+
ref_template="#/components/schemas/{model}"
|
|
242
259
|
)
|
|
243
260
|
|
|
244
261
|
def call(self, params: T, base_url: Optional[str] = None) -> U:
|
|
245
262
|
"""Call the API with the given parameters.
|
|
246
|
-
|
|
263
|
+
|
|
247
264
|
Args:
|
|
248
265
|
params: Parameters matching the input model T
|
|
249
266
|
base_url: Optional base URL override. If not provided, uses the global
|
|
250
267
|
base URL set via set_base_url() or MOOSE_BASE_URL environment variable.
|
|
251
|
-
|
|
268
|
+
|
|
252
269
|
Returns:
|
|
253
270
|
Response data matching the output model U
|
|
254
|
-
|
|
271
|
+
|
|
255
272
|
Raises:
|
|
256
273
|
ValueError: If no base URL is configured
|
|
257
274
|
"""
|
|
@@ -268,11 +285,13 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
268
285
|
# Check if the custom path already contains the version
|
|
269
286
|
if self.config.version:
|
|
270
287
|
path_ends_with_version = (
|
|
271
|
-
self.config.path.endswith(f"/{self.config.version}")
|
|
272
|
-
self.config.path == self.config.version
|
|
273
|
-
(
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
self.config.path.endswith(f"/{self.config.version}")
|
|
289
|
+
or self.config.path == self.config.version
|
|
290
|
+
or (
|
|
291
|
+
self.config.path.endswith(self.config.version)
|
|
292
|
+
and len(self.config.path) > len(self.config.version)
|
|
293
|
+
and self.config.path[-(len(self.config.version) + 1)] == "/"
|
|
294
|
+
)
|
|
276
295
|
)
|
|
277
296
|
if path_ends_with_version:
|
|
278
297
|
path = self.config.path
|
|
@@ -282,7 +301,11 @@ class Api(BaseTypedResource, Generic[U]):
|
|
|
282
301
|
path = self.config.path
|
|
283
302
|
else:
|
|
284
303
|
# Default to name with optional version
|
|
285
|
-
path =
|
|
304
|
+
path = (
|
|
305
|
+
self.name
|
|
306
|
+
if not self.config.version
|
|
307
|
+
else f"{self.name}/{self.config.version}"
|
|
308
|
+
)
|
|
286
309
|
url = f"{effective_base_url.rstrip('/')}/api/{path}"
|
|
287
310
|
|
|
288
311
|
# Convert Pydantic model to dictionary
|
moose_lib/dmv2/ingest_api.py
CHANGED
|
@@ -4,6 +4,7 @@ Ingestion API definitions for Moose Data Model v2 (dmv2).
|
|
|
4
4
|
This module provides classes for defining and configuring ingestion APIs
|
|
5
5
|
that receive data and send it to streams.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import dataclasses
|
|
8
9
|
from typing import Any, Optional, Generic
|
|
9
10
|
from pydantic import BaseModel
|
|
@@ -12,6 +13,7 @@ from .types import TypedMooseResource, T
|
|
|
12
13
|
from .stream import Stream, DeadLetterQueue
|
|
13
14
|
from ._registry import _ingest_apis
|
|
14
15
|
|
|
16
|
+
|
|
15
17
|
class IngestConfig(BaseModel):
|
|
16
18
|
"""Basic configuration for an ingestion point.
|
|
17
19
|
|
|
@@ -20,10 +22,12 @@ class IngestConfig(BaseModel):
|
|
|
20
22
|
path: Optional custom path for the ingestion endpoint.
|
|
21
23
|
metadata: Optional metadata for the ingestion point.
|
|
22
24
|
"""
|
|
25
|
+
|
|
23
26
|
version: Optional[str] = None
|
|
24
27
|
path: Optional[str] = None
|
|
25
28
|
metadata: Optional[dict] = None
|
|
26
29
|
|
|
30
|
+
|
|
27
31
|
@dataclasses.dataclass
|
|
28
32
|
class IngestConfigWithDestination[T: BaseModel]:
|
|
29
33
|
"""Ingestion configuration that includes the mandatory destination stream.
|
|
@@ -35,12 +39,14 @@ class IngestConfigWithDestination[T: BaseModel]:
|
|
|
35
39
|
path: Optional custom path for the ingestion endpoint.
|
|
36
40
|
metadata: Optional metadata for the ingestion configuration.
|
|
37
41
|
"""
|
|
42
|
+
|
|
38
43
|
destination: Stream[T]
|
|
39
44
|
dead_letter_queue: Optional[DeadLetterQueue[T]] = None
|
|
40
45
|
version: Optional[str] = None
|
|
41
46
|
path: Optional[str] = None
|
|
42
47
|
metadata: Optional[dict] = None
|
|
43
48
|
|
|
49
|
+
|
|
44
50
|
class IngestApi(TypedMooseResource, Generic[T]):
|
|
45
51
|
"""Represents an Ingestion API endpoint typed with a Pydantic model.
|
|
46
52
|
|
|
@@ -59,11 +65,12 @@ class IngestApi(TypedMooseResource, Generic[T]):
|
|
|
59
65
|
name (str): The name of the API.
|
|
60
66
|
model_type (type[T]): The Pydantic model associated with this API's input.
|
|
61
67
|
"""
|
|
68
|
+
|
|
62
69
|
config: IngestConfigWithDestination[T]
|
|
63
70
|
|
|
64
71
|
def __init__(self, name: str, config: IngestConfigWithDestination[T], **kwargs):
|
|
65
72
|
super().__init__()
|
|
66
73
|
self._set_type(name, self._get_type(kwargs))
|
|
67
74
|
self.config = config
|
|
68
|
-
self.metadata = getattr(config,
|
|
69
|
-
_ingest_apis[name] = self
|
|
75
|
+
self.metadata = getattr(config, "metadata", None)
|
|
76
|
+
_ingest_apis[name] = self
|
|
@@ -4,6 +4,7 @@ Ingestion Pipeline definitions for Moose Data Model v2 (dmv2).
|
|
|
4
4
|
This module provides classes for defining and configuring complete ingestion pipelines,
|
|
5
5
|
which combine tables, streams, and ingestion APIs into a single cohesive unit.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import warnings
|
|
8
9
|
from typing import Any, Optional, Generic, TypeVar
|
|
9
10
|
from pydantic import BaseModel, model_validator
|
|
@@ -14,6 +15,7 @@ from .stream import Stream, StreamConfig, DeadLetterQueue
|
|
|
14
15
|
from .ingest_api import IngestApi, IngestConfig, IngestConfigWithDestination
|
|
15
16
|
from .life_cycle import LifeCycle
|
|
16
17
|
|
|
18
|
+
|
|
17
19
|
class IngestPipelineConfig(BaseModel):
|
|
18
20
|
"""Configuration for creating a complete ingestion pipeline.
|
|
19
21
|
|
|
@@ -31,6 +33,7 @@ class IngestPipelineConfig(BaseModel):
|
|
|
31
33
|
metadata: Optional metadata for the ingestion pipeline.
|
|
32
34
|
life_cycle: Determines how changes in code will propagate to the resources.
|
|
33
35
|
"""
|
|
36
|
+
|
|
34
37
|
table: bool | OlapConfig = True
|
|
35
38
|
stream: bool | StreamConfig = True
|
|
36
39
|
ingest_api: bool | IngestConfig = True
|
|
@@ -39,30 +42,31 @@ class IngestPipelineConfig(BaseModel):
|
|
|
39
42
|
path: Optional[str] = None
|
|
40
43
|
metadata: Optional[dict] = None
|
|
41
44
|
life_cycle: Optional[LifeCycle] = None
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
# Legacy support - will be removed in future version
|
|
44
47
|
ingest: Optional[bool | IngestConfig] = None
|
|
45
|
-
|
|
46
|
-
@model_validator(mode=
|
|
48
|
+
|
|
49
|
+
@model_validator(mode="before")
|
|
47
50
|
@classmethod
|
|
48
51
|
def handle_legacy_ingest_param(cls, data):
|
|
49
52
|
"""Handle backwards compatibility for the deprecated 'ingest' parameter."""
|
|
50
|
-
if isinstance(data, dict) and
|
|
53
|
+
if isinstance(data, dict) and "ingest" in data:
|
|
51
54
|
warnings.warn(
|
|
52
55
|
"The 'ingest' parameter is deprecated and will be removed in a future version. "
|
|
53
56
|
"Please use 'ingest_api' instead.",
|
|
54
57
|
DeprecationWarning,
|
|
55
|
-
stacklevel=3
|
|
58
|
+
stacklevel=3,
|
|
56
59
|
)
|
|
57
60
|
# Make a copy first to avoid mutating the original dictionary
|
|
58
61
|
data = data.copy()
|
|
59
62
|
# If ingest_api is not explicitly set, use the ingest value
|
|
60
|
-
if
|
|
61
|
-
data[
|
|
63
|
+
if "ingest_api" not in data:
|
|
64
|
+
data["ingest_api"] = data["ingest"]
|
|
62
65
|
# Remove the legacy parameter
|
|
63
|
-
del data[
|
|
66
|
+
del data["ingest"]
|
|
64
67
|
return data
|
|
65
68
|
|
|
69
|
+
|
|
66
70
|
class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
67
71
|
"""Creates and configures a linked Table, Stream, and Ingest API pipeline.
|
|
68
72
|
|
|
@@ -84,6 +88,7 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
84
88
|
name (str): The base name of the pipeline.
|
|
85
89
|
model_type (type[T]): The Pydantic model associated with this pipeline.
|
|
86
90
|
"""
|
|
91
|
+
|
|
87
92
|
table: Optional[OlapTable[T]] = None
|
|
88
93
|
stream: Optional[Stream[T]] = None
|
|
89
94
|
ingest_api: Optional[IngestApi[T]] = None
|
|
@@ -150,23 +155,37 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
150
155
|
stream_metadata = config.metadata
|
|
151
156
|
ingest_metadata = config.metadata
|
|
152
157
|
if config.table:
|
|
153
|
-
table_config = (
|
|
154
|
-
|
|
158
|
+
table_config = (
|
|
159
|
+
config.table
|
|
160
|
+
if isinstance(config.table, OlapConfig)
|
|
161
|
+
else OlapConfig(life_cycle=config.life_cycle)
|
|
162
|
+
)
|
|
155
163
|
if config.version:
|
|
156
164
|
table_config.version = config.version
|
|
157
165
|
table_config.metadata = table_metadata
|
|
158
166
|
self.table = OlapTable(name, table_config, t=self._t)
|
|
159
167
|
if config.dead_letter_queue:
|
|
160
|
-
dlq_stream_config =
|
|
168
|
+
dlq_stream_config = (
|
|
169
|
+
StreamConfig()
|
|
170
|
+
if config.dead_letter_queue is True
|
|
171
|
+
else config.dead_letter_queue
|
|
172
|
+
)
|
|
161
173
|
if config.version:
|
|
162
174
|
dlq_stream_config.version = config.version
|
|
163
175
|
dlq_stream_config.metadata = stream_metadata
|
|
164
|
-
self.dead_letter_queue = DeadLetterQueue(
|
|
176
|
+
self.dead_letter_queue = DeadLetterQueue(
|
|
177
|
+
f"{name}DeadLetterQueue", dlq_stream_config, t=self._t
|
|
178
|
+
)
|
|
165
179
|
if config.stream:
|
|
166
|
-
stream_config = (
|
|
167
|
-
|
|
180
|
+
stream_config = (
|
|
181
|
+
config.stream
|
|
182
|
+
if isinstance(config.stream, StreamConfig)
|
|
183
|
+
else StreamConfig(life_cycle=config.life_cycle)
|
|
184
|
+
)
|
|
168
185
|
if config.table and stream_config.destination is not None:
|
|
169
|
-
raise ValueError(
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"The destination of the stream should be the table created in the IngestPipeline"
|
|
188
|
+
)
|
|
170
189
|
stream_config.destination = self.table
|
|
171
190
|
if self.dead_letter_queue is not None:
|
|
172
191
|
stream_config.default_dead_letter_queue = self.dead_letter_queue
|
|
@@ -189,4 +208,4 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
189
208
|
ingest_config_dict["dead_letter_queue"] = self.dead_letter_queue
|
|
190
209
|
ingest_config_dict["metadata"] = ingest_metadata
|
|
191
210
|
ingest_config = IngestConfigWithDestination(**ingest_config_dict)
|
|
192
|
-
self.ingest_api = IngestApi(name, ingest_config, t=self._t)
|
|
211
|
+
self.ingest_api = IngestApi(name, ingest_config, t=self._t)
|
moose_lib/dmv2/life_cycle.py
CHANGED
|
@@ -4,8 +4,10 @@ Lifecycle management definitions for Moose Data Model v2 (dmv2).
|
|
|
4
4
|
This module defines how Moose manages the lifecycle of database resources
|
|
5
5
|
when your code changes.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from enum import Enum
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
class LifeCycle(Enum):
|
|
10
12
|
"""Defines how Moose manages the lifecycle of database resources when your code changes.
|
|
11
13
|
|
|
@@ -29,4 +31,4 @@ class LifeCycle(Enum):
|
|
|
29
31
|
"""External management - no automatic changes.
|
|
30
32
|
Moose will not modify the database resources. You are responsible for managing
|
|
31
33
|
the schema and ensuring it matches your code definitions manually.
|
|
32
|
-
"""
|
|
34
|
+
"""
|