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
tests/test_secrets.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Tests for the moose_lib.secrets module.
|
|
2
|
+
|
|
3
|
+
This module tests the runtime environment variable marker functionality,
|
|
4
|
+
which allows users to defer secret resolution until runtime rather than
|
|
5
|
+
embedding secrets at build time.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import pytest
|
|
10
|
+
from moose_lib.secrets import moose_runtime_env, get, MOOSE_RUNTIME_ENV_PREFIX
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(scope="module", autouse=True)
|
|
14
|
+
def set_infra_map_loading_for_secrets_tests():
|
|
15
|
+
"""Set IS_LOADING_INFRA_MAP=true for secrets tests so moose_runtime_env.get() returns markers."""
|
|
16
|
+
os.environ["IS_LOADING_INFRA_MAP"] = "true"
|
|
17
|
+
yield
|
|
18
|
+
# Clean up after all tests in this module
|
|
19
|
+
os.environ.pop("IS_LOADING_INFRA_MAP", None)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestMooseRuntimeEnvGet:
|
|
23
|
+
"""Tests for the moose_runtime_env.get() method."""
|
|
24
|
+
|
|
25
|
+
def test_creates_marker_with_correct_prefix(self):
|
|
26
|
+
"""Should create a marker string with the correct prefix."""
|
|
27
|
+
var_name = "AWS_ACCESS_KEY_ID"
|
|
28
|
+
result = moose_runtime_env.get(var_name)
|
|
29
|
+
|
|
30
|
+
assert result == f"{MOOSE_RUNTIME_ENV_PREFIX}{var_name}"
|
|
31
|
+
assert result == "__MOOSE_RUNTIME_ENV__:AWS_ACCESS_KEY_ID"
|
|
32
|
+
|
|
33
|
+
def test_handles_different_variable_names(self):
|
|
34
|
+
"""Should handle different environment variable names correctly."""
|
|
35
|
+
test_cases = [
|
|
36
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
37
|
+
"DATABASE_PASSWORD",
|
|
38
|
+
"API_KEY",
|
|
39
|
+
"MY_CUSTOM_SECRET",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
for var_name in test_cases:
|
|
43
|
+
result = moose_runtime_env.get(var_name)
|
|
44
|
+
assert result == f"{MOOSE_RUNTIME_ENV_PREFIX}{var_name}"
|
|
45
|
+
assert var_name in result
|
|
46
|
+
|
|
47
|
+
def test_raises_error_for_empty_string(self):
|
|
48
|
+
"""Should raise ValueError for empty string."""
|
|
49
|
+
with pytest.raises(
|
|
50
|
+
ValueError, match="Environment variable name cannot be empty"
|
|
51
|
+
):
|
|
52
|
+
moose_runtime_env.get("")
|
|
53
|
+
|
|
54
|
+
def test_raises_error_for_whitespace_only(self):
|
|
55
|
+
"""Should raise ValueError for whitespace-only string."""
|
|
56
|
+
with pytest.raises(
|
|
57
|
+
ValueError, match="Environment variable name cannot be empty"
|
|
58
|
+
):
|
|
59
|
+
moose_runtime_env.get(" ")
|
|
60
|
+
|
|
61
|
+
def test_raises_error_for_tabs_only(self):
|
|
62
|
+
"""Should raise ValueError for string with only tabs."""
|
|
63
|
+
with pytest.raises(
|
|
64
|
+
ValueError, match="Environment variable name cannot be empty"
|
|
65
|
+
):
|
|
66
|
+
moose_runtime_env.get("\t\t")
|
|
67
|
+
|
|
68
|
+
def test_allows_underscores_in_variable_names(self):
|
|
69
|
+
"""Should allow variable names with underscores."""
|
|
70
|
+
var_name = "MY_LONG_VAR_NAME"
|
|
71
|
+
result = moose_runtime_env.get(var_name)
|
|
72
|
+
|
|
73
|
+
assert result == f"{MOOSE_RUNTIME_ENV_PREFIX}{var_name}"
|
|
74
|
+
|
|
75
|
+
def test_allows_numbers_in_variable_names(self):
|
|
76
|
+
"""Should allow variable names with numbers."""
|
|
77
|
+
var_name = "API_KEY_123"
|
|
78
|
+
result = moose_runtime_env.get(var_name)
|
|
79
|
+
|
|
80
|
+
assert result == f"{MOOSE_RUNTIME_ENV_PREFIX}{var_name}"
|
|
81
|
+
|
|
82
|
+
def test_preserves_exact_casing(self):
|
|
83
|
+
"""Should preserve exact variable name casing."""
|
|
84
|
+
var_name = "MixedCase_VarName"
|
|
85
|
+
result = moose_runtime_env.get(var_name)
|
|
86
|
+
|
|
87
|
+
assert var_name in result
|
|
88
|
+
assert var_name.lower() not in result # Ensure casing wasn't changed
|
|
89
|
+
|
|
90
|
+
def test_can_be_used_in_s3queue_config(self):
|
|
91
|
+
"""Should create markers that can be used in S3Queue configuration."""
|
|
92
|
+
access_key_marker = moose_runtime_env.get("AWS_ACCESS_KEY_ID")
|
|
93
|
+
secret_key_marker = moose_runtime_env.get("AWS_SECRET_ACCESS_KEY")
|
|
94
|
+
|
|
95
|
+
config = {
|
|
96
|
+
"aws_access_key_id": access_key_marker,
|
|
97
|
+
"aws_secret_access_key": secret_key_marker,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
assert "AWS_ACCESS_KEY_ID" in config["aws_access_key_id"]
|
|
101
|
+
assert "AWS_SECRET_ACCESS_KEY" in config["aws_secret_access_key"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestModuleLevelGetFunction:
|
|
105
|
+
"""Tests for the module-level get() function."""
|
|
106
|
+
|
|
107
|
+
def test_module_level_get_creates_marker(self):
|
|
108
|
+
"""The module-level get function should create markers."""
|
|
109
|
+
var_name = "TEST_SECRET"
|
|
110
|
+
result = get(var_name)
|
|
111
|
+
|
|
112
|
+
assert result == f"{MOOSE_RUNTIME_ENV_PREFIX}{var_name}"
|
|
113
|
+
|
|
114
|
+
def test_module_level_get_matches_class_method(self):
|
|
115
|
+
"""Module-level get should produce same result as class method."""
|
|
116
|
+
var_name = "MY_SECRET"
|
|
117
|
+
|
|
118
|
+
result_module = get(var_name)
|
|
119
|
+
result_class = moose_runtime_env.get(var_name)
|
|
120
|
+
|
|
121
|
+
assert result_module == result_class
|
|
122
|
+
|
|
123
|
+
def test_module_level_get_raises_error_for_empty(self):
|
|
124
|
+
"""Module-level get should raise ValueError for empty string."""
|
|
125
|
+
with pytest.raises(
|
|
126
|
+
ValueError, match="Environment variable name cannot be empty"
|
|
127
|
+
):
|
|
128
|
+
get("")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TestMooseRuntimeEnvPrefix:
|
|
132
|
+
"""Tests for the MOOSE_RUNTIME_ENV_PREFIX constant."""
|
|
133
|
+
|
|
134
|
+
def test_has_expected_value(self):
|
|
135
|
+
"""Should have the expected prefix value."""
|
|
136
|
+
assert MOOSE_RUNTIME_ENV_PREFIX == "__MOOSE_RUNTIME_ENV__:"
|
|
137
|
+
|
|
138
|
+
def test_is_string(self):
|
|
139
|
+
"""Should be a string."""
|
|
140
|
+
assert isinstance(MOOSE_RUNTIME_ENV_PREFIX, str)
|
|
141
|
+
|
|
142
|
+
def test_is_not_empty(self):
|
|
143
|
+
"""Should not be empty."""
|
|
144
|
+
assert len(MOOSE_RUNTIME_ENV_PREFIX) > 0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestMarkerFormatValidation:
|
|
148
|
+
"""Tests for marker format validation and parsing."""
|
|
149
|
+
|
|
150
|
+
def test_creates_easily_detectable_markers(self):
|
|
151
|
+
"""Should create markers that are easily detectable."""
|
|
152
|
+
marker = moose_runtime_env.get("TEST_VAR")
|
|
153
|
+
|
|
154
|
+
assert marker.startswith("__MOOSE_RUNTIME_ENV__:")
|
|
155
|
+
|
|
156
|
+
def test_markers_can_be_split_to_extract_variable_name(self):
|
|
157
|
+
"""Should create markers that can be split to extract variable name."""
|
|
158
|
+
var_name = "MY_SECRET"
|
|
159
|
+
marker = moose_runtime_env.get(var_name)
|
|
160
|
+
|
|
161
|
+
parts = marker.split(MOOSE_RUNTIME_ENV_PREFIX)
|
|
162
|
+
assert len(parts) == 2
|
|
163
|
+
assert parts[1] == var_name
|
|
164
|
+
|
|
165
|
+
def test_markers_are_json_serializable(self):
|
|
166
|
+
"""Should create markers that are JSON serializable."""
|
|
167
|
+
import json
|
|
168
|
+
|
|
169
|
+
marker = moose_runtime_env.get("TEST_VAR")
|
|
170
|
+
json_str = json.dumps({"secret": marker})
|
|
171
|
+
parsed = json.loads(json_str)
|
|
172
|
+
|
|
173
|
+
assert parsed["secret"] == marker
|
|
174
|
+
|
|
175
|
+
def test_markers_work_with_dict_serialization(self):
|
|
176
|
+
"""Should work correctly with dictionary serialization."""
|
|
177
|
+
marker = moose_runtime_env.get("DATABASE_PASSWORD")
|
|
178
|
+
|
|
179
|
+
config = {"password": marker, "other_field": "value"}
|
|
180
|
+
|
|
181
|
+
# Verify the marker is preserved in the dict
|
|
182
|
+
assert config["password"] == marker
|
|
183
|
+
assert MOOSE_RUNTIME_ENV_PREFIX in config["password"]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TestIntegrationScenarios:
|
|
187
|
+
"""Integration tests for real-world usage scenarios."""
|
|
188
|
+
|
|
189
|
+
def test_s3queue_engine_with_secrets(self):
|
|
190
|
+
"""Should work correctly in S3Queue engine configuration."""
|
|
191
|
+
from moose_lib.blocks import S3QueueEngine
|
|
192
|
+
|
|
193
|
+
engine = S3QueueEngine(
|
|
194
|
+
s3_path="s3://my-bucket/data/*.json",
|
|
195
|
+
format="JSONEachRow",
|
|
196
|
+
aws_access_key_id=moose_runtime_env.get("AWS_ACCESS_KEY_ID"),
|
|
197
|
+
aws_secret_access_key=moose_runtime_env.get("AWS_SECRET_ACCESS_KEY"),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Verify markers were set correctly
|
|
201
|
+
assert engine.aws_access_key_id == "__MOOSE_RUNTIME_ENV__:AWS_ACCESS_KEY_ID"
|
|
202
|
+
assert (
|
|
203
|
+
engine.aws_secret_access_key
|
|
204
|
+
== "__MOOSE_RUNTIME_ENV__:AWS_SECRET_ACCESS_KEY"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def test_multiple_secrets_in_same_config(self):
|
|
208
|
+
"""Should handle multiple secrets in the same configuration."""
|
|
209
|
+
config = {
|
|
210
|
+
"username": moose_runtime_env.get("DB_USERNAME"),
|
|
211
|
+
"password": moose_runtime_env.get("DB_PASSWORD"),
|
|
212
|
+
"api_key": moose_runtime_env.get("API_KEY"),
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# All should have the correct prefix
|
|
216
|
+
for value in config.values():
|
|
217
|
+
assert value.startswith(MOOSE_RUNTIME_ENV_PREFIX)
|
|
218
|
+
|
|
219
|
+
# Each should have the correct variable name
|
|
220
|
+
assert "DB_USERNAME" in config["username"]
|
|
221
|
+
assert "DB_PASSWORD" in config["password"]
|
|
222
|
+
assert "API_KEY" in config["api_key"]
|
|
223
|
+
|
|
224
|
+
def test_mixed_secret_and_plain_values(self):
|
|
225
|
+
"""Should handle mix of secret markers and plain values."""
|
|
226
|
+
config = {
|
|
227
|
+
"region": "us-east-1", # Plain value
|
|
228
|
+
"access_key": moose_runtime_env.get("AWS_ACCESS_KEY_ID"), # Secret
|
|
229
|
+
"bucket": "my-bucket", # Plain value
|
|
230
|
+
"secret_key": moose_runtime_env.get("AWS_SECRET_ACCESS_KEY"), # Secret
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Plain values should be unchanged
|
|
234
|
+
assert config["region"] == "us-east-1"
|
|
235
|
+
assert config["bucket"] == "my-bucket"
|
|
236
|
+
|
|
237
|
+
# Secrets should have markers
|
|
238
|
+
assert MOOSE_RUNTIME_ENV_PREFIX in config["access_key"]
|
|
239
|
+
assert MOOSE_RUNTIME_ENV_PREFIX in config["secret_key"]
|
tests/test_simple_aggregate.py
CHANGED
|
@@ -7,106 +7,108 @@ from moose_lib.data_models import SimpleAggregateFunction, _to_columns
|
|
|
7
7
|
|
|
8
8
|
def test_simple_aggregated_helper():
|
|
9
9
|
"""Test that simple_aggregated helper creates correct annotation"""
|
|
10
|
-
annotated_type = simple_aggregated(
|
|
10
|
+
annotated_type = simple_aggregated("sum", int)
|
|
11
11
|
|
|
12
12
|
# Check that it's annotated
|
|
13
|
-
assert hasattr(annotated_type,
|
|
13
|
+
assert hasattr(annotated_type, "__metadata__")
|
|
14
14
|
metadata = annotated_type.__metadata__[0]
|
|
15
15
|
|
|
16
16
|
# Check metadata is SimpleAggregateFunction instance
|
|
17
17
|
assert isinstance(metadata, SimpleAggregateFunction)
|
|
18
|
-
assert metadata.agg_func ==
|
|
18
|
+
assert metadata.agg_func == "sum"
|
|
19
19
|
assert metadata.arg_type == int
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def test_simple_aggregate_function_to_dict():
|
|
23
23
|
"""Test that SimpleAggregateFunction.to_dict() creates correct structure"""
|
|
24
|
-
func = SimpleAggregateFunction(agg_func=
|
|
24
|
+
func = SimpleAggregateFunction(agg_func="sum", arg_type=int)
|
|
25
25
|
result = func.to_dict()
|
|
26
26
|
|
|
27
|
-
assert result[
|
|
28
|
-
assert
|
|
29
|
-
# Python int
|
|
30
|
-
assert result[
|
|
27
|
+
assert result["functionName"] == "sum"
|
|
28
|
+
assert "argumentType" in result
|
|
29
|
+
# unless Annotated, Python int becomes `Int64`
|
|
30
|
+
assert result["argumentType"] == "Int64"
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def test_simple_aggregate_function_to_dict_with_different_types():
|
|
34
34
|
"""Test SimpleAggregateFunction.to_dict() with various types"""
|
|
35
35
|
# Test with float
|
|
36
|
-
func_float = SimpleAggregateFunction(agg_func=
|
|
36
|
+
func_float = SimpleAggregateFunction(agg_func="max", arg_type=float)
|
|
37
37
|
result_float = func_float.to_dict()
|
|
38
|
-
assert result_float[
|
|
39
|
-
assert result_float[
|
|
38
|
+
assert result_float["functionName"] == "max"
|
|
39
|
+
assert result_float["argumentType"] == "Float64"
|
|
40
40
|
|
|
41
41
|
# Test with str
|
|
42
|
-
func_str = SimpleAggregateFunction(agg_func=
|
|
42
|
+
func_str = SimpleAggregateFunction(agg_func="anyLast", arg_type=str)
|
|
43
43
|
result_str = func_str.to_dict()
|
|
44
|
-
assert result_str[
|
|
45
|
-
assert result_str[
|
|
44
|
+
assert result_str["functionName"] == "anyLast"
|
|
45
|
+
assert result_str["argumentType"] == "String"
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def test_dataclass_with_simple_aggregated():
|
|
49
49
|
"""Test that BaseModel with simple_aggregated field converts correctly"""
|
|
50
|
+
|
|
50
51
|
class TestModel(BaseModel):
|
|
51
52
|
date_stamp: Key[datetime.datetime]
|
|
52
53
|
table_name: Key[str]
|
|
53
|
-
row_count: simple_aggregated(
|
|
54
|
+
row_count: simple_aggregated("sum", int)
|
|
54
55
|
|
|
55
56
|
columns = _to_columns(TestModel)
|
|
56
57
|
|
|
57
58
|
# Find the row_count column
|
|
58
|
-
row_count_col = next(c for c in columns if c.name ==
|
|
59
|
+
row_count_col = next(c for c in columns if c.name == "row_count")
|
|
59
60
|
|
|
60
|
-
# Check basic type - Python int maps to "
|
|
61
|
-
assert row_count_col.data_type ==
|
|
61
|
+
# Check basic type - Python int maps to "Int64" by default
|
|
62
|
+
assert row_count_col.data_type == "Int64"
|
|
62
63
|
|
|
63
64
|
# Check annotation
|
|
64
65
|
simple_agg_annotation = next(
|
|
65
|
-
(a for a in row_count_col.annotations if a[0] ==
|
|
66
|
-
None
|
|
66
|
+
(a for a in row_count_col.annotations if a[0] == "simpleAggregationFunction"),
|
|
67
|
+
None,
|
|
67
68
|
)
|
|
68
69
|
assert simple_agg_annotation is not None
|
|
69
|
-
assert simple_agg_annotation[1][
|
|
70
|
-
assert simple_agg_annotation[1][
|
|
70
|
+
assert simple_agg_annotation[1]["functionName"] == "sum"
|
|
71
|
+
assert simple_agg_annotation[1]["argumentType"] == "Int64"
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
def test_multiple_simple_aggregated_fields():
|
|
74
75
|
"""Test BaseModel with multiple SimpleAggregateFunction fields"""
|
|
76
|
+
|
|
75
77
|
class StatsModel(BaseModel):
|
|
76
78
|
timestamp: Key[datetime.datetime]
|
|
77
|
-
total_count: simple_aggregated(
|
|
78
|
-
max_value: simple_aggregated(
|
|
79
|
-
min_value: simple_aggregated(
|
|
80
|
-
last_seen: simple_aggregated(
|
|
79
|
+
total_count: simple_aggregated("sum", int)
|
|
80
|
+
max_value: simple_aggregated("max", int)
|
|
81
|
+
min_value: simple_aggregated("min", int)
|
|
82
|
+
last_seen: simple_aggregated("anyLast", datetime.datetime)
|
|
81
83
|
|
|
82
84
|
columns = _to_columns(StatsModel)
|
|
83
85
|
|
|
84
86
|
# Test sum
|
|
85
|
-
sum_col = next(c for c in columns if c.name ==
|
|
87
|
+
sum_col = next(c for c in columns if c.name == "total_count")
|
|
86
88
|
sum_annotation = next(
|
|
87
|
-
a for a in sum_col.annotations if a[0] ==
|
|
89
|
+
a for a in sum_col.annotations if a[0] == "simpleAggregationFunction"
|
|
88
90
|
)
|
|
89
|
-
assert sum_annotation[1][
|
|
91
|
+
assert sum_annotation[1]["functionName"] == "sum"
|
|
90
92
|
|
|
91
93
|
# Test max
|
|
92
|
-
max_col = next(c for c in columns if c.name ==
|
|
94
|
+
max_col = next(c for c in columns if c.name == "max_value")
|
|
93
95
|
max_annotation = next(
|
|
94
|
-
a for a in max_col.annotations if a[0] ==
|
|
96
|
+
a for a in max_col.annotations if a[0] == "simpleAggregationFunction"
|
|
95
97
|
)
|
|
96
|
-
assert max_annotation[1][
|
|
98
|
+
assert max_annotation[1]["functionName"] == "max"
|
|
97
99
|
|
|
98
100
|
# Test min
|
|
99
|
-
min_col = next(c for c in columns if c.name ==
|
|
101
|
+
min_col = next(c for c in columns if c.name == "min_value")
|
|
100
102
|
min_annotation = next(
|
|
101
|
-
a for a in min_col.annotations if a[0] ==
|
|
103
|
+
a for a in min_col.annotations if a[0] == "simpleAggregationFunction"
|
|
102
104
|
)
|
|
103
|
-
assert min_annotation[1][
|
|
105
|
+
assert min_annotation[1]["functionName"] == "min"
|
|
104
106
|
|
|
105
107
|
# Test anyLast with datetime
|
|
106
|
-
last_col = next(c for c in columns if c.name ==
|
|
107
|
-
assert last_col.data_type ==
|
|
108
|
+
last_col = next(c for c in columns if c.name == "last_seen")
|
|
109
|
+
assert last_col.data_type == "DateTime"
|
|
108
110
|
last_annotation = next(
|
|
109
|
-
a for a in last_col.annotations if a[0] ==
|
|
111
|
+
a for a in last_col.annotations if a[0] == "simpleAggregationFunction"
|
|
110
112
|
)
|
|
111
|
-
assert last_annotation[1][
|
|
112
|
-
assert last_annotation[1][
|
|
113
|
+
assert last_annotation[1]["functionName"] == "anyLast"
|
|
114
|
+
assert last_annotation[1]["argumentType"] == "DateTime"
|
tests/test_web_app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Unit tests for WebApp SDK functionality.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import pytest
|
|
5
6
|
from moose_lib.dmv2 import WebApp, WebAppConfig, WebAppMetadata
|
|
6
7
|
from moose_lib.dmv2._registry import _web_apps
|
|
@@ -9,6 +10,7 @@ from moose_lib.dmv2._registry import _web_apps
|
|
|
9
10
|
# Mock FastAPI app for testing
|
|
10
11
|
class MockFastAPIApp:
|
|
11
12
|
"""Mock FastAPI application for testing."""
|
|
13
|
+
|
|
12
14
|
pass
|
|
13
15
|
|
|
14
16
|
|
|
@@ -84,7 +86,10 @@ def test_webapp_root_path_rejected():
|
|
|
84
86
|
app = MockFastAPIApp()
|
|
85
87
|
config = WebAppConfig(mount_path="/")
|
|
86
88
|
|
|
87
|
-
with pytest.raises(
|
|
89
|
+
with pytest.raises(
|
|
90
|
+
ValueError,
|
|
91
|
+
match='mountPath cannot be "/" as it would allow routes to overlap with reserved paths',
|
|
92
|
+
):
|
|
88
93
|
WebApp("test_app", app, config)
|
|
89
94
|
|
|
90
95
|
|
|
@@ -127,7 +132,9 @@ def test_webapp_duplicate_mount_path():
|
|
|
127
132
|
WebApp("app1", app1, config1)
|
|
128
133
|
|
|
129
134
|
config2 = WebAppConfig(mount_path="/myapi")
|
|
130
|
-
with pytest.raises(
|
|
135
|
+
with pytest.raises(
|
|
136
|
+
ValueError, match='WebApp with mountPath "/myapi" already exists'
|
|
137
|
+
):
|
|
131
138
|
WebApp("app2", app2, config2)
|
|
132
139
|
|
|
133
140
|
|
|
@@ -178,9 +185,8 @@ def test_webapp_serialization():
|
|
|
178
185
|
"test_app",
|
|
179
186
|
app,
|
|
180
187
|
WebAppConfig(
|
|
181
|
-
mount_path="/myapi",
|
|
182
|
-
|
|
183
|
-
)
|
|
188
|
+
mount_path="/myapi", metadata=WebAppMetadata(description="Test API")
|
|
189
|
+
),
|
|
184
190
|
)
|
|
185
191
|
|
|
186
192
|
# Verify it's in the registry
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
moose_lib/__init__.py,sha256=swNNCec7czJO62pYKeRyonMR4QlzgCN74GLFFI3NDxs,917
|
|
2
|
-
moose_lib/blocks.py,sha256=Cwjx7UX5qXLVpxF5eBomBLzIoxVn769jfZRJfJnqPyw,14423
|
|
3
|
-
moose_lib/commons.py,sha256=YDMzRO3ySa_iQRknmFwwE539KgSyjWhYJ-E3jIsfSgc,6585
|
|
4
|
-
moose_lib/data_models.py,sha256=t_RL658-l9iCau2NXdnCSbt_MFH9S-vYNCmimZfWZ_o,15509
|
|
5
|
-
moose_lib/dmv2_serializer.py,sha256=CL_Pvvg8tJOT8Qk6hywDNzY8MYGhMVdTOw8arZi3jng,49
|
|
6
|
-
moose_lib/internal.py,sha256=FMd4GuQ2uRXiV7_9JrTYN1xx2HFagMbEY9uu_Kb4omg,27742
|
|
7
|
-
moose_lib/main.py,sha256=JWsgza52xEh25AyF61cO1ItJ8VXJHHz8j-4HG445Whg,20380
|
|
8
|
-
moose_lib/query_builder.py,sha256=-L5p2dArBx3SBA-WZPkcCJPemKXnqJw60NHy-wn5wa4,6619
|
|
9
|
-
moose_lib/query_param.py,sha256=kxcR09BMIsEg4o2qetjKrVu1YFRaLfMEzwzyGsKUpvA,6474
|
|
10
|
-
moose_lib/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
moose_lib/clients/redis_client.py,sha256=BDYjJ582V-rW92qVQ4neZ9Pu7JtDNt8x8jBWApt1XUg,11895
|
|
12
|
-
moose_lib/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
moose_lib/config/config_file.py,sha256=x6Gm3MkhPYLcaZefgNTpZCFnnqSWVlLz2JVoISxG7rI,3885
|
|
14
|
-
moose_lib/config/runtime.py,sha256=zfd58p6tTIIkcjeMYtQJNMzXGVbXGBHGmq7FX7GLoXc,8130
|
|
15
|
-
moose_lib/dmv2/__init__.py,sha256=BQf-WUA2AY0X5h6ITZ_BrTGul2vDnflbIiSl_m7thhE,3312
|
|
16
|
-
moose_lib/dmv2/_registry.py,sha256=ACc_Cr__tZJEQfp6uTVx7O8nF62IrHt_i-X6w1s4QQA,739
|
|
17
|
-
moose_lib/dmv2/consumption.py,sha256=sWfGwgCBQIIrhFEbx1CULfoN3rFFoy8uCBUefu4ejiw,12928
|
|
18
|
-
moose_lib/dmv2/ingest_api.py,sha256=XhvHHgGPXp-BuRpAALth-FRhanwy-zJQ_83Cg_RLolM,2586
|
|
19
|
-
moose_lib/dmv2/ingest_pipeline.py,sha256=R8NRrfD9zkEzwWEJKIrNL9Nk18DdGc7wdAakae_2-7o,8459
|
|
20
|
-
moose_lib/dmv2/life_cycle.py,sha256=wl0k6yzwU1MJ_fO_UkN29buoY5G6ChYZvfwigP9fVfM,1254
|
|
21
|
-
moose_lib/dmv2/materialized_view.py,sha256=wTam1V5CC2rExt7YrdK_Cz4rRTONm2keKOF951LlCP4,4875
|
|
22
|
-
moose_lib/dmv2/olap_table.py,sha256=o8wNZ9qTrbNB8mz8VnQTSme7AsNQ8KofDI2erywnSa0,35763
|
|
23
|
-
moose_lib/dmv2/registry.py,sha256=5V8HRKBJXKLIi5tdIC2tKEmsGELQ1C2fBrMjns8a3ao,2947
|
|
24
|
-
moose_lib/dmv2/sql_resource.py,sha256=kUZoGqxhZMHMthtBZGYJBxTFjXkspXiWLXhJRYXgGUM,1864
|
|
25
|
-
moose_lib/dmv2/stream.py,sha256=pDryS1x5zMkDSJsFlGqm8quwKTl1ctAL-a_U8ySgqQI,18043
|
|
26
|
-
moose_lib/dmv2/types.py,sha256=3YZ5Kbz2eOn94GWMTYT6Ix69Ekwe6aoUR4DiETQjv9E,4633
|
|
27
|
-
moose_lib/dmv2/view.py,sha256=fVbfbJgc2lvhjpGvpfKcFUqZqxKuLD4X59jdupxIe94,1350
|
|
28
|
-
moose_lib/dmv2/web_app.py,sha256=BPJIKMGNJqezThw56maVpjLwinJ8afDaBAoRgLiTGQA,5487
|
|
29
|
-
moose_lib/dmv2/web_app_helpers.py,sha256=3HUDTUmNwvOLQHrOcvo_ocXD6HBfyKEprPYAqT0kVnI,2895
|
|
30
|
-
moose_lib/dmv2/workflow.py,sha256=_FY4-VRo7uWxRtoipxGSo04qzBb4pbP30iQei1W0Ios,6287
|
|
31
|
-
moose_lib/streaming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
moose_lib/streaming/streaming_function_runner.py,sha256=A4XrfawOR432hSfJr86-s1-aXsN6jAvwTyJcoY2Hos4,23171
|
|
33
|
-
moose_lib/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
moose_lib/utilities/sql.py,sha256=kbg1DT5GdsIwgTsMzbsd6SAVf9aWER8DqmT_eKS3XN4,904
|
|
35
|
-
tests/__init__.py,sha256=0Gh4yzPkkC3TzBGKhenpMIxJcRhyrrCfxLSfpTZnPMQ,53
|
|
36
|
-
tests/conftest.py,sha256=tsBozrzGuJUO7dRGVcIxCjqi-l77I-DvYnzpbKtmGzE,1003
|
|
37
|
-
tests/test_moose.py,sha256=mBsx_OYWmL8ppDzL_7Bd7xR6qf_i3-pCIO3wm2iQNaA,2136
|
|
38
|
-
tests/test_olap_table_versioning.py,sha256=ffuJsO5TYKBd2aWV0GWDmosCwL4j518Y_MqYJdrfgDY,7041
|
|
39
|
-
tests/test_query_builder.py,sha256=O3imdFSaqU13kbK1jSQaHbBgynhVmJaApT8DlRqYwJU,1116
|
|
40
|
-
tests/test_redis_client.py,sha256=d9_MLYsJ4ecVil_jPB2gW3Q5aWnavxmmjZg2uYI3LVo,3256
|
|
41
|
-
tests/test_s3queue_config.py,sha256=F05cnD61S2wBKPabcpEJxf55-DJGF4nLqwBb6aFbprc,9741
|
|
42
|
-
tests/test_simple_aggregate.py,sha256=yyFSYHUXskA1aTcwC-KQ9XmgOikeQ8wKXzntsUBIC6w,4055
|
|
43
|
-
tests/test_web_app.py,sha256=iL86sg0NS2k6nP8jIGKZvrtg0BjvaqNTRp7-4T1TTck,6557
|
|
44
|
-
moose_lib-0.6.148.dev3442438466.dist-info/METADATA,sha256=zgGu8QHz4E6NOqio2tEqoteLxLhnf1YKTBwjWkgOvDQ,841
|
|
45
|
-
moose_lib-0.6.148.dev3442438466.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
46
|
-
moose_lib-0.6.148.dev3442438466.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
|
|
47
|
-
moose_lib-0.6.148.dev3442438466.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|