moose-lib 0.6.90__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 +38 -3
- moose_lib/blocks.py +497 -37
- moose_lib/clients/redis_client.py +26 -14
- moose_lib/commons.py +94 -5
- moose_lib/config/config_file.py +44 -2
- moose_lib/config/runtime.py +137 -5
- moose_lib/data_models.py +451 -46
- moose_lib/dmv2/__init__.py +88 -60
- moose_lib/dmv2/_registry.py +3 -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 +56 -13
- 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 +304 -119
- moose_lib/dmv2/registry.py +28 -3
- moose_lib/dmv2/sql_resource.py +16 -8
- moose_lib/dmv2/stream.py +241 -21
- moose_lib/dmv2/types.py +14 -8
- moose_lib/dmv2/view.py +13 -6
- moose_lib/dmv2/web_app.py +175 -0
- moose_lib/dmv2/web_app_helpers.py +96 -0
- moose_lib/dmv2/workflow.py +37 -9
- moose_lib/internal.py +537 -68
- 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 +266 -156
- moose_lib/utilities/sql.py +0 -1
- {moose_lib-0.6.90.dist-info → moose_lib-0.6.283.dist-info}/METADATA +19 -1
- moose_lib-0.6.283.dist-info/RECORD +63 -0
- tests/__init__.py +1 -1
- tests/conftest.py +38 -1
- 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 +210 -0
- 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 +114 -0
- tests/test_web_app.py +227 -0
- moose_lib-0.6.90.dist-info/RECORD +0 -42
- {moose_lib-0.6.90.dist-info → moose_lib-0.6.283.dist-info}/WHEEL +0 -0
- {moose_lib-0.6.90.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,
|
|
@@ -28,6 +32,10 @@ from .stream import (
|
|
|
28
32
|
Stream,
|
|
29
33
|
DeadLetterModel,
|
|
30
34
|
DeadLetterQueue,
|
|
35
|
+
SubjectLatest,
|
|
36
|
+
SubjectVersion,
|
|
37
|
+
SchemaById,
|
|
38
|
+
KafkaSchemaConfig,
|
|
31
39
|
)
|
|
32
40
|
|
|
33
41
|
from .ingest_api import (
|
|
@@ -76,6 +84,18 @@ from .life_cycle import (
|
|
|
76
84
|
LifeCycle,
|
|
77
85
|
)
|
|
78
86
|
|
|
87
|
+
from .web_app import (
|
|
88
|
+
WebApp,
|
|
89
|
+
WebAppConfig,
|
|
90
|
+
WebAppMetadata,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
from .web_app_helpers import (
|
|
94
|
+
ApiUtil,
|
|
95
|
+
get_moose_utils,
|
|
96
|
+
get_moose_dependency,
|
|
97
|
+
)
|
|
98
|
+
|
|
79
99
|
from .registry import (
|
|
80
100
|
get_tables,
|
|
81
101
|
get_table,
|
|
@@ -89,6 +109,8 @@ from .registry import (
|
|
|
89
109
|
get_sql_resource,
|
|
90
110
|
get_workflows,
|
|
91
111
|
get_workflow,
|
|
112
|
+
get_web_apps,
|
|
113
|
+
get_web_app,
|
|
92
114
|
# Backward compatibility aliases
|
|
93
115
|
get_consumption_apis,
|
|
94
116
|
get_consumption_api,
|
|
@@ -96,74 +118,80 @@ from .registry import (
|
|
|
96
118
|
|
|
97
119
|
__all__ = [
|
|
98
120
|
# Types
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
"BaseTypedResource",
|
|
122
|
+
"TypedMooseResource",
|
|
123
|
+
"Columns",
|
|
124
|
+
"MooseModel",
|
|
125
|
+
"T",
|
|
126
|
+
"U",
|
|
127
|
+
"T_none",
|
|
128
|
+
"U_none",
|
|
129
|
+
"ZeroOrMany",
|
|
108
130
|
# OLAP Tables
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
"OlapConfig",
|
|
132
|
+
"OlapTable",
|
|
133
|
+
"InsertOptions",
|
|
113
134
|
# Streams
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
"StreamConfig",
|
|
136
|
+
"TransformConfig",
|
|
137
|
+
"ConsumerConfig",
|
|
138
|
+
"Stream",
|
|
139
|
+
"DeadLetterModel",
|
|
140
|
+
"DeadLetterQueue",
|
|
141
|
+
"SubjectLatest",
|
|
142
|
+
"SubjectVersion",
|
|
143
|
+
"SchemaById",
|
|
144
|
+
"KafkaSchemaConfig",
|
|
121
145
|
# Ingestion
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
"IngestConfig",
|
|
147
|
+
"IngestConfigWithDestination",
|
|
148
|
+
"IngestPipelineConfig",
|
|
149
|
+
"IngestApi",
|
|
150
|
+
"IngestPipeline",
|
|
128
151
|
# Consumption
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
"ApiConfig",
|
|
153
|
+
"Api",
|
|
154
|
+
"get_moose_base_url",
|
|
155
|
+
"set_moose_base_url",
|
|
133
156
|
# Backward compatibility aliases (deprecated)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
"ConsumptionApi",
|
|
158
|
+
"EgressConfig",
|
|
137
159
|
# SQL
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
"SqlResource",
|
|
161
|
+
"View",
|
|
162
|
+
"MaterializedViewOptions",
|
|
163
|
+
"MaterializedView",
|
|
143
164
|
# Workflow
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
"TaskContext",
|
|
166
|
+
"TaskConfig",
|
|
167
|
+
"Task",
|
|
168
|
+
"WorkflowConfig",
|
|
169
|
+
"Workflow",
|
|
150
170
|
# Lifecycle
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
"LifeCycle",
|
|
172
|
+
# WebApp
|
|
173
|
+
"WebApp",
|
|
174
|
+
"WebAppConfig",
|
|
175
|
+
"WebAppMetadata",
|
|
176
|
+
"ApiUtil",
|
|
177
|
+
"get_moose_utils",
|
|
178
|
+
"get_moose_dependency",
|
|
153
179
|
# Registry
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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",
|
|
166
194
|
# Backward compatibility aliases (deprecated)
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
"get_consumption_apis",
|
|
196
|
+
"get_consumption_api",
|
|
169
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
|
|
@@ -16,4 +17,5 @@ _api_name_aliases: Dict[str, Any] = {}
|
|
|
16
17
|
# Map from custom paths to API instances for path-based lookup
|
|
17
18
|
_api_path_map: Dict[str, Any] = {}
|
|
18
19
|
_sql_resources: Dict[str, Any] = {}
|
|
19
|
-
_workflows: Dict[str, Any] = {}
|
|
20
|
+
_workflows: 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,8 +4,10 @@ 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
|
+
|
|
8
|
+
import warnings
|
|
7
9
|
from typing import Any, Optional, Generic, TypeVar
|
|
8
|
-
from pydantic import BaseModel
|
|
10
|
+
from pydantic import BaseModel, model_validator
|
|
9
11
|
|
|
10
12
|
from .types import TypedMooseResource, T
|
|
11
13
|
from .olap_table import OlapTable, OlapConfig
|
|
@@ -13,6 +15,7 @@ from .stream import Stream, StreamConfig, DeadLetterQueue
|
|
|
13
15
|
from .ingest_api import IngestApi, IngestConfig, IngestConfigWithDestination
|
|
14
16
|
from .life_cycle import LifeCycle
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
class IngestPipelineConfig(BaseModel):
|
|
17
20
|
"""Configuration for creating a complete ingestion pipeline.
|
|
18
21
|
|
|
@@ -23,22 +26,47 @@ class IngestPipelineConfig(BaseModel):
|
|
|
23
26
|
Attributes:
|
|
24
27
|
table: Configuration for the OLAP table component.
|
|
25
28
|
stream: Configuration for the stream component.
|
|
26
|
-
|
|
29
|
+
ingest_api: Configuration for the ingest API component.
|
|
27
30
|
dead_letter_queue: Configuration for the dead letter queue.
|
|
28
31
|
version: Optional version string applied to all created components.
|
|
29
32
|
path: Optional custom path for the ingestion API endpoint.
|
|
30
33
|
metadata: Optional metadata for the ingestion pipeline.
|
|
31
34
|
life_cycle: Determines how changes in code will propagate to the resources.
|
|
32
35
|
"""
|
|
36
|
+
|
|
33
37
|
table: bool | OlapConfig = True
|
|
34
38
|
stream: bool | StreamConfig = True
|
|
35
|
-
|
|
39
|
+
ingest_api: bool | IngestConfig = True
|
|
36
40
|
dead_letter_queue: bool | StreamConfig = True
|
|
37
41
|
version: Optional[str] = None
|
|
38
42
|
path: Optional[str] = None
|
|
39
43
|
metadata: Optional[dict] = None
|
|
40
44
|
life_cycle: Optional[LifeCycle] = None
|
|
41
45
|
|
|
46
|
+
# Legacy support - will be removed in future version
|
|
47
|
+
ingest: Optional[bool | IngestConfig] = None
|
|
48
|
+
|
|
49
|
+
@model_validator(mode="before")
|
|
50
|
+
@classmethod
|
|
51
|
+
def handle_legacy_ingest_param(cls, data):
|
|
52
|
+
"""Handle backwards compatibility for the deprecated 'ingest' parameter."""
|
|
53
|
+
if isinstance(data, dict) and "ingest" in data:
|
|
54
|
+
warnings.warn(
|
|
55
|
+
"The 'ingest' parameter is deprecated and will be removed in a future version. "
|
|
56
|
+
"Please use 'ingest_api' instead.",
|
|
57
|
+
DeprecationWarning,
|
|
58
|
+
stacklevel=3,
|
|
59
|
+
)
|
|
60
|
+
# Make a copy first to avoid mutating the original dictionary
|
|
61
|
+
data = data.copy()
|
|
62
|
+
# If ingest_api is not explicitly set, use the ingest value
|
|
63
|
+
if "ingest_api" not in data:
|
|
64
|
+
data["ingest_api"] = data["ingest"]
|
|
65
|
+
# Remove the legacy parameter
|
|
66
|
+
del data["ingest"]
|
|
67
|
+
return data
|
|
68
|
+
|
|
69
|
+
|
|
42
70
|
class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
43
71
|
"""Creates and configures a linked Table, Stream, and Ingest API pipeline.
|
|
44
72
|
|
|
@@ -60,6 +88,7 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
60
88
|
name (str): The base name of the pipeline.
|
|
61
89
|
model_type (type[T]): The Pydantic model associated with this pipeline.
|
|
62
90
|
"""
|
|
91
|
+
|
|
63
92
|
table: Optional[OlapTable[T]] = None
|
|
64
93
|
stream: Optional[Stream[T]] = None
|
|
65
94
|
ingest_api: Optional[IngestApi[T]] = None
|
|
@@ -126,23 +155,37 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
126
155
|
stream_metadata = config.metadata
|
|
127
156
|
ingest_metadata = config.metadata
|
|
128
157
|
if config.table:
|
|
129
|
-
table_config = (
|
|
130
|
-
|
|
158
|
+
table_config = (
|
|
159
|
+
config.table
|
|
160
|
+
if isinstance(config.table, OlapConfig)
|
|
161
|
+
else OlapConfig(life_cycle=config.life_cycle)
|
|
162
|
+
)
|
|
131
163
|
if config.version:
|
|
132
164
|
table_config.version = config.version
|
|
133
165
|
table_config.metadata = table_metadata
|
|
134
166
|
self.table = OlapTable(name, table_config, t=self._t)
|
|
135
167
|
if config.dead_letter_queue:
|
|
136
|
-
dlq_stream_config =
|
|
168
|
+
dlq_stream_config = (
|
|
169
|
+
StreamConfig()
|
|
170
|
+
if config.dead_letter_queue is True
|
|
171
|
+
else config.dead_letter_queue
|
|
172
|
+
)
|
|
137
173
|
if config.version:
|
|
138
174
|
dlq_stream_config.version = config.version
|
|
139
175
|
dlq_stream_config.metadata = stream_metadata
|
|
140
|
-
self.dead_letter_queue = DeadLetterQueue(
|
|
176
|
+
self.dead_letter_queue = DeadLetterQueue(
|
|
177
|
+
f"{name}DeadLetterQueue", dlq_stream_config, t=self._t
|
|
178
|
+
)
|
|
141
179
|
if config.stream:
|
|
142
|
-
stream_config = (
|
|
143
|
-
|
|
180
|
+
stream_config = (
|
|
181
|
+
config.stream
|
|
182
|
+
if isinstance(config.stream, StreamConfig)
|
|
183
|
+
else StreamConfig(life_cycle=config.life_cycle)
|
|
184
|
+
)
|
|
144
185
|
if config.table and stream_config.destination is not None:
|
|
145
|
-
raise ValueError(
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"The destination of the stream should be the table created in the IngestPipeline"
|
|
188
|
+
)
|
|
146
189
|
stream_config.destination = self.table
|
|
147
190
|
if self.dead_letter_queue is not None:
|
|
148
191
|
stream_config.default_dead_letter_queue = self.dead_letter_queue
|
|
@@ -150,11 +193,11 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
150
193
|
stream_config.version = config.version
|
|
151
194
|
stream_config.metadata = stream_metadata
|
|
152
195
|
self.stream = Stream(name, stream_config, t=self._t)
|
|
153
|
-
if config.
|
|
196
|
+
if config.ingest_api:
|
|
154
197
|
if self.stream is None:
|
|
155
198
|
raise ValueError("Ingest API needs a stream to write to.")
|
|
156
199
|
ingest_config_dict = (
|
|
157
|
-
IngestConfig() if config.
|
|
200
|
+
IngestConfig() if config.ingest_api is True else config.ingest_api
|
|
158
201
|
).model_dump()
|
|
159
202
|
ingest_config_dict["destination"] = self.stream
|
|
160
203
|
if config.version:
|
|
@@ -165,4 +208,4 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
|
|
|
165
208
|
ingest_config_dict["dead_letter_queue"] = self.dead_letter_queue
|
|
166
209
|
ingest_config_dict["metadata"] = ingest_metadata
|
|
167
210
|
ingest_config = IngestConfigWithDestination(**ingest_config_dict)
|
|
168
|
-
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
|
+
"""
|