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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cledar-sdk
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Cledar Python SDK
5
5
  Author: Cledar
6
6
  License-File: LICENSE
@@ -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=yHPDvgkwcM4vtJ2nvSsw9Sc0XqEoWUwhewRXH7x1V3A,1012
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=xgeXoS6J67YZ7w3QbhdiGkzD99RNeLteObDeXBGzDTw,3959
11
- kafka_service/clients/producer.py,sha256=1kLaeWoHb9y7NYkdk7cW_2Lz3UY0podj-9mVOIwWIko,2862
12
- kafka_service/config/schemas.py,sha256=tYw1cFBM4JPkHTxKNsTfpkA2OQ0wUkyeBu_Kfgb2cyI,3840
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=H_kjbmeHWbjA8a8vMZm0dZjsDhznYSom5X_lm3bY5zs,16907
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.3.0.dist-info/METADATA,sha256=x9Pa7dwAJdLAW5A1eY8qQD-DTvMkUp5nrkWpCXvD3nA,6752
81
- cledar_sdk-1.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
82
- cledar_sdk-1.3.0.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
83
- cledar_sdk-1.3.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
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 KafkaConsumerConfig, KafkaProducerConfig
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.",
@@ -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 KafkaConsumerConfig, KafkaProducerConfig
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"