feat: Add Podman rootless support alongside Docker
What
Added comprehensive support for Podman (both rootless and rootful) alongside existing Docker support, enabling the Unstract platform to run with either container runtime without configuration changes for Docker users.
Key changes:
- Universal socket mount configuration using environment variable expansion with Docker as default
- Changed Traefik HTTP port from
80to8081for rootless Podman compatibility - Updated frontend container to expose port
8080internally (nginx listens on 8080) - Fixed
worker-file-processing-v2command structure to match other workers - New comprehensive documentation in
docker/CONTAINER_RUNTIME.md
Why
- Container Runtime Flexibility: Users should have the choice to run Unstract with their preferred container runtime (Docker or Podman) without platform limitations
- Rootless Container Support: Enable secure, rootless Podman deployments for improved security posture
- Zero-Config Docker Experience: Docker users (majority) should not need any configuration changes
- Enterprise Requirements: Some organizations mandate Podman for container workloads due to security policies
How
Socket Configuration:
- Default socket path:
/var/run/docker.sock(Docker) - Podman opt-in:
export DOCKER_SOCKET=${XDG_RUNTIME_DIR}/podman/podman.sock - Socket mount uses:
${DOCKER_SOCKET:-/var/run/docker.sock}
Port Configuration:
- Changed Traefik HTTP port:
80→8081(rootless compatibility) - Updated all service URLs to port
8081 - Frontend container exposes
8080internally
Documentation:
- Created
docker/CONTAINER_RUNTIME.mdwith runtime support guide - Updated
docker/README.mdwith quick start for both runtimes - Included prerequisites, troubleshooting, and technical details
Environment Files:
- Updated
backend/sample.env:DJANGO_APP_BACKEND_URLandWEB_APP_ORIGIN_URLto port 8081 - Updated
frontend/sample.env:REACT_APP_BACKEND_URLto port 8081
Code Quality:
- Fixed
worker-file-processing-v2to useentrypoint+commandpattern (consistency with other workers) - Added
.serenato.gitignore
Can this PR break any existing features. If yes, please list possible items. If no, please explain why.
YES - Breaking Change:
⚠️ Port Change from 80 to 8081
Impact:
- Users accessing the platform at
http://frontend.unstract.localhost(port 80) will need to update tohttp://frontend.unstract.localhost:8081 - Bookmarks, scripts, integrations using port 80 will break
- Environment variables in
.envfiles need updating
Mitigation:
- Clear documentation of breaking change in PR description and README
- Sample environment files updated with new port
- Migration guide provided in documentation
Why This Won't Break Other Features:
- Socket configuration defaults to Docker standard (
/var/run/docker.sock) - All internal service communication updated consistently
- Traefik routing rules updated to match new port
- Container discovery mechanisms unchanged
- Worker command fix improves consistency (doesn't break functionality)
Database Migrations
N/A - No database schema changes
Env Config
Required Updates:
backend/.env:
DJANGO_APP_BACKEND_URL=http://frontend.unstract.localhost:8081
WEB_APP_ORIGIN_URL=http://frontend.unstract.localhost:8081
frontend/.env:
REACT_APP_BACKEND_URL=http://frontend.unstract.localhost:8081
Optional (Podman users only):
export DOCKER_SOCKET=${XDG_RUNTIME_DIR}/podman/podman.sock
Relevant Docs
docker/CONTAINER_RUNTIME.md- New comprehensive runtime support guidedocker/README.md- Updated with quick start instructionsbackend/sample.env- Updated backend URLsfrontend/sample.env- Updated frontend backend URL
Related Issues or PRs
N/A
Dependencies Versions
- Podman 4.0+ (for Podman users)
- Docker 20.10+ (existing requirement)
- podman-compose 1.0.3+ (for Podman users using compose)
Notes on Testing
Docker Testing (Default):
VERSION=dev docker compose -f docker/docker-compose.yaml up -d
curl http://frontend.unstract.localhost:8081
Podman Testing (Opt-In):
# Enable Podman socket
systemctl --user enable --now podman.socket
# Set environment variable and run
export DOCKER_SOCKET=${XDG_RUNTIME_DIR}/podman/podman.sock
VERSION=dev podman-compose -f docker/docker-compose.yaml up -d
curl http://frontend.unstract.localhost:8081
Verification Checklist:
- [x] Docker works with default configuration (no environment variables needed)
- [x] Podman rootless works with
DOCKER_SOCKETenvironment variable - [x] Traefik discovers containers correctly with both runtimes
- [x] Frontend accessible at port 8081
- [x] Runner service spawns tool containers with both socket types
- [x] All service URLs updated consistently to port 8081
- [x]
worker-file-processing-v2uses correct command structure
Screenshots
N/A - Infrastructure change, no UI modifications
Checklist
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code
Co-Authored-By: Claude [email protected]
Summary by CodeRabbit
-
New Features
- Added Docker and Podman container runtime support with comprehensive documentation.
-
Documentation
- Added Container Runtime Support guide with socket detection, troubleshooting steps, and usage examples for both Docker and Podman workflows.
-
Chores
- Updated frontend service port configuration to 8080 and Traefik reverse proxy to port 8081.
- Modified backend and frontend URLs to reflect new port configuration.
Walkthrough
Reconfigures frontend to listen on 8080 and Traefik to expose HTTP on 8081, updates environment samples to reference the new frontend origin, replaces fixed Docker socket mounts with an environment-driven DOCKER_SOCKET fallback (Podman-aware), adds container runtime documentation, and adds .serena to .gitignore.
Changes
| Cohort / File(s) | Summary |
|---|---|
Port config — frontend image & serverdocker/dockerfiles/frontend.Dockerfile, frontend/nginx.conf |
Production EXPOSE/listen changed from port 80 to 8080. |
Compose — ports, socket, Traefik labels, workersdocker/docker-compose.yaml, docker/docker-compose-dev-essentials.yaml |
Traefik HTTP mapping moved to 8081; frontend service now serves on 8080 (external mapping preserved as 3000:8080 in dev compose); replaced fixed /var/run/docker.sock mounts with DOCKER_SOCKET environment-driven bind (with fallback and Podman notes); Celery worker/beat entries and formatting adjusted; comments updated. |
Environment samplesbackend/sample.env, frontend/sample.env |
Backend and frontend sample envs updated to reference http://frontend.unstract.localhost:8081 for frontend origin/backend URL variables. |
Container runtime docs & READMEdocker/CONTAINER_RUNTIME.md, docker/README.md |
Added container runtime support documentation covering Docker vs Podman socket detection, DOCKER_SOCKET override, examples, and troubleshooting; linked from Docker README with quick-start notes. |
Repository ignore.gitignore |
Added comment # MCP servers and ignore pattern .serena. |
Sequence Diagram(s)
sequenceDiagram
autonumber
participant Compose as docker-compose / podman-compose
participant SocketDetector as DOCKER_SOCKET logic
participant Traefik as Traefik reverse-proxy
participant Frontend as Frontend (NGINX)
Note over Compose,SocketDetector #D6EAF8: Startup / env evaluation
Compose->>SocketDetector: Read DOCKER_SOCKET or default /var/run/docker.sock
alt Podman socket present
SocketDetector-->>Compose: Use Podman socket path
else Default Docker socket
SocketDetector-->>Compose: Use /var/run/docker.sock
end
Note over Traefik,Frontend #F9E79F: Service binding & port mapping
Compose->>Traefik: Mount socket, set port mapping (8081 -> 80) and service label (frontend -> 8080)
Compose->>Frontend: Start container exposing 8080
Traefik->>Frontend: Route requests to port 8080
Frontend->>Frontend: NGINX listens on 8080
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
- Pay attention to socket mount changes and comments in
docker/docker-compose.yamlanddocker/docker-compose-dev-essentials.yaml. - Verify port consistency between
dockerfiles/frontend.Dockerfile,frontend/nginx.conf, and compose port mappings/labels. - Confirm environment variable names/values in
backend/sample.envandfrontend/sample.envmatch runtime expectations.
Pre-merge checks and finishing touches
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title Check | ✅ Passed | The title "feat: Add Podman rootless support alongside Docker" directly and accurately reflects the main objective of the pull request. The PR's primary feature is adding Podman support as an opt-in alternative to the existing Docker runtime, which is clearly captured in the title. The title is concise, readable, and avoids vague language. While the PR includes other changes such as port configuration updates and documentation, the core feature—enabling Podman support—is appropriately highlighted as the primary change. |
| Description Check | ✅ Passed | The pull request description is comprehensive and well-structured, containing all required sections from the repository's template. The "What" section clearly outlines key changes with bullet points, "Why" explains the rationale with four distinct points, and "How" provides detailed implementation guidance with configuration examples. Critically, the "Can this PR break any existing features" section—which the template notes is essential for admin review—is thoroughly completed with explicit acknowledgment of the breaking change (port 80 to 8081), detailed impact analysis, and mitigation strategies. Additional sections including Database Migrations, Env Config, Relevant Docs, Dependencies Versions, Notes on Testing, and Checklist are all present and appropriately detailed. The description demonstrates strong documentation practices with clear examples, testing procedures, and verification checklists. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
- [ ] Commit unit tests in branch
feat/podman
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
@chandrasekharan-zipstack Since there are breaking changes involved, we will need to add a release note to docker/scripts/release-notes/release_notes.json once the target release version is finalized.
Anywhere else to update?
@chandrasekharan-zipstack Since there are breaking changes involved, we will need to add a release note to
docker/scripts/release-notes/release_notes.jsononce the target release version is finalized.Anywhere else to update?
That's the only place to update. Once its up we could update the public documentation and link this README.md or maintain it there alone
Quality Gate passed
Issues
0 New issues
0 Accepted issues
Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code
Test Results
Summary
- ✅ Runner Tests: 11 passed, 0 failed (11 total)
- ✅ SDK1 Tests: 66 passed, 0 failed (66 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\_max\_time\_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{TestRetryWithExponentialBackoff.test\_delay\_would\_exceed\_max\_time}}$$ | $$\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\_max\_time}}$$ | $$\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{66}}$$ | $$\textcolor{#23d18b}{\tt{66}}$$ |