cledar-sdk 1.3.0__py3-none-any.whl → 1.4.0__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.
- {cledar_sdk-1.3.0.dist-info → cledar_sdk-1.4.0.dist-info}/METADATA +1 -1
- {cledar_sdk-1.3.0.dist-info → cledar_sdk-1.4.0.dist-info}/RECORD +9 -9
- {cledar_sdk-1.3.0.dist-info → cledar_sdk-1.4.0.dist-info}/WHEEL +1 -1
- kafka_service/__init__.py +8 -1
- kafka_service/clients/consumer.py +1 -10
- kafka_service/clients/producer.py +1 -8
- kafka_service/config/schemas.py +72 -0
- kafka_service/tests/unit/test_config_validation.py +85 -1
- {cledar_sdk-1.3.0.dist-info → cledar_sdk-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,13 +3,13 @@ common_logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
3
3
|
common_logging/universal_plaintext_formatter.py,sha256=ExuXwfZmc_4tp3RIsoe_ByYpnTN0iQBOi101FB8hksc,3309
|
|
4
4
|
common_logging/tests/test_universal_plaintext_formatter.py,sha256=bY6vvpra81_YXN2MLVjNuA8Iu7kOaZH0lmQMsohjRPI,8560
|
|
5
5
|
kafka_service/README.md,sha256=3lKulsfEUs6qaKExlpPkxT_X1NYBAHraiqQRdvkQ48I,7021
|
|
6
|
-
kafka_service/__init__.py,sha256=
|
|
6
|
+
kafka_service/__init__.py,sha256=YejBYb9sS5d0VLuYg48vqKWTWSTwlEnsGF_2Z3guXAQ,1131
|
|
7
7
|
kafka_service/exceptions.py,sha256=LJ-mUYCwoQsDM-bL4ms9ZCiWIOw5kt1KStbN04Mavl0,501
|
|
8
8
|
kafka_service/logger.py,sha256=jdZSJnuPpUXEwG5eLKlQMLkVxaKwuma_o9udbOAjXo0,60
|
|
9
9
|
kafka_service/clients/base.py,sha256=LhiCF9vNxn-kCWld4Xfzy1lYCUj7PbhJhNURE7wIx70,3692
|
|
10
|
-
kafka_service/clients/consumer.py,sha256=
|
|
11
|
-
kafka_service/clients/producer.py,sha256=
|
|
12
|
-
kafka_service/config/schemas.py,sha256=
|
|
10
|
+
kafka_service/clients/consumer.py,sha256=m0u-Q2HXLk7pD4z2ucwaQxavW3VATbvVD1fDTSAi0tA,3590
|
|
11
|
+
kafka_service/clients/producer.py,sha256=oJMZ9zg7n-DFWrySu-xveQ70ZXZEQkuXE0ewMeLLKoo,2605
|
|
12
|
+
kafka_service/config/schemas.py,sha256=ToAv2d3F7mMpuePBWsC12jugdMWLPQMoNCXub0VIbgE,6550
|
|
13
13
|
kafka_service/handlers/dead_letter.py,sha256=Y4krNONrMayaYsvFZrXh-pRK8kfyWibEYrUyDLy-XH8,2552
|
|
14
14
|
kafka_service/handlers/parser.py,sha256=BsEUFI5bDBQzf9f9s7XClk_TfEXveOG5yCTT4sOibPI,1475
|
|
15
15
|
kafka_service/models/input.py,sha256=VEf9DDbXew4tJSHP7cEbXWW3k6s_1gN8QGcqs5LmxuU,254
|
|
@@ -27,7 +27,7 @@ kafka_service/tests/integration/test_producer_consumer_interaction.py,sha256=7Z6
|
|
|
27
27
|
kafka_service/tests/integration/test_producer_integration.py,sha256=0NXNB5gN0hPSXj5j2oP9FlhAXrNVpNg5N_1DkGOHUy0,6438
|
|
28
28
|
kafka_service/tests/unit/__init__.py,sha256=uiWL_3JORErRzHi12hW0-kFU36zdkl3MBg4_qtH6TM0,36
|
|
29
29
|
kafka_service/tests/unit/test_base_kafka_client.py,sha256=6O31wIuoKkb-EEqD-a5PjB-zbM0h5vTFz1VGr9V3KVQ,13321
|
|
30
|
-
kafka_service/tests/unit/test_config_validation.py,sha256=
|
|
30
|
+
kafka_service/tests/unit/test_config_validation.py,sha256=KpMAI_5s-XWOW_bNT9rTie61eoet4kkB7YTFd9W2hOE,19971
|
|
31
31
|
kafka_service/tests/unit/test_dead_letter_handler.py,sha256=qsJZKXm_fBZUnaDwBAF7Bj9aS-y7Uxc-e7u6pPQLTgc,13905
|
|
32
32
|
kafka_service/tests/unit/test_error_handling.py,sha256=h91hwG02DwGEZrziSv0mLshQFK98p-P38g4gWPHuyyY,22828
|
|
33
33
|
kafka_service/tests/unit/test_input_parser.py,sha256=_MIWkvhYRZR_7lg9Jo_MD3ckCTGrMrSyeR_7F5wrHSw,11556
|
|
@@ -77,7 +77,7 @@ storage_service/tests/test_integration_filesystem.py,sha256=-H3Skc_geYIjXW1si-8u
|
|
|
77
77
|
storage_service/tests/test_integration_s3.py,sha256=Ivg_52LXibqVGMS-53z4zda_Yh4u6FO8WplUUu5WWBc,13614
|
|
78
78
|
storage_service/tests/test_local.py,sha256=3CgtxQ_lBBaPR4t9Ip0i7T98scrTOCdkmHYMVansNCc,11256
|
|
79
79
|
storage_service/tests/test_s3.py,sha256=zAppsvVCeLx_NN1tQfqHo57mONEjeFDmiwyecrS3ZgQ,16355
|
|
80
|
-
cledar_sdk-1.
|
|
81
|
-
cledar_sdk-1.
|
|
82
|
-
cledar_sdk-1.
|
|
83
|
-
cledar_sdk-1.
|
|
80
|
+
cledar_sdk-1.4.0.dist-info/METADATA,sha256=KY0mpTdaySyoD8xe8RDSEM0IZRFrF2rLCwNAy1SBcjg,6752
|
|
81
|
+
cledar_sdk-1.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
82
|
+
cledar_sdk-1.4.0.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
|
|
83
|
+
cledar_sdk-1.4.0.dist-info/RECORD,,
|
kafka_service/__init__.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
from .clients.base import BaseKafkaClient
|
|
2
2
|
from .clients.consumer import KafkaConsumer
|
|
3
3
|
from .clients.producer import KafkaProducer
|
|
4
|
-
from .config.schemas import
|
|
4
|
+
from .config.schemas import (
|
|
5
|
+
KafkaConsumerConfig,
|
|
6
|
+
KafkaProducerConfig,
|
|
7
|
+
KafkaSaslMechanism,
|
|
8
|
+
KafkaSecurityProtocol,
|
|
9
|
+
)
|
|
5
10
|
from .exceptions import (
|
|
6
11
|
KafkaConnectionError,
|
|
7
12
|
KafkaConsumerError,
|
|
@@ -26,6 +31,8 @@ __all__ = [
|
|
|
26
31
|
"KafkaMessage",
|
|
27
32
|
"KafkaProducerConfig",
|
|
28
33
|
"KafkaConsumerConfig",
|
|
34
|
+
"KafkaSecurityProtocol",
|
|
35
|
+
"KafkaSaslMechanism",
|
|
29
36
|
"KafkaConnectionError",
|
|
30
37
|
"KafkaConsumerNotConnectedError",
|
|
31
38
|
"KafkaProducerNotConnectedError",
|
|
@@ -20,16 +20,7 @@ class KafkaConsumer(BaseKafkaClient):
|
|
|
20
20
|
client: Consumer | None = None
|
|
21
21
|
|
|
22
22
|
def connect(self) -> None:
|
|
23
|
-
self.client = Consumer(
|
|
24
|
-
{
|
|
25
|
-
"bootstrap.servers": self.config.kafka_servers,
|
|
26
|
-
"enable.auto.commit": False,
|
|
27
|
-
"enable.partition.eof": False,
|
|
28
|
-
"auto.commit.interval.ms": self.config.kafka_auto_commit_interval_ms,
|
|
29
|
-
"auto.offset.reset": self.config.kafka_offset,
|
|
30
|
-
"group.id": self.config.kafka_group_id,
|
|
31
|
-
}
|
|
32
|
-
)
|
|
23
|
+
self.client = Consumer(self.config.to_kafka_config())
|
|
33
24
|
self.check_connection()
|
|
34
25
|
logger.info(
|
|
35
26
|
"Connected KafkaConsumer to Kafka servers.",
|
|
@@ -17,14 +17,7 @@ class KafkaProducer(BaseKafkaClient):
|
|
|
17
17
|
client: Producer | None = None
|
|
18
18
|
|
|
19
19
|
def connect(self) -> None:
|
|
20
|
-
self.client = Producer(
|
|
21
|
-
{
|
|
22
|
-
"bootstrap.servers": self.config.kafka_servers,
|
|
23
|
-
"client.id": self.config.kafka_group_id,
|
|
24
|
-
"compression.type": self.config.compression_type,
|
|
25
|
-
"partitioner": self.config.kafka_partitioner,
|
|
26
|
-
}
|
|
27
|
-
)
|
|
20
|
+
self.client = Producer(self.config.to_kafka_config())
|
|
28
21
|
self.check_connection()
|
|
29
22
|
logger.info(
|
|
30
23
|
"Connected Producer to Kafka servers.",
|
kafka_service/config/schemas.py
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
1
3
|
from pydantic import field_validator
|
|
2
4
|
from pydantic.dataclasses import dataclass
|
|
3
5
|
|
|
4
6
|
|
|
7
|
+
class KafkaSecurityProtocol(str, Enum):
|
|
8
|
+
"""Supported Kafka security protocols."""
|
|
9
|
+
|
|
10
|
+
PLAINTEXT = "PLAINTEXT"
|
|
11
|
+
SSL = "SSL"
|
|
12
|
+
SASL_PLAINTEXT = "SASL_PLAINTEXT"
|
|
13
|
+
SASL_SSL = "SASL_SSL"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class KafkaSaslMechanism(str, Enum):
|
|
17
|
+
"""Supported Kafka SASL mechanisms."""
|
|
18
|
+
|
|
19
|
+
PLAIN = "PLAIN"
|
|
20
|
+
SCRAM_SHA_256 = "SCRAM-SHA-256"
|
|
21
|
+
SCRAM_SHA_512 = "SCRAM-SHA-512"
|
|
22
|
+
GSSAPI = "GSSAPI"
|
|
23
|
+
|
|
24
|
+
|
|
5
25
|
def _validate_kafka_servers(v: list[str] | str) -> list[str] | str:
|
|
6
26
|
"""Validate kafka_servers is not empty."""
|
|
7
27
|
if isinstance(v, str) and v.strip() == "":
|
|
@@ -36,6 +56,10 @@ class KafkaProducerConfig:
|
|
|
36
56
|
|
|
37
57
|
kafka_servers: list[str] | str
|
|
38
58
|
kafka_group_id: str
|
|
59
|
+
kafka_security_protocol: KafkaSecurityProtocol | None = None
|
|
60
|
+
kafka_sasl_mechanism: KafkaSaslMechanism | None = None
|
|
61
|
+
kafka_sasl_username: str | None = None
|
|
62
|
+
kafka_sasl_password: str | None = None
|
|
39
63
|
kafka_topic_prefix: str | None = None
|
|
40
64
|
kafka_block_buffer_time_sec: int = 10
|
|
41
65
|
kafka_connection_check_timeout_sec: int = 5
|
|
@@ -57,6 +81,27 @@ class KafkaProducerConfig:
|
|
|
57
81
|
def validate_positive_timeouts(cls, v: int) -> int:
|
|
58
82
|
return _validate_non_negative(v)
|
|
59
83
|
|
|
84
|
+
def to_kafka_config(self) -> dict[str, list[str] | str | None]:
|
|
85
|
+
"""Build Kafka producer configuration dictionary."""
|
|
86
|
+
config = {
|
|
87
|
+
"bootstrap.servers": self.kafka_servers,
|
|
88
|
+
"client.id": self.kafka_group_id,
|
|
89
|
+
"compression.type": self.compression_type,
|
|
90
|
+
"partitioner": self.kafka_partitioner,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Add SASL configuration if specified
|
|
94
|
+
if self.kafka_security_protocol:
|
|
95
|
+
config["security.protocol"] = self.kafka_security_protocol.value
|
|
96
|
+
if self.kafka_sasl_mechanism:
|
|
97
|
+
config["sasl.mechanism"] = self.kafka_sasl_mechanism.value
|
|
98
|
+
if self.kafka_sasl_username:
|
|
99
|
+
config["sasl.username"] = self.kafka_sasl_username
|
|
100
|
+
if self.kafka_sasl_password:
|
|
101
|
+
config["sasl.password"] = self.kafka_sasl_password
|
|
102
|
+
|
|
103
|
+
return config
|
|
104
|
+
|
|
60
105
|
|
|
61
106
|
@dataclass(frozen=True)
|
|
62
107
|
class KafkaConsumerConfig:
|
|
@@ -76,6 +121,10 @@ class KafkaConsumerConfig:
|
|
|
76
121
|
|
|
77
122
|
kafka_servers: list[str] | str
|
|
78
123
|
kafka_group_id: str
|
|
124
|
+
kafka_security_protocol: KafkaSecurityProtocol | None = None
|
|
125
|
+
kafka_sasl_mechanism: KafkaSaslMechanism | None = None
|
|
126
|
+
kafka_sasl_username: str | None = None
|
|
127
|
+
kafka_sasl_password: str | None = None
|
|
79
128
|
kafka_offset: str = "latest"
|
|
80
129
|
kafka_topic_prefix: str | None = None
|
|
81
130
|
kafka_block_consumer_time_sec: int = 2
|
|
@@ -104,3 +153,26 @@ class KafkaConsumerConfig:
|
|
|
104
153
|
@classmethod
|
|
105
154
|
def validate_positive_timeouts(cls, v: int) -> int:
|
|
106
155
|
return _validate_non_negative(v)
|
|
156
|
+
|
|
157
|
+
def to_kafka_config(self) -> dict[str, int | list[str] | str]:
|
|
158
|
+
"""Build Kafka consumer configuration dictionary."""
|
|
159
|
+
config = {
|
|
160
|
+
"bootstrap.servers": self.kafka_servers,
|
|
161
|
+
"enable.auto.commit": False,
|
|
162
|
+
"enable.partition.eof": False,
|
|
163
|
+
"auto.commit.interval.ms": self.kafka_auto_commit_interval_ms,
|
|
164
|
+
"auto.offset.reset": self.kafka_offset,
|
|
165
|
+
"group.id": self.kafka_group_id,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Add SASL configuration if specified
|
|
169
|
+
if self.kafka_security_protocol:
|
|
170
|
+
config["security.protocol"] = self.kafka_security_protocol.value
|
|
171
|
+
if self.kafka_sasl_mechanism:
|
|
172
|
+
config["sasl.mechanism"] = self.kafka_sasl_mechanism.value
|
|
173
|
+
if self.kafka_sasl_username:
|
|
174
|
+
config["sasl.username"] = self.kafka_sasl_username
|
|
175
|
+
if self.kafka_sasl_password:
|
|
176
|
+
config["sasl.password"] = self.kafka_sasl_password
|
|
177
|
+
|
|
178
|
+
return config
|
|
@@ -8,7 +8,12 @@ from dataclasses import FrozenInstanceError
|
|
|
8
8
|
import pytest
|
|
9
9
|
from pydantic import ValidationError
|
|
10
10
|
|
|
11
|
-
from kafka_service.config.schemas import
|
|
11
|
+
from kafka_service.config.schemas import (
|
|
12
|
+
KafkaConsumerConfig,
|
|
13
|
+
KafkaProducerConfig,
|
|
14
|
+
KafkaSaslMechanism,
|
|
15
|
+
KafkaSecurityProtocol,
|
|
16
|
+
)
|
|
12
17
|
from kafka_service.models.message import KafkaMessage
|
|
13
18
|
|
|
14
19
|
|
|
@@ -523,3 +528,82 @@ def test_config_inequality() -> None:
|
|
|
523
528
|
)
|
|
524
529
|
|
|
525
530
|
assert config1 != config2
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# SASL Configuration Tests
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_config_with_sasl_authentication() -> None:
|
|
537
|
+
"""Test creating configs with SASL authentication."""
|
|
538
|
+
producer_config = KafkaProducerConfig(
|
|
539
|
+
kafka_servers="localhost:9092",
|
|
540
|
+
kafka_group_id="test-group",
|
|
541
|
+
kafka_security_protocol=KafkaSecurityProtocol.SASL_PLAINTEXT,
|
|
542
|
+
kafka_sasl_mechanism=KafkaSaslMechanism.PLAIN,
|
|
543
|
+
kafka_sasl_username="test-user",
|
|
544
|
+
kafka_sasl_password="test-password",
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
consumer_config = KafkaConsumerConfig(
|
|
548
|
+
kafka_servers="localhost:9092",
|
|
549
|
+
kafka_group_id="test-group",
|
|
550
|
+
kafka_security_protocol=KafkaSecurityProtocol.SASL_SSL,
|
|
551
|
+
kafka_sasl_mechanism=KafkaSaslMechanism.SCRAM_SHA_256,
|
|
552
|
+
kafka_sasl_username="test-user",
|
|
553
|
+
kafka_sasl_password="test-password",
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
assert (
|
|
557
|
+
producer_config.kafka_security_protocol == KafkaSecurityProtocol.SASL_PLAINTEXT
|
|
558
|
+
)
|
|
559
|
+
assert producer_config.kafka_sasl_mechanism == KafkaSaslMechanism.PLAIN
|
|
560
|
+
assert consumer_config.kafka_security_protocol == KafkaSecurityProtocol.SASL_SSL
|
|
561
|
+
assert consumer_config.kafka_sasl_mechanism == KafkaSaslMechanism.SCRAM_SHA_256
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def test_config_invalid_sasl_values() -> None:
|
|
565
|
+
"""Test that invalid SASL values raise ValidationError."""
|
|
566
|
+
with pytest.raises(ValidationError):
|
|
567
|
+
KafkaProducerConfig(
|
|
568
|
+
kafka_servers="localhost:9092",
|
|
569
|
+
kafka_group_id="test-group",
|
|
570
|
+
kafka_security_protocol="INVALID", # type: ignore[arg-type]
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
with pytest.raises(ValidationError):
|
|
574
|
+
KafkaConsumerConfig(
|
|
575
|
+
kafka_servers="localhost:9092",
|
|
576
|
+
kafka_group_id="test-group",
|
|
577
|
+
kafka_sasl_mechanism="INVALID", # type: ignore[arg-type]
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def test_to_kafka_config_with_and_without_sasl() -> None:
|
|
582
|
+
"""Test that to_kafka_config() correctly includes/excludes SASL parameters."""
|
|
583
|
+
# Without SASL
|
|
584
|
+
config_no_sasl = KafkaProducerConfig(
|
|
585
|
+
kafka_servers="localhost:9092",
|
|
586
|
+
kafka_group_id="test-group",
|
|
587
|
+
)
|
|
588
|
+
kafka_config_no_sasl = config_no_sasl.to_kafka_config()
|
|
589
|
+
|
|
590
|
+
assert "security.protocol" not in kafka_config_no_sasl
|
|
591
|
+
assert "sasl.mechanism" not in kafka_config_no_sasl
|
|
592
|
+
assert "sasl.username" not in kafka_config_no_sasl
|
|
593
|
+
assert "sasl.password" not in kafka_config_no_sasl
|
|
594
|
+
|
|
595
|
+
# With SASL
|
|
596
|
+
config_with_sasl = KafkaConsumerConfig(
|
|
597
|
+
kafka_servers="localhost:9092",
|
|
598
|
+
kafka_group_id="test-group",
|
|
599
|
+
kafka_security_protocol=KafkaSecurityProtocol.SASL_SSL,
|
|
600
|
+
kafka_sasl_mechanism=KafkaSaslMechanism.SCRAM_SHA_256,
|
|
601
|
+
kafka_sasl_username="user",
|
|
602
|
+
kafka_sasl_password="pass",
|
|
603
|
+
)
|
|
604
|
+
kafka_config_with_sasl = config_with_sasl.to_kafka_config()
|
|
605
|
+
|
|
606
|
+
assert kafka_config_with_sasl["security.protocol"] == "SASL_SSL"
|
|
607
|
+
assert kafka_config_with_sasl["sasl.mechanism"] == "SCRAM-SHA-256"
|
|
608
|
+
assert kafka_config_with_sasl["sasl.username"] == "user"
|
|
609
|
+
assert kafka_config_with_sasl["sasl.password"] == "pass"
|
|
File without changes
|