Skip to content

Show host and db in Redis check __repr__ (with cluster support)#663

Merged
codingjoe merged 6 commits intoissues/659/aliasfrom
copilot/sub-pr-662
Feb 23, 2026
Merged

Show host and db in Redis check __repr__ (with cluster support)#663
codingjoe merged 6 commits intoissues/659/aliasfrom
copilot/sub-pr-662

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 23, 2026

Multiple Redis health check instances are indistinguishable in health check output. This adds host, db, and cluster node information to Redis.__repr__ so each instance is identifiable in JSON, text, and feed outputs.

Changes

  • Redis.__repr__: Extracts host and db from connection_pool.connection_kwargs for standard Redis clients using EAFP; for RedisCluster clients, extracts startup node names (host:port) from client.startup_nodes; falls back to default dataclass repr for clients that don't expose these (e.g. Sentinel)
  • Tests: Eight new non-mocked tests covering standard client, from_url, deprecated client param, Sentinel fallback, RedisCluster with startup node hosts, and explicit security tests ensuring passwords and usernames are never exposed in the repr output

Security

Credentials are never included in the repr output. The implementation only extracts host and db keys explicitly from connection_kwargs, and uses node.name (host:port) for cluster nodes. Security regression tests enforce this for all client types:

  • Standard client with password= kwarg
  • Credentials embedded in a Redis URL (redis://user:pass@host/db)
  • RedisCluster with password= and username= kwargs

Example

from redis.asyncio import Redis as RedisClient, RedisCluster
from redis.asyncio.cluster import ClusterNode
from health_check.contrib.redis import Redis

check = Redis(client_factory=lambda: RedisClient.from_url("redis://localhost:6379/1"))
repr(check)  # "Redis(client=RedisClient(host=localhost, db=1))"

# RedisCluster shows startup node host:port names
cluster_check = Redis(client_factory=lambda: RedisCluster(
    startup_nodes=[ClusterNode("node1", 7000), ClusterNode("node2", 7001)]
))
repr(cluster_check)  # "Redis(client=RedisCluster(hosts=['node1:7000', 'node2:7001']))"

# Sentinel falls back gracefully — host/db not accessible via connection_pool
sentinel_check = Redis(client_factory=lambda: Sentinel([("localhost", 26379)]).master_for("mymaster"))
repr(sentinel_check)  # "Redis()"

# Credentials are never included
secret_check = Redis(client_factory=lambda: RedisClient.from_url("redis://admin:secret@host/0"))
repr(secret_check)  # "Redis(client=RedisClient(host=host, db=0))"  — no credentials

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: codingjoe <1772890+codingjoe@users.noreply.github.com>
Copilot AI changed the title [WIP] Show host and db in Redis check __repr__ Show host and db in Redis check __repr__ Feb 23, 2026
Copilot AI requested a review from codingjoe February 23, 2026 09:22
@codingjoe codingjoe marked this pull request as ready for review February 23, 2026 09:28
Copilot AI review requested due to automatic review settings February 23, 2026 09:28
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (5fe1264) to head (8223212).
⚠️ Report is 4 commits behind head on issues/659/alias.

Additional details and impacted files
@@                  Coverage Diff                  @@
##           issues/659/alias      #663      +/-   ##
=====================================================
+ Coverage             98.66%   100.00%   +1.33%     
=====================================================
  Files                    13        13              
  Lines                   749       754       +5     
=====================================================
+ Hits                    739       754      +15     
+ Misses                   10         0      -10     
Flag Coverage Δ
python-3.10-django-5.2 49.86% <0.00%> (-0.34%) ⬇️
python-3.10-django-5.2-celery 54.77% <0.00%> (-0.37%) ⬇️
python-3.10-django-5.2-kafka 52.91% <0.00%> (-0.36%) ⬇️
python-3.10-django-5.2-psutil 61.53% <0.00%> (-0.42%) ⬇️
python-3.10-django-5.2-rabbitmq 52.65% <0.00%> (-0.36%) ⬇️
python-3.10-django-5.2-redis 56.76% <100.00%> (+1.62%) ⬆️
python-3.10-django-5.2-rss 69.09% <0.00%> (-0.47%) ⬇️
python-3.11-django-5.2 49.86% <0.00%> (-0.34%) ⬇️
python-3.12-django-5.2 49.86% <0.00%> (-0.34%) ⬇️
python-3.12-django-6.0 49.86% <0.00%> (-0.34%) ⬇️
python-3.13-django-5.2 49.86% <0.00%> (-0.34%) ⬇️
python-3.13-django-6.0 49.86% <0.00%> (-0.34%) ⬇️
python-3.14-django-5.2 50.13% <0.00%> (-0.34%) ⬇️
python-3.14-django-5.2-celery 55.06% <0.00%> (-0.37%) ⬇️
python-3.14-django-5.2-kafka 53.06% <0.00%> (-0.36%) ⬇️
python-3.14-django-5.2-psutil 61.86% <0.00%> (-0.42%) ⬇️
python-3.14-django-5.2-rabbitmq 52.80% <0.00%> (-0.36%) ⬇️
python-3.14-django-5.2-redis 57.06% <100.00%> (+1.63%) ⬆️
python-3.14-django-5.2-rss 70.13% <0.00%> (-0.48%) ⬇️
python-3.14-django-6.0 50.13% <0.00%> (-0.34%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codingjoe
Copy link
Copy Markdown
Owner

@copilot there is a test for a cluster client missing.

Co-authored-by: codingjoe <1772890+codingjoe@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 23, 2026

@copilot there is a test for a cluster client missing.

Added in ec4b6ce. RedisCluster has no connection_pool attribute, so __repr__ falls back to "Redis()" via the AttributeError path — verified by the new test_redis__repr_cluster_client test.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make multiple Redis health check instances distinguishable in health check output by adding host and database information to the Redis.__repr__ method. The implementation extracts connection details from the Redis client's connection pool and handles edge cases like Sentinel clients that don't expose these details.

Changes:

  • Removed a misplaced comment from the Redis.__repr__ method
  • Added four new test cases covering standard clients, URL-based clients, deprecated client parameter, and Sentinel fallback behavior

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
health_check/contrib/redis.py Removed a misplaced comment that incorrectly described repr as creating clients for health check requests
tests/contrib/test_redis.py Added comprehensive test coverage for repr with different Redis client types and configurations

Comment on lines 58 to 60
if self.client_factory is not None:
client = self.client_factory()
else:
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repr method creates Redis client instances via client_factory() but never closes them, causing a resource leak. This is particularly problematic because repr can be called multiple times (for JSON output, text output, feed output, logging, debugging, etc.).

Consider one of these alternatives:

  1. Cache the repr string after the first call to avoid creating multiple clients
  2. Store host/db information during initialization or first run() call instead of creating clients in repr
  3. Extract the information synchronously and close the client immediately (though this is tricky with async clients)

The current implementation creates a new unclosed connection pool every time repr() is invoked, which will eventually exhaust connection resources.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +201
def test_redis__repr_standard_client(self):
"""Verify repr includes host and db for a standard Redis client."""
from redis.asyncio import Redis as RedisClient

check = RedisHealthCheck(
client_factory=lambda: RedisClient(host="myhost", port=6379, db=2)
)
assert repr(check) == "Redis(client=RedisClient(host=myhost, db=2))"

def test_redis__repr_from_url(self):
"""Verify repr includes host and db when client is created via from_url."""
from redis.asyncio import Redis as RedisClient

check = RedisHealthCheck(
client_factory=lambda: RedisClient.from_url(
"redis://cache.example.com:6379/3"
)
)
assert "host=cache.example.com" in repr(check), (
"repr should include the host from the Redis URL"
)
assert "db=3" in repr(check), (
"repr should include the db from the Redis URL"
)

def test_redis__repr_deprecated_client(self):
"""Verify repr includes host and db when using deprecated client parameter."""
from redis.asyncio import Redis as RedisClient

with pytest.warns(DeprecationWarning):
check = RedisHealthCheck(client=RedisClient(host="oldhost", port=6379, db=5))
assert "host=oldhost" in repr(check), (
"repr should include the host from the deprecated client"
)
assert "db=5" in repr(check), (
"repr should include the db from the deprecated client"
)

def test_redis__repr_sentinel_client(self):
"""Verify repr falls back gracefully for Sentinel clients without host/db."""
from redis.asyncio import Sentinel

check = RedisHealthCheck(
client_factory=lambda: Sentinel([("localhost", 26379)]).master_for(
"mymaster"
)
)
# Sentinel clients don't expose host/db in connection_pool.connection_kwargs
# __repr__ should fall back to the default dataclass repr without raising
assert repr(check) == "Redis()"
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests verify the repr output but don't check for resource leaks. Each test creates client instances via client_factory in repr without closing them. Consider adding assertions to verify that connections are properly managed, or at minimum, explicitly close any created resources in test teardown to prevent test pollution.

Copilot uses AI. Check for mistakes.
Comment thread tests/contrib/test_redis.py Outdated
client_factory=lambda: RedisCluster(host="clusterhost", port=7000)
)
# RedisCluster has no connection_pool attribute, so __repr__ should fall back
assert repr(check) == "Redis()"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot let's actually implement support clusters and show the hosts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in 56e256b. RedisCluster exposes a startup_nodes list where each ClusterNode has a .name attribute in host:port format, so the repr now shows:

Redis(client=RedisCluster(hosts=['node1:7000', 'node2:7001']))

The test (test_redis__repr_cluster_client) was updated to assert the actual node names are present rather than falling back to "Redis()".

Co-authored-by: codingjoe <1772890+codingjoe@users.noreply.github.com>
Copilot AI changed the title Show host and db in Redis check __repr__ Show host and db in Redis check __repr__ (with cluster support) Feb 23, 2026
Copilot AI requested a review from codingjoe February 23, 2026 10:51
@codingjoe
Copy link
Copy Markdown
Owner

@copilot add explicit security tests to make sure passwords and usernames are never included in the the repr (including as part of a resource url).

…m repr

Co-authored-by: codingjoe <1772890+codingjoe@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 23, 2026

@copilot add explicit security tests to make sure passwords and usernames are never included in the the repr (including as part of a resource url).

Added in d1ff5bc. Three security tests cover the cases you mentioned:

  • test_redis__repr_excludes_password — standard client with password= kwarg
  • test_redis__repr_excludes_password_from_url — credentials embedded in a Redis URL (redis://admin:supersecret@host/db), checks neither password nor username appear
  • test_redis__repr_excludes_cluster_passwordRedisCluster with password= and username= kwargs

The current implementation is already safe since __repr__ only extracts host and db keys explicitly from connection_kwargs, and uses node.name (which is host:port) for cluster nodes — so no credentials can leak. The tests lock this in as a regression guard.

@codingjoe
Copy link
Copy Markdown
Owner

pre-commit.ci run

@codingjoe codingjoe merged commit 6c12d16 into issues/659/alias Feb 23, 2026
25 of 26 checks passed
@codingjoe codingjoe deleted the copilot/sub-pr-662 branch February 23, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants