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.
Files changed (34) hide show
  1. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/CHANGELOG.md +18 -0
  2. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/PKG-INFO +12 -3
  3. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/README.md +10 -2
  4. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/pyproject.toml +1 -0
  5. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/__init__.py +1 -1
  6. redis_client_kit-0.4.0/redis_client_kit/__version__.py +1 -0
  7. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/config.py +2 -0
  8. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/metrics/__init__.py +2 -2
  9. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/metrics/redis.py +31 -1
  10. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/__init__.py +2 -1
  11. redis_client_kit-0.4.0/redis_client_kit/providers/metrics.py +44 -0
  12. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/settings/redis.py +1 -0
  13. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/utils.py +1 -0
  14. redis_client_kit-0.2.0/redis_client_kit/__version__.py +0 -1
  15. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/.gitignore +0 -0
  16. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/LICENSE +0 -0
  17. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/__init__.py +0 -0
  18. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/factory.py +0 -0
  19. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/instrumented.py +0 -0
  20. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/lifecycle.py +0 -0
  21. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/aio/types.py +0 -0
  22. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/metrics/_deps.py +0 -0
  23. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/protocols.py +0 -0
  24. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/_deps.py +0 -0
  25. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/redis.py +0 -0
  26. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/providers/utils.py +0 -0
  27. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/py.typed +0 -0
  28. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/settings/__init__.py +0 -0
  29. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/settings/_deps.py +0 -0
  30. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/__init__.py +0 -0
  31. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/factory.py +0 -0
  32. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/instrumented.py +0 -0
  33. {redis_client_kit-0.2.0 → redis_client_kit-0.4.0}/redis_client_kit/sync/lifecycle.py +0 -0
  34. {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.2.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.2.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
- __all__ = ["REDIS_COMMAND_DURATION_BUCKETS", "RedisMetrics"]
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"