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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: investify-utils
3
- Version: 2.0.0a9
3
+ Version: 2.0.0a11
4
4
  Summary: Shared utilities for Investify services
5
5
  Author-Email: Investify <dev@investify.vn>
6
6
  License: MIT
@@ -25,4 +25,4 @@ Usage:
25
25
  from investify_utils.helpers import convert_to_pd_timestamp, create_sql_in_filter
26
26
  """
27
27
 
28
- __version__ = "2.0.0a8"
28
+ __version__ = "2.0.0a11"
@@ -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
- Sync (for Celery workers, scripts):
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 typing import Callable
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
- "Key": obj["Key"],
108
- "Size": obj["Size"],
109
- "LastModified": obj["LastModified"],
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
-
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "investify-utils"
9
- version = "2.0.0a9"
9
+ version = "2.0.0a11"
10
10
  description = "Shared utilities for Investify services"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.12"
@@ -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