redis-client-kit 0.2.0__tar.gz → 0.4.0__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.
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/CHANGELOG.md +18 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/PKG-INFO +12 -3
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/README.md +10 -2
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/pyproject.toml +1 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/__init__.py +1 -1
- redis_client_kit-0.4.0/redis_client_kit/__version__.py +1 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/config.py +2 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/metrics/__init__.py +2 -2
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/metrics/redis.py +31 -1
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/__init__.py +2 -1
- redis_client_kit-0.4.0/redis_client_kit/providers/metrics.py +44 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/settings/redis.py +1 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/utils.py +1 -0
- redis_client_kit-0.2.0/redis_client_kit/__version__.py +0 -1
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/.gitignore +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/LICENSE +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/__init__.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/factory.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/instrumented.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/lifecycle.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/types.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/metrics/_deps.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/protocols.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/_deps.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/redis.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/utils.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/py.typed +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/settings/__init__.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/settings/_deps.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/__init__.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/factory.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/instrumented.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/lifecycle.py +0 -0
- {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/types.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/bedrock-python/redis-client-kit/compare/redis-client-kit-v0.3.0...redis-client-kit-v0.4.0) (2026-10-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* a hand-written connection object now needs a username attribute (None for the default user); without it the factories raise AttributeError.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* log in as a Redis ACL user with connection.username ([#49](https://github.com/bedrock-python/redis-client-kit/issues/49)) ([53b09e5](https://github.com/bedrock-python/redis-client-kit/commit/53b09e5132fb8bdcfdd1beadfda5a581c22dd02b))
|
|
13
|
+
|
|
14
|
+
## [0.3.0](https://github.com/bedrock-python/redis-client-kit/compare/redis-client-kit-v0.2.0...redis-client-kit-v0.3.0) (2026-09-15)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* ship PrometheusRedisMetricsProvider and a cached get_redis_metrics ([fbe8feb](https://github.com/bedrock-python/redis-client-kit/commit/fbe8feba0b3041d6af4744482419156e7b1ca8e7))
|
|
20
|
+
|
|
3
21
|
## [0.2.0](https://github.com/bedrock-python/redis-client-kit/compare/redis-client-kit-v0.1.5...redis-client-kit-v0.2.0) (2026-09-07)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: redis-client-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Redis client with optional Pydantic, Prometheus, and Dishka support
|
|
5
5
|
Project-URL: Repository, https://github.com/bedrock-python/redis-client-kit
|
|
6
6
|
Project-URL: Documentation, https://bedrock-python.github.io/redis-client-kit/
|
|
@@ -219,6 +219,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
219
219
|
Classifier: Programming Language :: Python :: 3.11
|
|
220
220
|
Classifier: Programming Language :: Python :: 3.12
|
|
221
221
|
Classifier: Programming Language :: Python :: 3.13
|
|
222
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
222
223
|
Classifier: Topic :: Database
|
|
223
224
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
224
225
|
Classifier: Typing :: Typed
|
|
@@ -312,7 +313,10 @@ class MySettings:
|
|
|
312
313
|
class connection:
|
|
313
314
|
host = "localhost"
|
|
314
315
|
port = 6379
|
|
316
|
+
username = None
|
|
315
317
|
db = 0
|
|
318
|
+
client_name = None
|
|
319
|
+
protocol = 2
|
|
316
320
|
|
|
317
321
|
@staticmethod
|
|
318
322
|
def get_password():
|
|
@@ -425,14 +429,19 @@ client = create_async_redis_client(settings, metrics=metrics)
|
|
|
425
429
|
# - myapp_redis_connection_errors_total{error_type}
|
|
426
430
|
```
|
|
427
431
|
|
|
432
|
+
Prometheus registers a metric name once per process, so a second `RedisMetrics(prefix="myapp")`
|
|
433
|
+
raises `ValueError`. `get_redis_metrics(prefix="myapp")` returns the one instance per prefix
|
|
434
|
+
instead, which is what a test suite that rebuilds its container per test wants.
|
|
435
|
+
|
|
428
436
|
### With Dishka DI
|
|
429
437
|
|
|
430
438
|
```python
|
|
431
439
|
from dishka import make_async_container
|
|
432
|
-
from redis_client_kit.providers import AsyncRedisProvider
|
|
440
|
+
from redis_client_kit.providers import AsyncRedisProvider, PrometheusRedisMetricsProvider
|
|
433
441
|
|
|
434
442
|
container = make_async_container(
|
|
435
|
-
AsyncRedisProvider(),
|
|
443
|
+
AsyncRedisProvider(provide_default_metrics=False),
|
|
444
|
+
PrometheusRedisMetricsProvider(), # RedisMetrics when settings.metrics_enabled, else None
|
|
436
445
|
SettingsProvider(), # Your settings provider
|
|
437
446
|
)
|
|
438
447
|
|
|
@@ -72,7 +72,10 @@ class MySettings:
|
|
|
72
72
|
class connection:
|
|
73
73
|
host = "localhost"
|
|
74
74
|
port = 6379
|
|
75
|
+
username = None
|
|
75
76
|
db = 0
|
|
77
|
+
client_name = None
|
|
78
|
+
protocol = 2
|
|
76
79
|
|
|
77
80
|
@staticmethod
|
|
78
81
|
def get_password():
|
|
@@ -185,14 +188,19 @@ client = create_async_redis_client(settings, metrics=metrics)
|
|
|
185
188
|
# - myapp_redis_connection_errors_total{error_type}
|
|
186
189
|
```
|
|
187
190
|
|
|
191
|
+
Prometheus registers a metric name once per process, so a second `RedisMetrics(prefix="myapp")`
|
|
192
|
+
raises `ValueError`. `get_redis_metrics(prefix="myapp")` returns the one instance per prefix
|
|
193
|
+
instead, which is what a test suite that rebuilds its container per test wants.
|
|
194
|
+
|
|
188
195
|
### With Dishka DI
|
|
189
196
|
|
|
190
197
|
```python
|
|
191
198
|
from dishka import make_async_container
|
|
192
|
-
from redis_client_kit.providers import AsyncRedisProvider
|
|
199
|
+
from redis_client_kit.providers import AsyncRedisProvider, PrometheusRedisMetricsProvider
|
|
193
200
|
|
|
194
201
|
container = make_async_container(
|
|
195
|
-
AsyncRedisProvider(),
|
|
202
|
+
AsyncRedisProvider(provide_default_metrics=False),
|
|
203
|
+
PrometheusRedisMetricsProvider(), # RedisMetrics when settings.metrics_enabled, else None
|
|
196
204
|
SettingsProvider(), # Your settings provider
|
|
197
205
|
)
|
|
198
206
|
|
|
@@ -17,6 +17,7 @@ classifiers = [
|
|
|
17
17
|
"Programming Language :: Python :: 3.11",
|
|
18
18
|
"Programming Language :: Python :: 3.12",
|
|
19
19
|
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
20
21
|
"Topic :: Database",
|
|
21
22
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
23
|
"Typing :: Typed",
|
|
@@ -24,7 +24,7 @@ from .utils import build_base_redis_kwargs, build_redis_retry, parse_redis_url_n
|
|
|
24
24
|
try:
|
|
25
25
|
__version__ = version("redis-client-kit")
|
|
26
26
|
except PackageNotFoundError: # pragma: no cover
|
|
27
|
-
__version__ = "0.
|
|
27
|
+
__version__ = "0.4.0"
|
|
28
28
|
|
|
29
29
|
# ruff: noqa: RUF022
|
|
30
30
|
__all__ = [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -8,6 +8,7 @@ class RedisConnectionProtocol(Protocol):
|
|
|
8
8
|
|
|
9
9
|
host: str
|
|
10
10
|
port: int
|
|
11
|
+
username: str | None
|
|
11
12
|
db: int
|
|
12
13
|
client_name: str | None
|
|
13
14
|
protocol: int
|
|
@@ -89,3 +90,4 @@ class RedisSettingsProtocol(Protocol):
|
|
|
89
90
|
ssl: RedisSSLProtocol
|
|
90
91
|
response: RedisResponseProtocol
|
|
91
92
|
health_check_interval: int | None
|
|
93
|
+
metrics_enabled: bool
|
|
@@ -5,6 +5,6 @@ from ._deps import HAS_PROMETHEUS
|
|
|
5
5
|
if not HAS_PROMETHEUS:
|
|
6
6
|
raise ImportError("prometheus-client not installed. Install redis-client-kit[metrics] to use metrics.")
|
|
7
7
|
|
|
8
|
-
from .redis import REDIS_COMMAND_DURATION_BUCKETS, RedisMetrics
|
|
8
|
+
from .redis import REDIS_COMMAND_DURATION_BUCKETS, RedisMetrics, get_redis_metrics
|
|
9
9
|
|
|
10
|
-
__all__ = ["REDIS_COMMAND_DURATION_BUCKETS", "RedisMetrics"]
|
|
10
|
+
__all__ = ["REDIS_COMMAND_DURATION_BUCKETS", "RedisMetrics", "get_redis_metrics"]
|
|
@@ -30,6 +30,9 @@ class RedisMetrics:
|
|
|
30
30
|
>>> metrics = RedisMetrics(prefix="myapp")
|
|
31
31
|
>>> metrics.record_command("GET", "success", 0.001)
|
|
32
32
|
>>> metrics.record_pool_stats(pool_size=10, pool_checked_out=3)
|
|
33
|
+
|
|
34
|
+
Prometheus registers a metric name once per registry, so a second instance with the
|
|
35
|
+
same prefix raises ``ValueError``; ``get_redis_metrics`` hands back the first one instead.
|
|
33
36
|
"""
|
|
34
37
|
|
|
35
38
|
def __init__(self, prefix: str | None = None) -> None:
|
|
@@ -37,6 +40,10 @@ class RedisMetrics:
|
|
|
37
40
|
|
|
38
41
|
Args:
|
|
39
42
|
prefix: Optional metric name prefix (e.g., "myapp" -> "myapp_redis_pool_size")
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If a metric of the same name is already registered on the default
|
|
46
|
+
registry -- see ``get_redis_metrics`` for the second instance.
|
|
40
47
|
"""
|
|
41
48
|
metric_prefix = f"{prefix}_" if prefix else ""
|
|
42
49
|
|
|
@@ -95,4 +102,27 @@ class RedisMetrics:
|
|
|
95
102
|
self.pool_checked_out.set(float(pool_checked_out))
|
|
96
103
|
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
_REDIS_METRICS_CACHE: dict[str | None, RedisMetrics] = {}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_redis_metrics(prefix: str | None = None) -> RedisMetrics:
|
|
109
|
+
"""Get (or lazily create) the cached ``RedisMetrics`` for a prefix, on the default registry.
|
|
110
|
+
|
|
111
|
+
Caching by prefix is what lets a container be rebuilt -- a test suite does it per test --
|
|
112
|
+
without Prometheus refusing the second registration of the same series.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
prefix: The metric name prefix the instance was, or is, created with; ``""`` and
|
|
116
|
+
``None`` are the same unprefixed instance.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The one instance for that prefix.
|
|
120
|
+
"""
|
|
121
|
+
key = prefix or None
|
|
122
|
+
metrics = _REDIS_METRICS_CACHE.get(key)
|
|
123
|
+
if metrics is None:
|
|
124
|
+
metrics = _REDIS_METRICS_CACHE[key] = RedisMetrics(prefix=key)
|
|
125
|
+
return metrics
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
__all__ = ["REDIS_COMMAND_DURATION_BUCKETS", "RedisMetrics", "get_redis_metrics"]
|
|
@@ -5,6 +5,7 @@ from ._deps import HAS_DISHKA
|
|
|
5
5
|
if not HAS_DISHKA:
|
|
6
6
|
raise ImportError("dishka not installed. Install redis-client-kit[providers] to use AsyncRedisProvider.")
|
|
7
7
|
|
|
8
|
+
from .metrics import PrometheusRedisMetricsProvider
|
|
8
9
|
from .redis import AsyncRedisProvider
|
|
9
10
|
|
|
10
|
-
__all__ = ["AsyncRedisProvider"]
|
|
11
|
+
__all__ = ["AsyncRedisProvider", "PrometheusRedisMetricsProvider"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Dishka provider for the Prometheus Redis metrics collector."""
|
|
2
|
+
|
|
3
|
+
from ..config import RedisSettingsProtocol
|
|
4
|
+
from ..protocols import RedisMetricsProtocol
|
|
5
|
+
from ._deps import Provider, Scope, provide
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PrometheusRedisMetricsProvider(Provider): # type: ignore[misc]
|
|
9
|
+
"""Dishka provider for the collector ``AsyncRedisProvider`` records into.
|
|
10
|
+
|
|
11
|
+
Provides ``RedisMetricsProtocol | None`` -- the key ``AsyncRedisProvider`` reads -- as
|
|
12
|
+
``get_redis_metrics(prefix)`` when ``settings.metrics_enabled`` and ``None`` otherwise,
|
|
13
|
+
so the client is instrumented exactly when the settings say so. Register it with
|
|
14
|
+
``AsyncRedisProvider(provide_default_metrics=False)``, or after ``AsyncRedisProvider()``,
|
|
15
|
+
since the last provider of a type wins.
|
|
16
|
+
|
|
17
|
+
The collector comes from ``get_redis_metrics``: one instance per prefix on the default
|
|
18
|
+
registry, so a container rebuilt per test never asks Prometheus to register the same
|
|
19
|
+
series twice. Resolving it with metrics on needs the ``metrics`` extra; without it the
|
|
20
|
+
import raises ``ImportError`` naming the extra.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
scope = Scope.APP # type: ignore[misc]
|
|
24
|
+
|
|
25
|
+
def __init__(self, *, prefix: str | None = None) -> None:
|
|
26
|
+
"""Remember the prefix the collector is created with.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
prefix: Metric name prefix (``"myapp"`` gives ``myapp_redis_pool_size``)
|
|
30
|
+
"""
|
|
31
|
+
super().__init__()
|
|
32
|
+
self._prefix = prefix
|
|
33
|
+
|
|
34
|
+
@provide
|
|
35
|
+
def get_metrics(self, redis_settings: RedisSettingsProtocol) -> RedisMetricsProtocol | None:
|
|
36
|
+
"""Provide the collector when ``metrics_enabled`` is on, ``None`` otherwise."""
|
|
37
|
+
if not redis_settings.metrics_enabled:
|
|
38
|
+
return None
|
|
39
|
+
from ..metrics import get_redis_metrics # noqa: PLC0415 - lazy: needs the [metrics] extra
|
|
40
|
+
|
|
41
|
+
return get_redis_metrics(self._prefix)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["PrometheusRedisMetricsProvider"]
|
|
@@ -10,6 +10,7 @@ class RedisConnectionSettings(BaseModel): # type: ignore[misc]
|
|
|
10
10
|
|
|
11
11
|
host: str = Field(default="localhost", description="Redis host")
|
|
12
12
|
port: int = Field(default=6379, ge=1, le=65535, description="Redis port")
|
|
13
|
+
username: str | None = Field(default=None, description="Redis ACL user name (optional; `default` when unset)")
|
|
13
14
|
password: SecretStr | None = Field(default=None, description="Redis password (optional)")
|
|
14
15
|
db: int = Field(default=0, ge=0, le=15, description="Redis database number (ignored in cluster mode)")
|
|
15
16
|
client_name: str | None = Field(default=None, description="Identifier for this client in Redis")
|
|
@@ -53,6 +53,7 @@ def build_base_redis_kwargs(settings: RedisSettingsProtocol, *, asyncio: bool =
|
|
|
53
53
|
_validate_pem_format(settings.ssl.keyfile, "PRIVATE KEY")
|
|
54
54
|
|
|
55
55
|
kwargs: dict[str, object] = {
|
|
56
|
+
"username": settings.connection.username,
|
|
56
57
|
"password": settings.connection.get_password(),
|
|
57
58
|
"max_connections": settings.pool.max_connections,
|
|
58
59
|
"socket_timeout": settings.pool.socket_timeout,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|