glean-klient 0.1.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.
- glean_klient-0.1.0.dist-info/METADATA +955 -0
- glean_klient-0.1.0.dist-info/RECORD +12 -0
- glean_klient-0.1.0.dist-info/WHEEL +5 -0
- glean_klient-0.1.0.dist-info/entry_points.txt +2 -0
- glean_klient-0.1.0.dist-info/top_level.txt +1 -0
- klient/__init__.py +106 -0
- klient/__main__.py +1226 -0
- klient/admin.py +286 -0
- klient/consumer.py +629 -0
- klient/metrics.py +34 -0
- klient/producer.py +820 -0
- klient/relay.py +101 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
klient/__init__.py,sha256=QFQ7_cJpO2VoZO9DM55D9v9cH5VXA6ZxXxsw7-_zERA,5124
|
|
2
|
+
klient/__main__.py,sha256=mWLv3EylzMx6iJ0JnClxHUKnY8q5ZCvy1AUGb3cckLg,52077
|
|
3
|
+
klient/admin.py,sha256=Nmu20etsR-Fo96wi9lPevimP4LS3mHZ__D41y6hTHNo,9772
|
|
4
|
+
klient/consumer.py,sha256=IUUciDdpbMnYfC9IthrUzk4K_hKvjbMPQIiD0fSoIXI,23517
|
|
5
|
+
klient/metrics.py,sha256=ADtgUO6f2lGSIC2zfuLiUY0utUEvYC7fW4pcg0ukfOg,910
|
|
6
|
+
klient/producer.py,sha256=DrQI_FnDWs0sgLQYeDBkMQHT9c31AcyC8YNvyA3_aMw,29837
|
|
7
|
+
klient/relay.py,sha256=tWBRJoyaQizmEdoU4CZ7-Y3sGbhLvAXEHbpVB2BhTc8,3903
|
|
8
|
+
glean_klient-0.1.0.dist-info/METADATA,sha256=k0TRhsecLw0ovaHq7mWJMOVQEEkpH-BJAJdcRtrd614,33086
|
|
9
|
+
glean_klient-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
glean_klient-0.1.0.dist-info/entry_points.txt,sha256=jqD3TY-cMjnVswUCXAw6hXIVUqP9bN2I3Y-EYHcOQbM,52
|
|
11
|
+
glean_klient-0.1.0.dist-info/top_level.txt,sha256=oZsSRRLlGYX2kCEHIrFnXwU-aoNy7pEPfmDaDtMH0Oc,7
|
|
12
|
+
glean_klient-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
klient
|
klient/__init__.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Kafka client wrapper library supporting both synchronous and asynchronous operations.
|
|
2
|
+
|
|
3
|
+
Provides high-level interfaces for:
|
|
4
|
+
- Synchronous and asynchronous message consumption
|
|
5
|
+
- Synchronous and asynchronous message production
|
|
6
|
+
- Transaction support (begin/commit/abort + context managers)
|
|
7
|
+
- Topic and cluster administration
|
|
8
|
+
- Unified environment-based configuration loading utilities
|
|
9
|
+
|
|
10
|
+
Environment configuration loader utilities are defined first to avoid circular
|
|
11
|
+
import issues when producer/consumer/admin modules import them.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import typing as t
|
|
18
|
+
|
|
19
|
+
def _load_raw_config_file(path: Path) -> t.Dict[str, t.Any]:
|
|
20
|
+
if not path.exists():
|
|
21
|
+
return {}
|
|
22
|
+
try:
|
|
23
|
+
with path.open('r', encoding='utf-8') as f:
|
|
24
|
+
return json.load(f)
|
|
25
|
+
except json.JSONDecodeError as e:
|
|
26
|
+
raise ValueError(f"Invalid JSON in config file {path}: {e}")
|
|
27
|
+
except Exception as e:
|
|
28
|
+
raise ValueError(f"Failed to read config file {path}: {e}")
|
|
29
|
+
|
|
30
|
+
def resolve_env_config(env: t.Optional[str], explicit_file: t.Optional[str]) -> t.Dict[str, t.Any]:
|
|
31
|
+
"""Resolve configuration from a SINGLE JSON file with default + environment sections.
|
|
32
|
+
|
|
33
|
+
Model:
|
|
34
|
+
- Exactly one file supplies configuration: either `explicit_file` if provided,
|
|
35
|
+
else `~/.kafka/config.json`.
|
|
36
|
+
- File may contain a `default` object and any number of named environment objects.
|
|
37
|
+
- When `env` is specified, effective config = merge of `default` (if present) overlaid
|
|
38
|
+
by the named environment object. Key collisions favor the environment.
|
|
39
|
+
- When `env` is not specified, returns the `default` object if present else the raw top-level mapping.
|
|
40
|
+
|
|
41
|
+
Example structure:
|
|
42
|
+
{
|
|
43
|
+
"default": {"bootstrap.servers": "localhost:9092", "acks": "all"},
|
|
44
|
+
"prod": {"bootstrap.servers": "prod-broker:9092", "compression.type": "lz4"},
|
|
45
|
+
"dev": {"bootstrap.servers": "dev-broker:9092"}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Topic names MUST NOT appear in this file. Returns empty dict if file missing or invalid.
|
|
49
|
+
"""
|
|
50
|
+
home_dir = Path(os.path.expanduser('~')) / '.kafka'
|
|
51
|
+
path = Path(explicit_file) if explicit_file else home_dir / 'config.json'
|
|
52
|
+
raw = _load_raw_config_file(path)
|
|
53
|
+
if not isinstance(raw, dict):
|
|
54
|
+
return {}
|
|
55
|
+
# Legacy compatibility: allow per-env standalone file ~/.kafka/{env}.json if config.json missing
|
|
56
|
+
if not explicit_file and env and not raw and (home_dir / f"{env}.json").exists():
|
|
57
|
+
env_raw = _load_raw_config_file(home_dir / f"{env}.json")
|
|
58
|
+
return env_raw if isinstance(env_raw, dict) else {}
|
|
59
|
+
default_section = raw.get('default') if isinstance(raw.get('default'), dict) else {}
|
|
60
|
+
if env:
|
|
61
|
+
from typing import cast, Dict, Any
|
|
62
|
+
env_section_raw = raw.get(env) if isinstance(raw.get(env), dict) else {}
|
|
63
|
+
# If no default/env structured sections present, and raw looks like direct config, return raw mapping unchanged
|
|
64
|
+
if not default_section and not env_section_raw and 'bootstrap.servers' in raw:
|
|
65
|
+
return raw
|
|
66
|
+
default_cast: Dict[str, Any] = cast(Dict[str, Any], default_section if isinstance(default_section, dict) else {})
|
|
67
|
+
env_cast: Dict[str, Any] = cast(Dict[str, Any], env_section_raw if isinstance(env_section_raw, dict) else {})
|
|
68
|
+
merged: Dict[str, Any] = dict(default_cast)
|
|
69
|
+
merged.update(env_cast)
|
|
70
|
+
# If env not found, fall back to default_cast
|
|
71
|
+
return merged if env_cast else default_cast
|
|
72
|
+
# No env requested: return default section if present else raw mapping
|
|
73
|
+
return default_section or raw
|
|
74
|
+
|
|
75
|
+
def split_env_config(raw: t.Dict[str, t.Any]) -> t.Tuple[t.Dict[str, t.Any], t.Dict[str, t.Any], t.Dict[str, t.Any]]:
|
|
76
|
+
if not raw:
|
|
77
|
+
return {}, {}, {}
|
|
78
|
+
common = raw.get('common', {}) if isinstance(raw.get('common'), dict) else {}
|
|
79
|
+
def _section(name: str) -> t.Dict[str, t.Any]:
|
|
80
|
+
sec = raw.get(name, {})
|
|
81
|
+
return sec if isinstance(sec, dict) else {}
|
|
82
|
+
prod = {**common, **_section('producer')}
|
|
83
|
+
cons = {**common, **_section('consumer')}
|
|
84
|
+
adm = {**common, **_section('admin')}
|
|
85
|
+
if not prod and not cons and not adm and not common:
|
|
86
|
+
return raw, raw, raw
|
|
87
|
+
return prod, cons, adm
|
|
88
|
+
|
|
89
|
+
def extract_bootstrap(raw_sections: t.Sequence[t.Dict[str, t.Any]]) -> t.Optional[str]:
|
|
90
|
+
for section in raw_sections:
|
|
91
|
+
if 'bootstrap.servers' in section:
|
|
92
|
+
return section['bootstrap.servers']
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
from .producer import KafkaProducer, ProducerConfig, ProduceResult, KafkaProducerError, TransactionResult, KafkaTransactionError # noqa: E402
|
|
96
|
+
from .consumer import KafkaConsumer, ConsumerConfig, MessageResult, KafkaConsumerError # noqa: E402
|
|
97
|
+
from .admin import KafkaAdmin, AdminConfig, TopicMetadata, KafkaAdminError # noqa: E402
|
|
98
|
+
|
|
99
|
+
__version__ = "0.1.0"
|
|
100
|
+
|
|
101
|
+
__all__ = [
|
|
102
|
+
'KafkaProducer','ProducerConfig','ProduceResult','KafkaProducerError','KafkaTransactionError','TransactionResult',
|
|
103
|
+
'KafkaConsumer','ConsumerConfig','MessageResult','KafkaConsumerError',
|
|
104
|
+
'KafkaAdmin','AdminConfig','KafkaAdminError','TopicMetadata',
|
|
105
|
+
'resolve_env_config','split_env_config','extract_bootstrap'
|
|
106
|
+
]
|