moose-lib 0.6.85__tar.gz → 0.6.87__tar.gz
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.
Potentially problematic release.
This version of moose-lib might be problematic. Click here for more details.
- {moose_lib-0.6.85 → moose_lib-0.6.87}/PKG-INFO +1 -1
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/blocks.py +6 -57
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/clients/redis_client.py +103 -20
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib.egg-info/PKG-INFO +1 -1
- {moose_lib-0.6.85 → moose_lib-0.6.87}/README.md +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/clients/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/commons.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/config/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/config/config_file.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/config/runtime.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/data_models.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/_registry.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/consumption.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/ingest_api.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/ingest_pipeline.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/life_cycle.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/materialized_view.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/olap_table.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/registry.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/sql_resource.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/stream.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/types.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/view.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2/workflow.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/dmv2_serializer.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/internal.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/main.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/query_builder.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/query_param.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/streaming/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/streaming/streaming_function_runner.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/utilities/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib/utilities/sql.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib.egg-info/SOURCES.txt +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib.egg-info/dependency_links.txt +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib.egg-info/requires.txt +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/moose_lib.egg-info/top_level.txt +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/setup.cfg +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/setup.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/tests/__init__.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/tests/conftest.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/tests/test_moose.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/tests/test_query_builder.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/tests/test_redis_client.py +0 -0
- {moose_lib-0.6.85 → moose_lib-0.6.87}/tests/test_s3queue_config.py +0 -0
|
@@ -97,61 +97,10 @@ class TableConfig:
|
|
|
97
97
|
columns: Dict[str, str]
|
|
98
98
|
order_by: Optional[str] = None
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
s3_path: str,
|
|
105
|
-
format: str,
|
|
106
|
-
order_by: Optional[str] = None,
|
|
107
|
-
**kwargs) -> 'TableConfig':
|
|
108
|
-
"""Create a table with S3Queue engine"""
|
|
109
|
-
return cls(
|
|
110
|
-
name=name,
|
|
111
|
-
columns=columns,
|
|
112
|
-
engine=S3QueueEngine(s3_path=s3_path, format=format, **kwargs),
|
|
113
|
-
order_by=order_by
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
@classmethod
|
|
117
|
-
def with_merge_tree(cls,
|
|
118
|
-
name: str,
|
|
119
|
-
columns: Dict[str, str],
|
|
120
|
-
order_by: Optional[str] = None,
|
|
121
|
-
deduplicate: bool = False,
|
|
122
|
-
**kwargs) -> 'TableConfig':
|
|
123
|
-
"""Create a table with MergeTree or ReplacingMergeTree engine"""
|
|
124
|
-
engine = ReplacingMergeTreeEngine() if deduplicate else MergeTreeEngine()
|
|
125
|
-
return cls(
|
|
126
|
-
name=name,
|
|
127
|
-
columns=columns,
|
|
128
|
-
engine=engine,
|
|
129
|
-
order_by=order_by
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
@classmethod
|
|
133
|
-
def with_replacing_merge_tree(cls,
|
|
134
|
-
name: str,
|
|
135
|
-
columns: Dict[str, str],
|
|
136
|
-
order_by: Optional[str] = None,
|
|
137
|
-
ver: Optional[str] = None,
|
|
138
|
-
is_deleted: Optional[str] = None,
|
|
139
|
-
**kwargs) -> 'TableConfig':
|
|
140
|
-
"""Create a table with ReplacingMergeTree engine
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
name: Table name
|
|
144
|
-
columns: Column definitions
|
|
145
|
-
order_by: Order by clause
|
|
146
|
-
ver: Optional version column name
|
|
147
|
-
is_deleted: Optional is_deleted column name (requires ver)
|
|
148
|
-
"""
|
|
149
|
-
return cls(
|
|
150
|
-
name=name,
|
|
151
|
-
columns=columns,
|
|
152
|
-
engine=ReplacingMergeTreeEngine(ver=ver, is_deleted=is_deleted),
|
|
153
|
-
order_by=order_by
|
|
154
|
-
)
|
|
100
|
+
# Note: Factory methods (with_s3_queue, with_merge_tree, with_replacing_merge_tree)
|
|
101
|
+
# were removed in ENG-856. Use direct configuration instead, e.g.:
|
|
102
|
+
# TableConfig(name="table", columns={...}, engine=S3QueueEngine(s3_path="...", format="..."))
|
|
103
|
+
# TableConfig(name="table", columns={...}, engine=ReplacingMergeTreeEngine(ver="updated_at"))
|
|
155
104
|
|
|
156
105
|
# ==========================
|
|
157
106
|
# Legacy API Support (Deprecated)
|
|
@@ -204,8 +153,8 @@ def migrate_legacy_config(legacy: TableCreateOptions) -> TableConfig:
|
|
|
204
153
|
# Show deprecation warning
|
|
205
154
|
warnings.warn(
|
|
206
155
|
"Using deprecated TableCreateOptions. Please migrate to TableConfig:\n"
|
|
207
|
-
"- For S3Queue: Use TableConfig
|
|
208
|
-
"- For deduplication: Use engine=ReplacingMergeTreeEngine()\n"
|
|
156
|
+
"- For S3Queue: Use TableConfig(name='table', columns={...}, engine=S3QueueEngine(s3_path='...', format='...'))\n"
|
|
157
|
+
"- For deduplication: Use TableConfig(name='table', columns={...}, engine=ReplacingMergeTreeEngine())\n"
|
|
209
158
|
"See documentation for examples.",
|
|
210
159
|
DeprecationWarning,
|
|
211
160
|
stacklevel=2
|
|
@@ -4,11 +4,12 @@ import os
|
|
|
4
4
|
import redis
|
|
5
5
|
import threading
|
|
6
6
|
from pydantic import BaseModel
|
|
7
|
-
from typing import Optional, TypeVar, Type, Union, TypeAlias
|
|
7
|
+
from typing import Optional, TypeVar, Type, Union, TypeAlias, List, Any
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
T = TypeVar(
|
|
11
|
-
SupportedValue: TypeAlias = Union[str, BaseModel]
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
SupportedValue: TypeAlias = Union[str, BaseModel, List[Any]]
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
class MooseCache:
|
|
14
15
|
"""
|
|
@@ -18,6 +19,7 @@ class MooseCache:
|
|
|
18
19
|
Example:
|
|
19
20
|
cache = MooseCache() # Gets or creates the singleton instance
|
|
20
21
|
"""
|
|
22
|
+
|
|
21
23
|
_instance = None
|
|
22
24
|
_redis_url: str
|
|
23
25
|
_key_prefix: str
|
|
@@ -36,10 +38,10 @@ class MooseCache:
|
|
|
36
38
|
if self._client is not None:
|
|
37
39
|
return
|
|
38
40
|
|
|
39
|
-
self._redis_url = os.getenv(
|
|
40
|
-
prefix = os.getenv(
|
|
41
|
+
self._redis_url = os.getenv("MOOSE_REDIS_CONFIG__URL", "redis://127.0.0.1:6379")
|
|
42
|
+
prefix = os.getenv("MOOSE_REDIS_CONFIG__KEY_PREFIX", "MS")
|
|
41
43
|
# 30 seconds of inactivity before disconnecting
|
|
42
|
-
self._idle_timeout = int(os.getenv(
|
|
44
|
+
self._idle_timeout = int(os.getenv("MOOSE_REDIS_CONFIG__IDLE_TIMEOUT", "30"))
|
|
43
45
|
self._key_prefix = f"{prefix}::moosecache::"
|
|
44
46
|
|
|
45
47
|
self._ensure_connected()
|
|
@@ -65,14 +67,16 @@ class MooseCache:
|
|
|
65
67
|
self._clear_disconnect_timer()
|
|
66
68
|
self._disconnect_timer.start()
|
|
67
69
|
|
|
68
|
-
def set(
|
|
70
|
+
def set(
|
|
71
|
+
self, key: str, value: SupportedValue, ttl_seconds: Optional[int] = None
|
|
72
|
+
) -> None:
|
|
69
73
|
"""
|
|
70
|
-
Sets a value in the cache.
|
|
74
|
+
Sets a value in the cache. Accepts strings, Pydantic models, or lists.
|
|
71
75
|
Objects are automatically JSON stringified.
|
|
72
76
|
|
|
73
77
|
Args:
|
|
74
78
|
key: The key to store the value under
|
|
75
|
-
value: The value to store. Must be a string
|
|
79
|
+
value: The value to store. Must be a string, Pydantic model, or list
|
|
76
80
|
ttl_seconds: Optional time-to-live in seconds. If not provided, defaults to 1 hour (3600 seconds).
|
|
77
81
|
Must be a non-negative number. If 0, the key will expire immediately.
|
|
78
82
|
|
|
@@ -85,12 +89,15 @@ class MooseCache:
|
|
|
85
89
|
baz: int
|
|
86
90
|
qux: bool
|
|
87
91
|
cache.set("foo:config", Config(baz=123, qux=True))
|
|
92
|
+
|
|
93
|
+
### Store a list
|
|
94
|
+
cache.set("foo:list", [{"id": 1}, {"id": 2}])
|
|
88
95
|
"""
|
|
89
96
|
try:
|
|
90
97
|
# Validate value type
|
|
91
|
-
if not isinstance(value, (str, BaseModel)):
|
|
98
|
+
if not isinstance(value, (str, BaseModel, list)):
|
|
92
99
|
raise TypeError(
|
|
93
|
-
f"Value must be a string
|
|
100
|
+
f"Value must be a string, Pydantic model, or list. Got {type(value).__name__}"
|
|
94
101
|
)
|
|
95
102
|
|
|
96
103
|
# Validate TTL
|
|
@@ -99,27 +106,39 @@ class MooseCache:
|
|
|
99
106
|
|
|
100
107
|
self._ensure_connected()
|
|
101
108
|
prefixed_key = self._get_prefixed_key(key)
|
|
109
|
+
metadata_key = f"{prefixed_key}:__type__"
|
|
102
110
|
|
|
103
111
|
if isinstance(value, str):
|
|
104
112
|
string_value = value
|
|
105
|
-
|
|
113
|
+
value_type = "str"
|
|
114
|
+
elif isinstance(value, BaseModel):
|
|
106
115
|
string_value = value.model_dump_json()
|
|
116
|
+
value_type = f"pydantic:{value.__class__.__name__}"
|
|
117
|
+
else: # list
|
|
118
|
+
string_value = json.dumps(value)
|
|
119
|
+
value_type = "list"
|
|
107
120
|
|
|
108
121
|
# Use provided TTL or default to 1 hour
|
|
109
122
|
ttl = ttl_seconds if ttl_seconds is not None else 3600
|
|
110
|
-
|
|
123
|
+
|
|
124
|
+
# Store the value and its type metadata
|
|
125
|
+
pipe = self._client.pipeline()
|
|
126
|
+
pipe.setex(prefixed_key, ttl, string_value)
|
|
127
|
+
pipe.setex(metadata_key, ttl, value_type)
|
|
128
|
+
pipe.execute()
|
|
129
|
+
|
|
111
130
|
except Exception as e:
|
|
112
131
|
print(f"Error setting cache key {key}: {e}")
|
|
113
132
|
raise
|
|
114
133
|
|
|
115
134
|
def get(self, key: str, type_hint: Type[T] = str) -> Optional[T]:
|
|
116
135
|
"""
|
|
117
|
-
Retrieves a value from the cache.
|
|
136
|
+
Retrieves a value from the cache. Supports strings, Pydantic models, or lists.
|
|
118
137
|
The type_hint parameter determines how the value will be parsed and returned.
|
|
119
138
|
|
|
120
139
|
Args:
|
|
121
140
|
key: The key to retrieve
|
|
122
|
-
type_hint: Type hint for the return value. Must be str or a Pydantic model class.
|
|
141
|
+
type_hint: Type hint for the return value. Must be str, list, or a Pydantic model class.
|
|
123
142
|
Defaults to str.
|
|
124
143
|
|
|
125
144
|
Returns:
|
|
@@ -134,25 +153,82 @@ class MooseCache:
|
|
|
134
153
|
baz: int
|
|
135
154
|
qux: bool
|
|
136
155
|
config = cache.get("foo:config", Config)
|
|
156
|
+
|
|
157
|
+
### Get a list
|
|
158
|
+
items = cache.get("foo:list", list)
|
|
137
159
|
"""
|
|
138
160
|
try:
|
|
139
161
|
# Validate type_hint
|
|
140
162
|
if not isinstance(type_hint, type):
|
|
141
163
|
raise TypeError("type_hint must be a type")
|
|
142
|
-
if not (
|
|
164
|
+
if not (
|
|
165
|
+
type_hint is str
|
|
166
|
+
or type_hint is list
|
|
167
|
+
or issubclass(type_hint, BaseModel)
|
|
168
|
+
):
|
|
143
169
|
raise TypeError(
|
|
144
|
-
"type_hint must be str or a Pydantic model class. "
|
|
170
|
+
"type_hint must be str, list, or a Pydantic model class. "
|
|
145
171
|
f"Got {type_hint.__name__}"
|
|
146
172
|
)
|
|
147
173
|
|
|
148
174
|
self._ensure_connected()
|
|
149
175
|
prefixed_key = self._get_prefixed_key(key)
|
|
150
|
-
|
|
176
|
+
metadata_key = f"{prefixed_key}:__type__"
|
|
177
|
+
|
|
178
|
+
# Get both the value and metadata in a single pipeline call
|
|
179
|
+
pipe = self._client.pipeline()
|
|
180
|
+
pipe.get(prefixed_key)
|
|
181
|
+
pipe.get(metadata_key)
|
|
182
|
+
results = pipe.execute()
|
|
183
|
+
|
|
184
|
+
value, stored_type = results[0], results[1]
|
|
151
185
|
|
|
152
186
|
if value is None:
|
|
153
187
|
return None
|
|
154
|
-
|
|
188
|
+
|
|
189
|
+
# If we have metadata, use it to determine the correct deserialization
|
|
190
|
+
if stored_type:
|
|
191
|
+
if stored_type == "str":
|
|
192
|
+
if type_hint is str:
|
|
193
|
+
return value
|
|
194
|
+
elif type_hint is list:
|
|
195
|
+
# Type mismatch: stored as string but requested as list
|
|
196
|
+
raise ValueError(f"Value was stored as string but requested as list")
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(f"Value was stored as string but requested as {type_hint.__name__}")
|
|
199
|
+
|
|
200
|
+
elif stored_type == "list":
|
|
201
|
+
parsed_value = json.loads(value)
|
|
202
|
+
if type_hint is list:
|
|
203
|
+
return parsed_value
|
|
204
|
+
elif type_hint is str:
|
|
205
|
+
# Type mismatch: stored as list but requested as string
|
|
206
|
+
raise ValueError(f"Value was stored as list but requested as string")
|
|
207
|
+
else:
|
|
208
|
+
raise ValueError(f"Value was stored as list but requested as {type_hint.__name__}")
|
|
209
|
+
|
|
210
|
+
elif stored_type.startswith("pydantic:"):
|
|
211
|
+
parsed_value = json.loads(value)
|
|
212
|
+
if isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
|
|
213
|
+
return type_hint.model_validate(parsed_value)
|
|
214
|
+
elif type_hint is str:
|
|
215
|
+
# Type mismatch: stored as Pydantic but requested as string
|
|
216
|
+
raise ValueError(f"Value was stored as Pydantic model but requested as string")
|
|
217
|
+
elif type_hint is list:
|
|
218
|
+
# Type mismatch: stored as Pydantic but requested as list
|
|
219
|
+
raise ValueError(f"Value was stored as Pydantic model but requested as list")
|
|
220
|
+
else:
|
|
221
|
+
return type_hint.model_validate(parsed_value)
|
|
222
|
+
|
|
223
|
+
# Backwards compatibility: no metadata found, use legacy behavior
|
|
224
|
+
# But remove the problematic auto-detection for strings
|
|
225
|
+
if type_hint is str:
|
|
155
226
|
return value
|
|
227
|
+
elif type_hint is list:
|
|
228
|
+
try:
|
|
229
|
+
return json.loads(value)
|
|
230
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
231
|
+
raise ValueError(f"Failed to parse cached value as list: {e}")
|
|
156
232
|
elif isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
|
|
157
233
|
try:
|
|
158
234
|
parsed = json.loads(value)
|
|
@@ -179,7 +255,13 @@ class MooseCache:
|
|
|
179
255
|
try:
|
|
180
256
|
self._ensure_connected()
|
|
181
257
|
prefixed_key = self._get_prefixed_key(key)
|
|
182
|
-
|
|
258
|
+
metadata_key = f"{prefixed_key}:__type__"
|
|
259
|
+
|
|
260
|
+
# Delete both the value and its metadata
|
|
261
|
+
pipe = self._client.pipeline()
|
|
262
|
+
pipe.delete(prefixed_key)
|
|
263
|
+
pipe.delete(metadata_key)
|
|
264
|
+
pipe.execute()
|
|
183
265
|
except Exception as e:
|
|
184
266
|
print(f"Error deleting cache key {key}: {e}")
|
|
185
267
|
raise
|
|
@@ -198,6 +280,7 @@ class MooseCache:
|
|
|
198
280
|
try:
|
|
199
281
|
self._ensure_connected()
|
|
200
282
|
prefixed_key = self._get_prefixed_key(key_prefix)
|
|
283
|
+
# Get both data keys and metadata keys
|
|
201
284
|
keys = self._client.keys(f"{prefixed_key}*")
|
|
202
285
|
if keys:
|
|
203
286
|
self._client.delete(*keys)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|