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 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 Key, AggregateFunction, StringToEnumMixin, FixedString, ClickhouseFixedStringSize
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
- """Marks a value to be resolved from an environment variable at runtime.
23
+ """Gets a value from an environment variable, with behavior depending on context.
24
24
 
25
- When you use this function, the value is not read immediately. Instead,
26
- a special marker is created that the Moose CLI will resolve when it
27
- processes your infrastructure configuration.
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
- A marker string that Moose CLI will resolve at runtime
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
- return f"{MOOSE_RUNTIME_ENV_PREFIX}{env_var_name}"
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moose_lib
3
- Version: 0.6.173
3
+ Version: 0.6.188
4
4
  Home-page: https://www.fiveonefour.com/moose
5
5
  Author: Fiveonefour Labs Inc.
6
6
  Author-email: support@fiveonefour.com
@@ -1,13 +1,13 @@
1
- moose_lib/__init__.py,sha256=acIGHDFMHy8LNjwEr091htu7ep-mYH--Uj8MANz5EhE,997
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=YgcTALCacEe08OKcw-s9y8qLDghMZ6_a4GhlJfwOsFM,19293
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=upFjLorfAsiyL3F3UIl2STUFGjC6Pu0-Qe1FGs5S09c,3480
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=g5ZjtOyT7vTz_sRUB71Vd6s715u0gMc4C-_8m_59Mtg,8347
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.173.dist-info/METADATA,sha256=zSguPpMqeILfkpn9rEA9fo9WvcZRHhwufh6J5ZA3pKg,827
48
- moose_lib-0.6.173.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- moose_lib-0.6.173.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
50
- moose_lib-0.6.173.dist-info/RECORD,,
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,,
@@ -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