moose-lib 0.6.173__py3-none-any.whl → 0.6.188__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 +19 -1
- moose_lib/data_models.py +14 -0
- moose_lib/secrets.py +26 -6
- {moose_lib-0.6.173.dist-info → moose_lib-0.6.188.dist-info}/METADATA +1 -1
- {moose_lib-0.6.173.dist-info → moose_lib-0.6.188.dist-info}/RECORD +9 -8
- tests/test_int_types.py +204 -0
- tests/test_secrets.py +10 -0
- {moose_lib-0.6.173.dist-info → moose_lib-0.6.188.dist-info}/WHEEL +0 -0
- {moose_lib-0.6.173.dist-info → moose_lib-0.6.188.dist-info}/top_level.txt +0 -0
moose_lib/__init__.py
CHANGED
|
@@ -30,7 +30,25 @@ from .blocks import (
|
|
|
30
30
|
# Legacy enum (already exported via .blocks import, but explicit for clarity)
|
|
31
31
|
ClickHouseEngines
|
|
32
32
|
)
|
|
33
|
-
from .data_models import
|
|
33
|
+
from .data_models import (
|
|
34
|
+
Key,
|
|
35
|
+
AggregateFunction,
|
|
36
|
+
StringToEnumMixin,
|
|
37
|
+
FixedString,
|
|
38
|
+
ClickhouseFixedStringSize,
|
|
39
|
+
# Integer types
|
|
40
|
+
Int8,
|
|
41
|
+
Int16,
|
|
42
|
+
Int32,
|
|
43
|
+
Int64,
|
|
44
|
+
UInt8,
|
|
45
|
+
UInt16,
|
|
46
|
+
UInt32,
|
|
47
|
+
UInt64,
|
|
48
|
+
# Float types
|
|
49
|
+
Float32,
|
|
50
|
+
Float64,
|
|
51
|
+
)
|
|
34
52
|
from .commons import Logger
|
|
35
53
|
|
|
36
54
|
from .query_builder import *
|
moose_lib/data_models.py
CHANGED
|
@@ -15,6 +15,20 @@ import ipaddress
|
|
|
15
15
|
type Key[T: (str, int)] = T
|
|
16
16
|
type JWT[T] = T
|
|
17
17
|
|
|
18
|
+
# Integer type aliases for ClickHouse integer types
|
|
19
|
+
type Int8 = Annotated[int, "int8"]
|
|
20
|
+
type Int16 = Annotated[int, "int16"]
|
|
21
|
+
type Int32 = Annotated[int, "int32"]
|
|
22
|
+
type Int64 = Annotated[int, "int64"]
|
|
23
|
+
type UInt8 = Annotated[int, "uint8"]
|
|
24
|
+
type UInt16 = Annotated[int, "uint16"]
|
|
25
|
+
type UInt32 = Annotated[int, "uint32"]
|
|
26
|
+
type UInt64 = Annotated[int, "uint64"]
|
|
27
|
+
|
|
28
|
+
# Float type aliases for ClickHouse float types
|
|
29
|
+
type Float32 = Annotated[float, "float32"]
|
|
30
|
+
type Float64 = Annotated[float, "float64"]
|
|
31
|
+
|
|
18
32
|
|
|
19
33
|
@dataclasses.dataclass(frozen=True) # a BaseModel in the annotations will confuse pydantic
|
|
20
34
|
class ClickhousePrecision:
|
moose_lib/secrets.py
CHANGED
|
@@ -20,11 +20,13 @@ MOOSE_RUNTIME_ENV_PREFIX = "__MOOSE_RUNTIME_ENV__:"
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def get(env_var_name: str) -> str:
|
|
23
|
-
"""
|
|
23
|
+
"""Gets a value from an environment variable, with behavior depending on context.
|
|
24
24
|
|
|
25
|
-
When
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
When IS_LOADING_INFRA_MAP=true (infrastructure loading):
|
|
26
|
+
Returns a marker string that Moose CLI will resolve later
|
|
27
|
+
|
|
28
|
+
When IS_LOADING_INFRA_MAP is unset (function/workflow runtime):
|
|
29
|
+
Returns the actual value from the environment variable
|
|
28
30
|
|
|
29
31
|
This is useful for:
|
|
30
32
|
- Credentials that should never be embedded in Docker images
|
|
@@ -36,10 +38,11 @@ def get(env_var_name: str) -> str:
|
|
|
36
38
|
env_var_name: Name of the environment variable to resolve
|
|
37
39
|
|
|
38
40
|
Returns:
|
|
39
|
-
|
|
41
|
+
Either a marker string or the actual environment variable value
|
|
40
42
|
|
|
41
43
|
Raises:
|
|
42
44
|
ValueError: If the environment variable name is empty
|
|
45
|
+
KeyError: If the environment variable is not set (runtime mode only)
|
|
43
46
|
|
|
44
47
|
Example:
|
|
45
48
|
>>> # Instead of this (evaluated at build time):
|
|
@@ -49,9 +52,26 @@ def get(env_var_name: str) -> str:
|
|
|
49
52
|
>>> # Use this (evaluated at runtime):
|
|
50
53
|
>>> aws_key = moose_runtime_env.get("AWS_ACCESS_KEY_ID")
|
|
51
54
|
"""
|
|
55
|
+
import os
|
|
56
|
+
|
|
52
57
|
if not env_var_name or not env_var_name.strip():
|
|
53
58
|
raise ValueError("Environment variable name cannot be empty")
|
|
54
|
-
|
|
59
|
+
|
|
60
|
+
# Check if we're loading infrastructure map
|
|
61
|
+
is_loading_infra_map = os.environ.get("IS_LOADING_INFRA_MAP") == "true"
|
|
62
|
+
|
|
63
|
+
if is_loading_infra_map:
|
|
64
|
+
# Return marker string for later resolution by Moose CLI
|
|
65
|
+
return f"{MOOSE_RUNTIME_ENV_PREFIX}{env_var_name}"
|
|
66
|
+
else:
|
|
67
|
+
# Return actual value from environment for runtime execution
|
|
68
|
+
value = os.environ.get(env_var_name)
|
|
69
|
+
if value is None:
|
|
70
|
+
raise KeyError(
|
|
71
|
+
f"Environment variable '{env_var_name}' is not set. "
|
|
72
|
+
f"This is required for runtime execution of functions/workflows."
|
|
73
|
+
)
|
|
74
|
+
return value
|
|
55
75
|
|
|
56
76
|
|
|
57
77
|
class MooseRuntimeEnv:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
moose_lib/__init__.py,sha256=
|
|
1
|
+
moose_lib/__init__.py,sha256=GyWPjIaU59OAq4cKzwLqDmGc-YTygmELZqBg36ctZn8,1176
|
|
2
2
|
moose_lib/blocks.py,sha256=TJfaBJjDbCjGBVGpcV2HwVLWxiuqBC9lhx7FXCCbfsE,18238
|
|
3
3
|
moose_lib/commons.py,sha256=YDMzRO3ySa_iQRknmFwwE539KgSyjWhYJ-E3jIsfSgc,6585
|
|
4
|
-
moose_lib/data_models.py,sha256=
|
|
4
|
+
moose_lib/data_models.py,sha256=D9F8HV0BdAStNuMLx7j5i1e9g6Bj_cQzQ59dE5Vu-Ko,19781
|
|
5
5
|
moose_lib/dmv2_serializer.py,sha256=CL_Pvvg8tJOT8Qk6hywDNzY8MYGhMVdTOw8arZi3jng,49
|
|
6
6
|
moose_lib/internal.py,sha256=7T6PQyU2QLquSSPujO37e9t0tC_QYXpIK2CQom69li4,30453
|
|
7
7
|
moose_lib/main.py,sha256=JWsgza52xEh25AyF61cO1ItJ8VXJHHz8j-4HG445Whg,20380
|
|
8
8
|
moose_lib/query_builder.py,sha256=-L5p2dArBx3SBA-WZPkcCJPemKXnqJw60NHy-wn5wa4,6619
|
|
9
9
|
moose_lib/query_param.py,sha256=kxcR09BMIsEg4o2qetjKrVu1YFRaLfMEzwzyGsKUpvA,6474
|
|
10
|
-
moose_lib/secrets.py,sha256=
|
|
10
|
+
moose_lib/secrets.py,sha256=Ykt_iJZrozMRXAirC_NclDerSJVE5WmGTSyyHrfsTFE,4249
|
|
11
11
|
moose_lib/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
moose_lib/clients/redis_client.py,sha256=BDYjJ582V-rW92qVQ4neZ9Pu7JtDNt8x8jBWApt1XUg,11895
|
|
13
13
|
moose_lib/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,15 +36,16 @@ moose_lib/utilities/sql.py,sha256=kbg1DT5GdsIwgTsMzbsd6SAVf9aWER8DqmT_eKS3XN4,90
|
|
|
36
36
|
tests/__init__.py,sha256=0Gh4yzPkkC3TzBGKhenpMIxJcRhyrrCfxLSfpTZnPMQ,53
|
|
37
37
|
tests/conftest.py,sha256=tsBozrzGuJUO7dRGVcIxCjqi-l77I-DvYnzpbKtmGzE,1003
|
|
38
38
|
tests/test_fixedstring.py,sha256=wQbQLEMXgWRx7B7jtLomkP6HEaNFhhfbRkfVReoJY-E,1552
|
|
39
|
+
tests/test_int_types.py,sha256=2mZjWc0hf-58ECdHw5zCJjcGmYevnJaS5rR-Xgqezxo,6498
|
|
39
40
|
tests/test_moose.py,sha256=mBsx_OYWmL8ppDzL_7Bd7xR6qf_i3-pCIO3wm2iQNaA,2136
|
|
40
41
|
tests/test_olap_table_versioning.py,sha256=ffuJsO5TYKBd2aWV0GWDmosCwL4j518Y_MqYJdrfgDY,7041
|
|
41
42
|
tests/test_query_builder.py,sha256=O3imdFSaqU13kbK1jSQaHbBgynhVmJaApT8DlRqYwJU,1116
|
|
42
43
|
tests/test_redis_client.py,sha256=d9_MLYsJ4ecVil_jPB2gW3Q5aWnavxmmjZg2uYI3LVo,3256
|
|
43
44
|
tests/test_s3queue_config.py,sha256=A-17Lir2kvYoUA0qEaNczOkLKhHPzyp3glka7YqnPws,14490
|
|
44
|
-
tests/test_secrets.py,sha256=
|
|
45
|
+
tests/test_secrets.py,sha256=WFvmVsxwYsl5bD0SiOUR9ZELMBZ3CgY0NR4h7s9Oaww,8707
|
|
45
46
|
tests/test_simple_aggregate.py,sha256=yyFSYHUXskA1aTcwC-KQ9XmgOikeQ8wKXzntsUBIC6w,4055
|
|
46
47
|
tests/test_web_app.py,sha256=iL86sg0NS2k6nP8jIGKZvrtg0BjvaqNTRp7-4T1TTck,6557
|
|
47
|
-
moose_lib-0.6.
|
|
48
|
-
moose_lib-0.6.
|
|
49
|
-
moose_lib-0.6.
|
|
50
|
-
moose_lib-0.6.
|
|
48
|
+
moose_lib-0.6.188.dist-info/METADATA,sha256=pA-Ch_--rOTI4JeT55sonZEv1wMLE2sOQVVAMXGGym0,827
|
|
49
|
+
moose_lib-0.6.188.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
50
|
+
moose_lib-0.6.188.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
|
|
51
|
+
moose_lib-0.6.188.dist-info/RECORD,,
|
tests/test_int_types.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from moose_lib import (
|
|
5
|
+
Key,
|
|
6
|
+
Int8, Int16, Int32, Int64,
|
|
7
|
+
UInt8, UInt16, UInt32, UInt64,
|
|
8
|
+
Float32, Float64,
|
|
9
|
+
)
|
|
10
|
+
from moose_lib.data_models import _to_columns
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_integer_type_aliases():
|
|
14
|
+
"""Test that integer type aliases convert to correct ClickHouse types."""
|
|
15
|
+
|
|
16
|
+
class IntegerTypesTest(BaseModel):
|
|
17
|
+
id: Key[str]
|
|
18
|
+
created_at: datetime
|
|
19
|
+
tiny_int: Int8
|
|
20
|
+
small_int: Int16
|
|
21
|
+
medium_int: Int32
|
|
22
|
+
big_int: Int64
|
|
23
|
+
tiny_uint: UInt8
|
|
24
|
+
small_uint: UInt16
|
|
25
|
+
medium_uint: UInt32
|
|
26
|
+
big_uint: UInt64
|
|
27
|
+
|
|
28
|
+
columns = _to_columns(IntegerTypesTest)
|
|
29
|
+
by_name = {col.name: col for col in columns}
|
|
30
|
+
|
|
31
|
+
# Verify signed integer types
|
|
32
|
+
assert by_name["tiny_int"].data_type == "Int8"
|
|
33
|
+
assert by_name["small_int"].data_type == "Int16"
|
|
34
|
+
assert by_name["medium_int"].data_type == "Int32"
|
|
35
|
+
assert by_name["big_int"].data_type == "Int64"
|
|
36
|
+
|
|
37
|
+
# Verify unsigned integer types
|
|
38
|
+
assert by_name["tiny_uint"].data_type == "UInt8"
|
|
39
|
+
assert by_name["small_uint"].data_type == "UInt16"
|
|
40
|
+
assert by_name["medium_uint"].data_type == "UInt32"
|
|
41
|
+
assert by_name["big_uint"].data_type == "UInt64"
|
|
42
|
+
|
|
43
|
+
# Verify other fields still work
|
|
44
|
+
assert by_name["id"].data_type == "String"
|
|
45
|
+
assert by_name["created_at"].data_type == "DateTime"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_float_type_aliases():
|
|
49
|
+
"""Test that float type aliases convert to correct ClickHouse types."""
|
|
50
|
+
|
|
51
|
+
class FloatTypesTest(BaseModel):
|
|
52
|
+
id: Key[str]
|
|
53
|
+
precision_float: Float32
|
|
54
|
+
double_precision_float: Float64
|
|
55
|
+
|
|
56
|
+
columns = _to_columns(FloatTypesTest)
|
|
57
|
+
by_name = {col.name: col for col in columns}
|
|
58
|
+
|
|
59
|
+
assert by_name["precision_float"].data_type == "Float32"
|
|
60
|
+
assert by_name["double_precision_float"].data_type == "Float64"
|
|
61
|
+
assert by_name["id"].data_type == "String"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_combined_numeric_types():
|
|
65
|
+
"""Test combining integer and float types in a single model."""
|
|
66
|
+
|
|
67
|
+
class NumericTypesTest(BaseModel):
|
|
68
|
+
user_id: UInt64
|
|
69
|
+
age: UInt8
|
|
70
|
+
score: Int32
|
|
71
|
+
latitude: Float64
|
|
72
|
+
longitude: Float64
|
|
73
|
+
precision_value: Float32
|
|
74
|
+
|
|
75
|
+
columns = _to_columns(NumericTypesTest)
|
|
76
|
+
by_name = {col.name: col for col in columns}
|
|
77
|
+
|
|
78
|
+
assert by_name["user_id"].data_type == "UInt64"
|
|
79
|
+
assert by_name["age"].data_type == "UInt8"
|
|
80
|
+
assert by_name["score"].data_type == "Int32"
|
|
81
|
+
assert by_name["latitude"].data_type == "Float64"
|
|
82
|
+
assert by_name["longitude"].data_type == "Float64"
|
|
83
|
+
assert by_name["precision_value"].data_type == "Float32"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_integer_types_as_keys():
|
|
87
|
+
"""Test that integer types can be used as primary keys."""
|
|
88
|
+
|
|
89
|
+
class IntegerKeyTest(BaseModel):
|
|
90
|
+
user_id: Key[UInt64]
|
|
91
|
+
event_id: Key[Int64]
|
|
92
|
+
name: str
|
|
93
|
+
|
|
94
|
+
columns = _to_columns(IntegerKeyTest)
|
|
95
|
+
by_name = {col.name: col for col in columns}
|
|
96
|
+
|
|
97
|
+
assert by_name["user_id"].data_type == "UInt64"
|
|
98
|
+
assert by_name["user_id"].primary_key is True
|
|
99
|
+
assert by_name["event_id"].data_type == "Int64"
|
|
100
|
+
assert by_name["event_id"].primary_key is True
|
|
101
|
+
assert by_name["name"].data_type == "String"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_optional_integer_types():
|
|
105
|
+
"""Test that optional integer types work correctly."""
|
|
106
|
+
from typing import Optional
|
|
107
|
+
|
|
108
|
+
class OptionalIntTest(BaseModel):
|
|
109
|
+
required_count: UInt32
|
|
110
|
+
optional_count: Optional[UInt32]
|
|
111
|
+
optional_score: Optional[Int16]
|
|
112
|
+
|
|
113
|
+
columns = _to_columns(OptionalIntTest)
|
|
114
|
+
by_name = {col.name: col for col in columns}
|
|
115
|
+
|
|
116
|
+
assert by_name["required_count"].data_type == "UInt32"
|
|
117
|
+
assert by_name["required_count"].required is True
|
|
118
|
+
|
|
119
|
+
assert by_name["optional_count"].data_type == "UInt32"
|
|
120
|
+
assert by_name["optional_count"].required is False
|
|
121
|
+
|
|
122
|
+
assert by_name["optional_score"].data_type == "Int16"
|
|
123
|
+
assert by_name["optional_score"].required is False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_uint_common_use_cases():
|
|
127
|
+
"""Test common use cases for unsigned integers."""
|
|
128
|
+
|
|
129
|
+
class CommonUIntUseCases(BaseModel):
|
|
130
|
+
# User/entity IDs (always positive)
|
|
131
|
+
user_id: UInt64
|
|
132
|
+
# Counters (always positive or zero)
|
|
133
|
+
page_views: UInt32
|
|
134
|
+
click_count: UInt32
|
|
135
|
+
# Small enums/flags (0-255)
|
|
136
|
+
status_code: UInt8
|
|
137
|
+
# Port numbers (0-65535)
|
|
138
|
+
port: UInt16
|
|
139
|
+
# Timestamps as unix epoch
|
|
140
|
+
timestamp: UInt64
|
|
141
|
+
|
|
142
|
+
columns = _to_columns(CommonUIntUseCases)
|
|
143
|
+
by_name = {col.name: col for col in columns}
|
|
144
|
+
|
|
145
|
+
assert by_name["user_id"].data_type == "UInt64"
|
|
146
|
+
assert by_name["page_views"].data_type == "UInt32"
|
|
147
|
+
assert by_name["click_count"].data_type == "UInt32"
|
|
148
|
+
assert by_name["status_code"].data_type == "UInt8"
|
|
149
|
+
assert by_name["port"].data_type == "UInt16"
|
|
150
|
+
assert by_name["timestamp"].data_type == "UInt64"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_int_common_use_cases():
|
|
154
|
+
"""Test common use cases for signed integers."""
|
|
155
|
+
|
|
156
|
+
class CommonIntUseCases(BaseModel):
|
|
157
|
+
# Temperature (can be negative)
|
|
158
|
+
temperature: Int16
|
|
159
|
+
# Financial amounts (can be negative for debits)
|
|
160
|
+
balance: Int64
|
|
161
|
+
# Deltas/differences
|
|
162
|
+
delta: Int32
|
|
163
|
+
# Small range values
|
|
164
|
+
offset: Int8
|
|
165
|
+
|
|
166
|
+
columns = _to_columns(CommonIntUseCases)
|
|
167
|
+
by_name = {col.name: col for col in columns}
|
|
168
|
+
|
|
169
|
+
assert by_name["temperature"].data_type == "Int16"
|
|
170
|
+
assert by_name["balance"].data_type == "Int64"
|
|
171
|
+
assert by_name["delta"].data_type == "Int32"
|
|
172
|
+
assert by_name["offset"].data_type == "Int8"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_default_int_still_works():
|
|
176
|
+
"""Test that plain int without type annotation still works as before."""
|
|
177
|
+
|
|
178
|
+
class PlainIntTest(BaseModel):
|
|
179
|
+
plain_int: int
|
|
180
|
+
typed_int: UInt32
|
|
181
|
+
|
|
182
|
+
columns = _to_columns(PlainIntTest)
|
|
183
|
+
by_name = {col.name: col for col in columns}
|
|
184
|
+
|
|
185
|
+
# Plain int should still map to "Int" (default behavior)
|
|
186
|
+
assert by_name["plain_int"].data_type == "Int"
|
|
187
|
+
# Typed int should map to specific type
|
|
188
|
+
assert by_name["typed_int"].data_type == "UInt32"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_default_float_still_works():
|
|
192
|
+
"""Test that plain float without type annotation still works as before."""
|
|
193
|
+
|
|
194
|
+
class PlainFloatTest(BaseModel):
|
|
195
|
+
plain_float: float
|
|
196
|
+
typed_float: Float32
|
|
197
|
+
|
|
198
|
+
columns = _to_columns(PlainFloatTest)
|
|
199
|
+
by_name = {col.name: col for col in columns}
|
|
200
|
+
|
|
201
|
+
# Plain float should still map to "Float64" (default behavior)
|
|
202
|
+
assert by_name["plain_float"].data_type == "Float64"
|
|
203
|
+
# Typed float should map to specific type
|
|
204
|
+
assert by_name["typed_float"].data_type == "Float32"
|
tests/test_secrets.py
CHANGED
|
@@ -5,10 +5,20 @@ which allows users to defer secret resolution until runtime rather than
|
|
|
5
5
|
embedding secrets at build time.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
import pytest
|
|
9
10
|
from moose_lib.secrets import moose_runtime_env, get, MOOSE_RUNTIME_ENV_PREFIX
|
|
10
11
|
|
|
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
|
+
|
|
12
22
|
class TestMooseRuntimeEnvGet:
|
|
13
23
|
"""Tests for the moose_runtime_env.get() method."""
|
|
14
24
|
|
|
File without changes
|
|
File without changes
|