Skip to content

[FIX] Redis Sentinel support for tool containers and sidecars#1858

Merged
chandrasekharan-zipstack merged 7 commits intomainfrom
refactor/redis-client-creation-dry
Mar 15, 2026
Merged

[FIX] Redis Sentinel support for tool containers and sidecars#1858
chandrasekharan-zipstack merged 7 commits intomainfrom
refactor/redis-client-creation-dry

Conversation

@chandrasekharan-zipstack
Copy link
Contributor

What

  • DRY Redis client creation across worker cache modules using create_redis_client() factory from unstract-core
  • Fix Redis Sentinel crash in MetricsMixin (prompt-service)
  • Pass REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME env vars to tool containers and sidecar containers

Why

  • In Redis Sentinel environments (e.g., onprem-ha), the runner spawns tool pods with sidecar containers that only receive REDIS_HOST and REDIS_PORT. The sidecar connects in standalone mode to port 26379 (Sentinel protocol port), which rejects data commands like HGETALL and SETEX with "unknown command". This causes the sidecar's ToolExecutionTracker to fail writing execution status, making the worker conclude the tool container crashed — even though the tool completed successfully.
  • Multiple services had duplicated Redis client creation logic that didn't handle Sentinel mode consistently.

How

  • Runner (runner.py, constants.py): Forward REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME from runner env to sidecar container config, with sane defaults (False, mymaster)
  • Workflow Execution (tools_utils.py, constants.py): Pass Sentinel env vars in get_tool_environment_variables() so tool containers (MetricsMixin) can also connect via Sentinel
  • Workers: Refactored cache backends to use centralized create_redis_client() factory
  • SDK/Core: Fixed MetricsMixin Sentinel handling and DRY'd redis client creation

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

  • No. Sentinel env vars default to False / mymaster when not set, so non-Sentinel environments continue using standalone mode unchanged. The create_redis_client() factory already handles both modes.

Database Migrations

  • None

Env Config

  • REDIS_SENTINEL_MODE (default: False) — set to True to enable Sentinel mode for tool/sidecar containers
  • REDIS_SENTINEL_MASTER_NAME (default: mymaster) — Sentinel master name

Relevant Docs

  • N/A

Related Issues or PRs

Dependencies Versions

  • N/A

Notes on Testing

  • Deploy to an environment with Redis Sentinel (e.g., onprem-ha)
  • Run a workflow/API deployment that spawns tool containers
  • Verify sidecar logs show "Redis Sentinel mode enabled" instead of standalone
  • Verify no "unknown command" errors from sidecar
  • Verify file execution completes with SUCCESS status

Checklist

I have read and understood the Contribution Guidelines.

MetricsMixin was creating a raw StrictRedis client using REDIS_HOST/PORT
directly, which in Sentinel HA mode points to the Sentinel process (port
26379). Sentinel doesn't support the SELECT command, causing
ResponseError on every /answer-prompt call.

- Replace StrictRedis with create_redis_client() from unstract-core
  which handles Sentinel discovery via master_for()
- Add unstract-core as sdk1 dependency (all services using sdk1 already
  depend on core, so zero new transitive deps)
- Add error handling around set_start_time() and collect_metrics() so
  Redis failures degrade gracefully instead of crashing requests
- Add missing COPY for unstract/core in tool Dockerfiles (classifier,
  structure, text_extractor) since sdk1 now depends on core

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tool Dockerfiles (classifier, text_extractor, structure) use pip, not
uv. Since sdk1 now depends on unstract-core (which isn't on PyPI),
pip can't resolve it via [tool.uv.sources]. Adding explicit editable
install of core before sdk1 ensures pip finds it locally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Worker cache modules (cache_backends.py, cache_utils.py) duplicated
sentinel/standalone branching logic for Redis client creation. Both
now use create_redis_client() from unstract-core as the single entry
point, which already handles mode detection internally.

- Add SSL support (ssl, ssl_cert_reqs) to create_redis_client() via
  env vars ({prefix}SSL, {prefix}SSL_CERT_REQS), disabled by default
- Remove if-sentinel-else-standalone branching in cache_backends.py
  and cache_utils.py — replaced with single create_redis_client() call
- Remove unused os and redis imports from simplified modules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The runner was not forwarding REDIS_SENTINEL_MODE and
REDIS_SENTINEL_MASTER_NAME to tool containers and sidecars, causing the
sidecar to connect in standalone mode to the Sentinel port (26379) and
fail on all data commands (HGETALL, SETEX).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c40fd762-f2f2-4dcd-9efb-67f07acd16bc

📥 Commits

Reviewing files that changed from the base of the PR and between bf2c4c8 and 73b8216.

📒 Files selected for processing (5)
  • backend/sample.env
  • tools/classifier/src/config/properties.json
  • tools/structure/src/config/properties.json
  • tools/text_extractor/src/config/properties.json
  • unstract/tool-registry/tool_registry_config/public_tools.json
✅ Files skipped from review due to trivial changes (3)
  • backend/sample.env
  • tools/structure/src/config/properties.json
  • tools/classifier/src/config/properties.json

Summary by CodeRabbit

  • New Features

    • Added Redis Sentinel mode configuration support for Redis deployments.
  • Chores

    • Updated tool versions and container image tags for classifier, text extractor, and structure tools.
    • Updated sample environment image reference.

Walkthrough

Added Redis Sentinel configuration support by introducing two new environment variables (REDIS_SENTINEL_MODE, REDIS_SENTINEL_MASTER_NAME) and wiring them into constants, runtime/tool environment initialization, and sidecar container configuration. Also bumped several tool image versions and properties.

Changes

Cohort / File(s) Summary
Constants Definitions
runner/src/unstract/runner/constants.py, unstract/workflow-execution/src/unstract/workflow_execution/constants.py
Added REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME constants to Env and ToolRuntimeVariable classes.
Configuration Integration
runner/src/unstract/runner/runner.py, unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py
Expose sentinel variables in sidecar_env and tool environment initialization with defaults ("False", "mymaster").
Environment / Sample
backend/sample.env
Updated STRUCTURE_TOOL_IMAGE_URL and STRUCTURE_TOOL_IMAGE_TAG from 0.0.97 → 0.0.98.
Tool metadata updates
tools/classifier/src/config/properties.json, tools/structure/src/config/properties.json, tools/text_extractor/src/config/properties.json, unstract/tool-registry/tool_registry_config/public_tools.json
Bumped toolVersion and corresponding image_url/image_tag for classifier (0.0.76→0.0.77), text_extractor (0.0.72→0.0.73), and structure (0.0.97→0.0.98).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main changes: adding Redis Sentinel support for tool containers and sidecars, which aligns with the primary objective of the PR.
Description check ✅ Passed The description covers all required template sections with comprehensive details about What, Why, How, backward compatibility, env config, testing notes, and related issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/redis-client-creation-dry
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR fixes a Redis Sentinel crash in sidecar and tool containers by forwarding REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME env vars to both the tool container (via tools_utils.py::get_tool_environment_variables()) and the sidecar container (via runner.py::_get_sidecar_container_config()). It also bumps version numbers for the classifier, text_extractor, and structure tools across properties.json, public_tools.json, and sample.env.

  • Sentinel env vars added to constants: Both runner/constants.py and workflow_execution/constants.py now declare REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME in their respective Env/ToolRuntimeVariable classes.
  • tools_utils.py: Reads sentinel vars with raise_exception=False (optional) and forwards them to tool containers with or "False" / or "mymaster" defaults — correct given that create_redis_client() parses the string value with .strip().lower() == "true".
  • runner.py: Adds sentinel vars to the sidecar's env dict using os.getenv(..., default). However, the sidecar reads these directly from the runner service's environment, while the tool container receives them from the workflow-execution service's environment (via the envs dict). These are two separate services: if only one is updated with REDIS_SENTINEL_MODE=True, the sidecar will silently revert to standalone mode and reproduce the original crash. The runner's sample.env documents both vars, but there is no programmatic enforcement of consistency between the two sources.
  • Tool version bumps: All three tool version increments (classifier 0.0.77, text_extractor 0.0.73, structure 0.0.98) are consistent across properties.json, public_tools.json, and sample.env.

Confidence Score: 4/5

  • Safe to merge — non-Sentinel deployments are unaffected by the "False" defaults, and the fix is correct when both services are configured consistently.
  • The core fix is logically sound and the create_redis_client() factory correctly interprets the string "True"/"False" values passed as env vars. The main risk is an operational one: the sidecar reads REDIS_SENTINEL_MODE from the runner's own environment while the tool container reads it from the workflow-execution service's environment — two independent sources that must be kept in sync by the operator. A partial upgrade or misconfiguration could silently leave the sidecar in standalone mode and reproduce the original crash. The fix works correctly when both services are fully configured.
  • runner/src/unstract/runner/runner.py — the dual-source sentinel configuration between the sidecar and the tool container deserves attention.

Important Files Changed

Filename Overview
runner/src/unstract/runner/runner.py Adds REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME to the sidecar container env dict, reading directly from the runner service's own os.getenv(). Consistent with how all other Redis vars (HOST, PORT, etc.) are handled for the sidecar.
runner/src/unstract/runner/constants.py Adds REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME constants to the Env class. Clean, straightforward addition.
unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py Reads REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME from the workflow-execution service's env (raise_exception=False) and forwards them in get_tool_environment_variables() using an or-default pattern. Correctly handles None but differs from runner.py's os.getenv(..., default) pattern.
unstract/workflow-execution/src/unstract/workflow_execution/constants.py Adds REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME to ToolRuntimeVariable. Clean, no issues.
backend/sample.env Bumps STRUCTURE_TOOL_IMAGE_TAG from 0.0.97 to 0.0.98, consistent with the properties.json change in tools/structure.

Sequence Diagram

sequenceDiagram
    participant WE as Workflow Execution Service
    participant Runner as Runner Service
    participant Tool as Tool Container
    participant Sidecar as Sidecar Container
    participant Redis as Redis (Sentinel / Standalone)

    WE->>WE: get_tool_environment_variables()<br/>reads REDIS_SENTINEL_MODE from WE env<br/>falls back to "False" via `or` default
    WE->>Runner: run_container(envs={REDIS_SENTINEL_MODE, REDIS_HOST, ...})

    Runner->>Runner: _get_sidecar_container_config()<br/>reads REDIS_SENTINEL_MODE from runner's own os.getenv()<br/>defaults to "False" if not set

    Runner->>Tool: launch with envs[REDIS_SENTINEL_MODE] from WE
    Runner->>Sidecar: launch with runner's os.getenv(REDIS_SENTINEL_MODE)

    Note over Tool,Sidecar: ⚠️ Two independent env sources —<br/>must be configured consistently across both services

    Tool->>Redis: create_redis_client()<br/>_is_sentinel_mode() parses REDIS_SENTINEL_MODE
    Sidecar->>Redis: create_redis_client()<br/>_is_sentinel_mode() parses REDIS_SENTINEL_MODE

    alt Both services have REDIS_SENTINEL_MODE=True
        Redis-->>Tool: Sentinel master connection ✅
        Redis-->>Sidecar: Sentinel master connection ✅
    else Runner env missing REDIS_SENTINEL_MODE=True
        Redis-->>Tool: Sentinel master connection ✅
        Redis-->>Sidecar: Standalone to Sentinel port → "unknown command" ❌
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: runner/src/unstract/runner/runner.py
Line: 235-238

Comment:
**Sidecar sentinel config is decoupled from tool container sentinel config**

The sidecar's `REDIS_SENTINEL_MODE` is sourced exclusively from the **runner service's own environment** via `os.getenv()`, while the tool container receives it from the `envs` dict populated by the workflow-execution service (via `tools_utils.py`). These are two separate services that must be configured identically for the fix to hold.

If an operator sets `REDIS_SENTINEL_MODE=True` in the workflow-execution service but not in the runner service (or if the runner pod restarts before its env is updated), the sidecar will silently fall back to standalone mode with the default `"False"` — connecting to port 26379 in standalone mode and reproducing the exact `HGETALL`/`SETEX` "unknown command" crash this PR aims to fix.

The same divergence risk exists for `REDIS_HOST` and `REDIS_PORT`, but those are already long-established; adding two more independently-sourced sentinel vars raises the surface area further.

A more defensive approach would be to forward the sentinel values from the already-populated `envs` dict into the sidecar config inside `run_container()`:

```python
# Inside run_container(), before calling _get_sidecar_container_config()
sidecar_config = self._get_sidecar_container_config(
    ...,
    # Pass sentinel vars from the tool envs so both containers use the same source
    sentinel_mode=envs.get("REDIS_SENTINEL_MODE", os.getenv(Env.REDIS_SENTINEL_MODE, "False")),
    sentinel_master_name=envs.get("REDIS_SENTINEL_MASTER_NAME", os.getenv(Env.REDIS_SENTINEL_MASTER_NAME, "mymaster")),
)
```

This would make the sidecar's sentinel mode consistent with the tool container's by design, rather than relying on operational discipline across two independently deployed services.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 73b8216

Patch bump for tools that depend on unstract-core's updated
Redis client with Sentinel support:
- classifier: 0.0.76 -> 0.0.77
- structure: 0.0.97 -> 0.0.98
- text_extractor: 0.0.72 -> 0.0.73

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 63 passed, 0 failed (63 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{63}}$$ $$\textcolor{#23d18b}{\tt{63}}$$

@sonarqubecloud
Copy link

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit bf56976 into main Mar 15, 2026
8 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the refactor/redis-client-creation-dry branch March 15, 2026 09:14
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