investify-utils 2.0.0a9__tar.gz → 2.0.0a11__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 investify-utils might be problematic. Click here for more details.
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/PKG-INFO +1 -1
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/__init__.py +1 -1
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/kafka/__init__.py +10 -10
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/kafka/sync_producer.py +1 -1
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/postgres/__init__.py +8 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/s3/__init__.py +7 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/s3/sync_client.py +7 -6
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/pyproject.toml +1 -1
- investify_utils-2.0.0a9/investify_utils/kafka/async_producer.py +0 -152
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/README.md +0 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/helpers.py +0 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/kafka/sync_consumer.py +0 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/logging.py +0 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/postgres/async_client.py +0 -0
- {investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/postgres/sync_client.py +0 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Kafka Avro producer and consumer clients.
|
|
2
|
+
Kafka Avro producer and consumer clients (sync only).
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Usage:
|
|
5
5
|
from investify_utils.kafka import AvroProducer, AvroConsumer
|
|
6
|
-
|
|
7
|
-
Async producer (for LangGraph, FastAPI):
|
|
8
|
-
from investify_utils.kafka import AsyncAvroProducer
|
|
9
6
|
"""
|
|
10
7
|
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from investify_utils.kafka.sync_consumer import AvroConsumer, OffsetTracker
|
|
14
|
+
from investify_utils.kafka.sync_producer import AvroProducer
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
def __getattr__(name: str):
|
|
13
18
|
"""Lazy import to avoid loading confluent-kafka if not needed."""
|
|
@@ -23,10 +28,6 @@ def __getattr__(name: str):
|
|
|
23
28
|
from investify_utils.kafka.sync_consumer import OffsetTracker
|
|
24
29
|
|
|
25
30
|
return OffsetTracker
|
|
26
|
-
if name == "AsyncAvroProducer":
|
|
27
|
-
from investify_utils.kafka.async_producer import AsyncAvroProducer
|
|
28
|
-
|
|
29
|
-
return AsyncAvroProducer
|
|
30
31
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
31
32
|
|
|
32
33
|
|
|
@@ -34,5 +35,4 @@ __all__ = [
|
|
|
34
35
|
"AvroProducer",
|
|
35
36
|
"AvroConsumer",
|
|
36
37
|
"OffsetTracker",
|
|
37
|
-
"AsyncAvroProducer",
|
|
38
38
|
]
|
|
@@ -23,7 +23,7 @@ Usage:
|
|
|
23
23
|
|
|
24
24
|
import logging
|
|
25
25
|
import threading
|
|
26
|
-
from
|
|
26
|
+
from collections.abc import Callable
|
|
27
27
|
|
|
28
28
|
from confluent_kafka import SerializingProducer
|
|
29
29
|
from confluent_kafka.schema_registry import SchemaRegistryClient, record_subject_name_strategy
|
|
@@ -8,6 +8,14 @@ Async client (asyncpg):
|
|
|
8
8
|
from investify_utils.postgres import AsyncPostgresClient
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from investify_utils.postgres.async_client import AsyncPostgresClient
|
|
17
|
+
from investify_utils.postgres.sync_client import PostgresClient
|
|
18
|
+
|
|
11
19
|
|
|
12
20
|
def __getattr__(name: str):
|
|
13
21
|
"""Lazy import to avoid loading dependencies for unused clients."""
|
|
@@ -5,6 +5,13 @@ Usage:
|
|
|
5
5
|
from investify_utils.s3 import S3Client
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from investify_utils.s3.sync_client import S3Client
|
|
14
|
+
|
|
8
15
|
|
|
9
16
|
def __getattr__(name: str):
|
|
10
17
|
"""Lazy import to avoid loading boto3 if not needed."""
|
|
@@ -103,11 +103,13 @@ class S3Client:
|
|
|
103
103
|
|
|
104
104
|
for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
|
|
105
105
|
for obj in page.get("Contents", []):
|
|
106
|
-
objects.append(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
objects.append(
|
|
107
|
+
{
|
|
108
|
+
"Key": obj["Key"],
|
|
109
|
+
"Size": obj["Size"],
|
|
110
|
+
"LastModified": obj["LastModified"],
|
|
111
|
+
}
|
|
112
|
+
)
|
|
111
113
|
if max_keys and len(objects) >= max_keys:
|
|
112
114
|
return objects
|
|
113
115
|
|
|
@@ -223,4 +225,3 @@ class S3Client:
|
|
|
223
225
|
if e.response["Error"]["Code"] == "404":
|
|
224
226
|
return False
|
|
225
227
|
raise
|
|
226
|
-
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Asynchronous Avro producer using confluent-kafka with asyncio.
|
|
3
|
-
|
|
4
|
-
Features:
|
|
5
|
-
- Non-blocking produce with async/await
|
|
6
|
-
- Background asyncio task for polling
|
|
7
|
-
- Suitable for async frameworks (LangGraph, FastAPI)
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
from investify_utils.kafka import AsyncAvroProducer
|
|
11
|
-
|
|
12
|
-
producer = AsyncAvroProducer(
|
|
13
|
-
topic="my-topic",
|
|
14
|
-
subject="my-topic-value",
|
|
15
|
-
schema_registry_url="http://localhost:8081",
|
|
16
|
-
bootstrap_servers="localhost:9092",
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
# In async context
|
|
20
|
-
await producer.produce(key="key1", value={"field": "value"})
|
|
21
|
-
producer.close()
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
import asyncio
|
|
25
|
-
import logging
|
|
26
|
-
from typing import Callable
|
|
27
|
-
|
|
28
|
-
from confluent_kafka import KafkaException, SerializingProducer
|
|
29
|
-
from confluent_kafka.schema_registry import SchemaRegistryClient, record_subject_name_strategy
|
|
30
|
-
from confluent_kafka.schema_registry.avro import AvroSerializer
|
|
31
|
-
from confluent_kafka.serialization import StringSerializer
|
|
32
|
-
|
|
33
|
-
logger = logging.getLogger(__name__)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class AsyncAvroProducer:
|
|
37
|
-
"""
|
|
38
|
-
Asynchronous Avro producer for async frameworks.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
topic: Kafka topic name
|
|
42
|
-
subject: Schema Registry subject name
|
|
43
|
-
schema_registry_url: Schema Registry URL
|
|
44
|
-
bootstrap_servers: Kafka bootstrap servers
|
|
45
|
-
**kwargs: Additional Kafka producer config
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def __init__(
|
|
49
|
-
self,
|
|
50
|
-
topic: str,
|
|
51
|
-
subject: str,
|
|
52
|
-
schema_registry_url: str,
|
|
53
|
-
bootstrap_servers: str,
|
|
54
|
-
**kwargs,
|
|
55
|
-
):
|
|
56
|
-
self.topic = topic
|
|
57
|
-
self._schema_registry_url = schema_registry_url
|
|
58
|
-
self._bootstrap_servers = bootstrap_servers
|
|
59
|
-
self._subject = subject
|
|
60
|
-
self._kwargs = kwargs
|
|
61
|
-
self._producer: SerializingProducer | None = None
|
|
62
|
-
self._poll_task: asyncio.Task | None = None
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def producer(self) -> SerializingProducer:
|
|
66
|
-
"""Lazy producer initialization."""
|
|
67
|
-
if self._producer is None:
|
|
68
|
-
schema_registry_client = SchemaRegistryClient({"url": self._schema_registry_url})
|
|
69
|
-
registered_schema = schema_registry_client.get_latest_version(self._subject)
|
|
70
|
-
schema_str = registered_schema.schema.schema_str
|
|
71
|
-
|
|
72
|
-
avro_serializer = AvroSerializer(
|
|
73
|
-
schema_registry_client,
|
|
74
|
-
schema_str,
|
|
75
|
-
conf={
|
|
76
|
-
"auto.register.schemas": False,
|
|
77
|
-
"subject.name.strategy": record_subject_name_strategy,
|
|
78
|
-
},
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
producer_config = {
|
|
82
|
-
"bootstrap.servers": self._bootstrap_servers,
|
|
83
|
-
"key.serializer": StringSerializer("utf_8"),
|
|
84
|
-
"value.serializer": avro_serializer,
|
|
85
|
-
**self._kwargs,
|
|
86
|
-
}
|
|
87
|
-
self._producer = SerializingProducer(producer_config)
|
|
88
|
-
|
|
89
|
-
# Start background polling task
|
|
90
|
-
self._poll_task = asyncio.create_task(self._poll_loop())
|
|
91
|
-
|
|
92
|
-
return self._producer
|
|
93
|
-
|
|
94
|
-
async def _poll_loop(self) -> None:
|
|
95
|
-
"""Background task for polling delivery callbacks."""
|
|
96
|
-
while True:
|
|
97
|
-
self._producer.poll(0.1)
|
|
98
|
-
await asyncio.sleep(0.1)
|
|
99
|
-
|
|
100
|
-
async def produce(
|
|
101
|
-
self,
|
|
102
|
-
value: dict,
|
|
103
|
-
key: str | None = None,
|
|
104
|
-
on_delivery: Callable | None = None,
|
|
105
|
-
) -> asyncio.Future:
|
|
106
|
-
"""
|
|
107
|
-
Produce a message asynchronously.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
value: Message value (dict matching Avro schema)
|
|
111
|
-
key: Optional message key
|
|
112
|
-
on_delivery: Optional callback(err, msg) for delivery confirmation
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
Future that resolves to the delivered message
|
|
116
|
-
"""
|
|
117
|
-
loop = asyncio.get_running_loop()
|
|
118
|
-
result = loop.create_future()
|
|
119
|
-
|
|
120
|
-
def ack(err, msg):
|
|
121
|
-
if err:
|
|
122
|
-
loop.call_soon_threadsafe(result.set_exception, KafkaException(err))
|
|
123
|
-
else:
|
|
124
|
-
loop.call_soon_threadsafe(result.set_result, msg)
|
|
125
|
-
if on_delivery:
|
|
126
|
-
loop.call_soon_threadsafe(on_delivery, err, msg)
|
|
127
|
-
|
|
128
|
-
self.producer.produce(self.topic, key=key, value=value, on_delivery=ack)
|
|
129
|
-
return await result
|
|
130
|
-
|
|
131
|
-
def flush(self, timeout: float = 10.0) -> int:
|
|
132
|
-
"""
|
|
133
|
-
Wait for all messages to be delivered.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
timeout: Maximum time to wait in seconds
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
Number of messages still in queue
|
|
140
|
-
"""
|
|
141
|
-
if self._producer:
|
|
142
|
-
return self._producer.flush(timeout)
|
|
143
|
-
return 0
|
|
144
|
-
|
|
145
|
-
def close(self) -> None:
|
|
146
|
-
"""Cancel polling task and flush pending messages."""
|
|
147
|
-
if self._poll_task:
|
|
148
|
-
self._poll_task.cancel()
|
|
149
|
-
self._poll_task = None
|
|
150
|
-
if self._producer:
|
|
151
|
-
self._producer.flush()
|
|
152
|
-
self._producer = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/postgres/async_client.py
RENAMED
|
File without changes
|
{investify_utils-2.0.0a9 → investify_utils-2.0.0a11}/investify_utils/postgres/sync_client.py
RENAMED
|
File without changes
|