moose-lib 0.6.110__tar.gz → 0.6.112__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.110 → moose_lib-0.6.112}/PKG-INFO +2 -1
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/config/config_file.py +2 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/config/runtime.py +5 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/__init__.py +8 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/stream.py +66 -14
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/internal.py +3 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/streaming/streaming_function_runner.py +9 -1
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib.egg-info/PKG-INFO +2 -1
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib.egg-info/requires.txt +1 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/setup.py +1 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/README.md +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/__init__.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/blocks.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/clients/__init__.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/clients/redis_client.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/commons.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/config/__init__.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/data_models.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/_registry.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/consumption.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/ingest_api.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/ingest_pipeline.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/life_cycle.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/materialized_view.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/olap_table.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/registry.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/sql_resource.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/types.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/view.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2/workflow.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/dmv2_serializer.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/main.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/query_builder.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/query_param.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/streaming/__init__.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/utilities/__init__.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib/utilities/sql.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib.egg-info/SOURCES.txt +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib.egg-info/dependency_links.txt +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/moose_lib.egg-info/top_level.txt +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/setup.cfg +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/tests/__init__.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/tests/conftest.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/tests/test_moose.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/tests/test_query_builder.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/tests/test_redis_client.py +0 -0
- {moose_lib-0.6.110 → moose_lib-0.6.112}/tests/test_s3queue_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moose_lib
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.112
|
|
4
4
|
Home-page: https://www.fiveonefour.com/moose
|
|
5
5
|
Author: Fiveonefour Labs Inc.
|
|
6
6
|
Author-email: support@fiveonefour.com
|
|
@@ -15,6 +15,7 @@ Requires-Dist: humanfriendly>=10.0
|
|
|
15
15
|
Requires-Dist: clickhouse_connect>=0.7.16
|
|
16
16
|
Requires-Dist: requests>=2.32.3
|
|
17
17
|
Requires-Dist: sqlglot[rs]>=27.16.3
|
|
18
|
+
Requires-Dist: confluent-kafka[json,schemaregistry]>=2.11.1
|
|
18
19
|
Dynamic: author
|
|
19
20
|
Dynamic: author-email
|
|
20
21
|
Dynamic: description
|
|
@@ -32,6 +32,7 @@ class KafkaConfig:
|
|
|
32
32
|
sasl_mechanism: Optional[str] = None
|
|
33
33
|
security_protocol: Optional[str] = None
|
|
34
34
|
namespace: Optional[str] = None
|
|
35
|
+
schema_registry_url: Optional[str] = None
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
@dataclass
|
|
@@ -103,6 +104,7 @@ def read_project_config() -> ProjectConfig:
|
|
|
103
104
|
sasl_mechanism=sec.get("sasl_mechanism"),
|
|
104
105
|
security_protocol=sec.get("security_protocol"),
|
|
105
106
|
namespace=sec.get("namespace"),
|
|
107
|
+
schema_registry_url=sec.get("schema_registry_url"),
|
|
106
108
|
)
|
|
107
109
|
|
|
108
110
|
kafka_cfg = _parse_kafka("kafka_config")
|
|
@@ -30,6 +30,7 @@ class RuntimeKafkaConfig:
|
|
|
30
30
|
sasl_mechanism: Optional[str]
|
|
31
31
|
security_protocol: Optional[str]
|
|
32
32
|
namespace: Optional[str]
|
|
33
|
+
schema_registry_url: Optional[str]
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class ConfigurationRegistry:
|
|
@@ -160,6 +161,8 @@ class ConfigurationRegistry:
|
|
|
160
161
|
_env("MOOSE_KAFKA_CONFIG__SECURITY_PROTOCOL")
|
|
161
162
|
namespace = _env("MOOSE_REDPANDA_CONFIG__NAMESPACE") or \
|
|
162
163
|
_env("MOOSE_KAFKA_CONFIG__NAMESPACE")
|
|
164
|
+
schema_registry_url = _env("MOOSE_REDPANDA_CONFIG__SCHEMA_REGISTRY_URL") or \
|
|
165
|
+
_env("MOOSE_KAFKA_CONFIG__SCHEMA_REGISTRY_URL")
|
|
163
166
|
|
|
164
167
|
file_kafka = config.kafka_config
|
|
165
168
|
|
|
@@ -181,6 +184,8 @@ class ConfigurationRegistry:
|
|
|
181
184
|
security_protocol=security_protocol if security_protocol is not None else (
|
|
182
185
|
file_kafka.security_protocol if file_kafka else None),
|
|
183
186
|
namespace=namespace if namespace is not None else (file_kafka.namespace if file_kafka else None),
|
|
187
|
+
schema_registry_url=schema_registry_url if schema_registry_url is not None else (
|
|
188
|
+
file_kafka.schema_registry_url if file_kafka else None),
|
|
184
189
|
)
|
|
185
190
|
except Exception as e:
|
|
186
191
|
raise RuntimeError(f"Failed to get Kafka configuration: {e}")
|
|
@@ -28,6 +28,10 @@ from .stream import (
|
|
|
28
28
|
Stream,
|
|
29
29
|
DeadLetterModel,
|
|
30
30
|
DeadLetterQueue,
|
|
31
|
+
SubjectLatest,
|
|
32
|
+
SubjectVersion,
|
|
33
|
+
SchemaById,
|
|
34
|
+
KafkaSchemaConfig,
|
|
31
35
|
)
|
|
32
36
|
|
|
33
37
|
from .ingest_api import (
|
|
@@ -117,6 +121,10 @@ __all__ = [
|
|
|
117
121
|
'Stream',
|
|
118
122
|
'DeadLetterModel',
|
|
119
123
|
'DeadLetterQueue',
|
|
124
|
+
'SubjectLatest',
|
|
125
|
+
'SubjectVersion',
|
|
126
|
+
'SchemaById',
|
|
127
|
+
'KafkaSchemaConfig',
|
|
120
128
|
|
|
121
129
|
# Ingestion
|
|
122
130
|
'IngestConfig',
|
|
@@ -8,7 +8,7 @@ import dataclasses
|
|
|
8
8
|
import datetime
|
|
9
9
|
import json
|
|
10
10
|
from typing import Any, Optional, Callable, Union, Literal, Generic
|
|
11
|
-
from pydantic import BaseModel, ConfigDict, AliasGenerator
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, AliasGenerator, Field
|
|
12
12
|
from pydantic.alias_generators import to_camel
|
|
13
13
|
from kafka import KafkaProducer
|
|
14
14
|
|
|
@@ -20,6 +20,24 @@ from ..config.runtime import config_registry, RuntimeKafkaConfig
|
|
|
20
20
|
from ..commons import get_kafka_producer
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
class SubjectLatest(BaseModel):
|
|
24
|
+
name: str = Field(serialization_alias="subjectLatest")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SubjectVersion(BaseModel):
|
|
28
|
+
subject: str
|
|
29
|
+
version: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SchemaById(BaseModel):
|
|
33
|
+
id: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class KafkaSchemaConfig(BaseModel):
|
|
37
|
+
kind: Literal["JSON", "AVRO", "PROTOBUF"]
|
|
38
|
+
reference: Union[SubjectLatest, SubjectVersion, SchemaById]
|
|
39
|
+
|
|
40
|
+
|
|
23
41
|
class StreamConfig(BaseModel):
|
|
24
42
|
"""Configuration for data streams (e.g., Redpanda topics).
|
|
25
43
|
|
|
@@ -41,6 +59,7 @@ class StreamConfig(BaseModel):
|
|
|
41
59
|
default_dead_letter_queue: "Optional[DeadLetterQueue]" = None
|
|
42
60
|
# allow DeadLetterQueue
|
|
43
61
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
62
|
+
schema_config: Optional[KafkaSchemaConfig] = None
|
|
44
63
|
|
|
45
64
|
|
|
46
65
|
class TransformConfig(BaseModel):
|
|
@@ -249,9 +268,9 @@ class Stream(TypedMooseResource, Generic[T]):
|
|
|
249
268
|
return []
|
|
250
269
|
return [b.strip() for b in broker_string.split(",") if b.strip()]
|
|
251
270
|
|
|
252
|
-
def _get_memoized_producer(self) -> tuple[KafkaProducer,
|
|
271
|
+
def _get_memoized_producer(self) -> tuple[KafkaProducer, RuntimeKafkaConfig]:
|
|
253
272
|
"""Create or reuse a KafkaProducer using runtime configuration."""
|
|
254
|
-
cfg = config_registry.get_kafka_config()
|
|
273
|
+
cfg: RuntimeKafkaConfig = config_registry.get_kafka_config()
|
|
255
274
|
current_hash = self._create_kafka_config_hash(cfg)
|
|
256
275
|
|
|
257
276
|
if self._memoized_producer is not None and self._kafka_config_hash == current_hash:
|
|
@@ -267,16 +286,16 @@ class Stream(TypedMooseResource, Generic[T]):
|
|
|
267
286
|
finally:
|
|
268
287
|
self._memoized_producer = None
|
|
269
288
|
|
|
270
|
-
brokers = self._parse_brokers(
|
|
289
|
+
brokers = self._parse_brokers(cfg.broker)
|
|
271
290
|
if not brokers:
|
|
272
|
-
raise RuntimeError(f"No valid broker addresses found in: '{
|
|
291
|
+
raise RuntimeError(f"No valid broker addresses found in: '{cfg.broker}'")
|
|
273
292
|
|
|
274
293
|
producer = get_kafka_producer(
|
|
275
294
|
broker=brokers,
|
|
276
|
-
sasl_username=
|
|
277
|
-
sasl_password=
|
|
278
|
-
sasl_mechanism=
|
|
279
|
-
security_protocol=
|
|
295
|
+
sasl_username=cfg.sasl_username,
|
|
296
|
+
sasl_password=cfg.sasl_password,
|
|
297
|
+
sasl_mechanism=cfg.sasl_mechanism,
|
|
298
|
+
security_protocol=cfg.security_protocol,
|
|
280
299
|
value_serializer=lambda v: v.model_dump_json().encode('utf-8'),
|
|
281
300
|
acks="all",
|
|
282
301
|
)
|
|
@@ -300,7 +319,9 @@ class Stream(TypedMooseResource, Generic[T]):
|
|
|
300
319
|
def send(self, values: ZeroOrMany[T]) -> None:
|
|
301
320
|
"""Send one or more records to this stream's Kafka topic.
|
|
302
321
|
|
|
303
|
-
|
|
322
|
+
If `schema_registry` (JSON) is configured, resolve schema id and
|
|
323
|
+
send using Confluent wire format (0x00 + 4-byte schema id + JSON bytes).
|
|
324
|
+
Otherwise, values are JSON-serialized.
|
|
304
325
|
"""
|
|
305
326
|
# Normalize inputs to a flat list of records
|
|
306
327
|
filtered: list[T] = []
|
|
@@ -326,11 +347,42 @@ class Stream(TypedMooseResource, Generic[T]):
|
|
|
326
347
|
)
|
|
327
348
|
|
|
328
349
|
producer, cfg = self._get_memoized_producer()
|
|
329
|
-
topic = self._build_full_topic_name(
|
|
350
|
+
topic = self._build_full_topic_name(cfg.namespace)
|
|
330
351
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
352
|
+
sr = self.config.schema_config
|
|
353
|
+
if sr is not None:
|
|
354
|
+
if sr.kind != "JSON":
|
|
355
|
+
raise NotImplementedError("Currently JSON Schema is supported.")
|
|
356
|
+
try:
|
|
357
|
+
from confluent_kafka.schema_registry import SchemaRegistryClient
|
|
358
|
+
from confluent_kafka.schema_registry.json_schema import JSONSerializer
|
|
359
|
+
except Exception as e:
|
|
360
|
+
raise RuntimeError(
|
|
361
|
+
"confluent-kafka[json,schemaregistry] is required for Schema Registry JSON"
|
|
362
|
+
) from e
|
|
363
|
+
|
|
364
|
+
sr_url = cfg.schema_registry_url
|
|
365
|
+
if not sr_url:
|
|
366
|
+
raise RuntimeError("Schema Registry URL not configured")
|
|
367
|
+
client = SchemaRegistryClient({"url": sr_url})
|
|
368
|
+
|
|
369
|
+
if isinstance(sr.reference, SchemaById):
|
|
370
|
+
schema = client.get_schema(sr.reference.id)
|
|
371
|
+
elif isinstance(sr.reference, SubjectLatest):
|
|
372
|
+
schema = client.get_latest_version(sr.reference.name).schema
|
|
373
|
+
else:
|
|
374
|
+
schema = client.get_version(sr.reference.subject, sr.reference.version).schema
|
|
375
|
+
|
|
376
|
+
serializer = JSONSerializer(schema, client)
|
|
377
|
+
|
|
378
|
+
for rec in filtered:
|
|
379
|
+
value_bytes = serializer(rec.model_dump())
|
|
380
|
+
producer.send(topic, value=value_bytes)
|
|
381
|
+
producer.flush()
|
|
382
|
+
else:
|
|
383
|
+
for rec in filtered:
|
|
384
|
+
producer.send(topic, value=rec)
|
|
385
|
+
producer.flush()
|
|
334
386
|
|
|
335
387
|
|
|
336
388
|
class DeadLetterModel(BaseModel, Generic[T]):
|
|
@@ -24,6 +24,7 @@ from moose_lib.dmv2 import (
|
|
|
24
24
|
MaterializedView,
|
|
25
25
|
SqlResource
|
|
26
26
|
)
|
|
27
|
+
from moose_lib.dmv2.stream import KafkaSchemaConfig
|
|
27
28
|
from pydantic.alias_generators import to_camel
|
|
28
29
|
from pydantic.json_schema import JsonSchemaValue
|
|
29
30
|
|
|
@@ -163,6 +164,7 @@ class TopicConfig(BaseModel):
|
|
|
163
164
|
consumers: List[Consumer]
|
|
164
165
|
metadata: Optional[dict] = None
|
|
165
166
|
life_cycle: Optional[str] = None
|
|
167
|
+
schema_config: Optional[KafkaSchemaConfig] = None
|
|
166
168
|
|
|
167
169
|
|
|
168
170
|
class IngestApiConfig(BaseModel):
|
|
@@ -477,6 +479,7 @@ def to_infra_map() -> dict:
|
|
|
477
479
|
consumers=consumers,
|
|
478
480
|
metadata=getattr(stream, "metadata", None),
|
|
479
481
|
life_cycle=stream.config.life_cycle.value if stream.config.life_cycle else None,
|
|
482
|
+
schema_config=stream.config.schema_config,
|
|
480
483
|
)
|
|
481
484
|
|
|
482
485
|
for name, api in get_ingest_apis().items():
|
|
@@ -299,11 +299,19 @@ def create_consumer() -> KafkaConsumer:
|
|
|
299
299
|
Returns:
|
|
300
300
|
Configured KafkaConsumer instance
|
|
301
301
|
"""
|
|
302
|
+
def _sr_json_deserializer(m: bytes):
|
|
303
|
+
if m is None:
|
|
304
|
+
return None
|
|
305
|
+
# Schema Registry JSON envelope: 0x00 + 4-byte schema ID (big-endian) + JSON
|
|
306
|
+
if len(m) >= 5 and m[0] == 0x00:
|
|
307
|
+
m = m[5:]
|
|
308
|
+
return json.loads(m.decode("utf-8"))
|
|
309
|
+
|
|
302
310
|
kwargs = dict(
|
|
303
311
|
broker=broker,
|
|
304
312
|
client_id="python_streaming_function_consumer",
|
|
305
313
|
group_id=streaming_function_id,
|
|
306
|
-
value_deserializer=
|
|
314
|
+
value_deserializer=_sr_json_deserializer,
|
|
307
315
|
sasl_username=sasl_config.get("username"),
|
|
308
316
|
sasl_password=sasl_config.get("password"),
|
|
309
317
|
sasl_mechanism=sasl_config.get("mechanism"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moose_lib
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.112
|
|
4
4
|
Home-page: https://www.fiveonefour.com/moose
|
|
5
5
|
Author: Fiveonefour Labs Inc.
|
|
6
6
|
Author-email: support@fiveonefour.com
|
|
@@ -15,6 +15,7 @@ Requires-Dist: humanfriendly>=10.0
|
|
|
15
15
|
Requires-Dist: clickhouse_connect>=0.7.16
|
|
16
16
|
Requires-Dist: requests>=2.32.3
|
|
17
17
|
Requires-Dist: sqlglot[rs]>=27.16.3
|
|
18
|
+
Requires-Dist: confluent-kafka[json,schemaregistry]>=2.11.1
|
|
18
19
|
Dynamic: author
|
|
19
20
|
Dynamic: author-email
|
|
20
21
|
Dynamic: description
|
|
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
|