chore: migrate to uv package manager and optimize production deployments#74
Conversation
- Unify dependencies in pyproject.toml and lock versions in uv.lock using PEP 621. - Refactor Dockerfile to use uv sync, copy .venv to runtime stage, and run using virtual environment interpreter. - Update CI and Code Quality GitHub workflows to use setup-uv@v5 and execute commands via uv run. - Align README.md commands and labeler.yml configurations with the uv workflow. - Tune production Kafka healthcheck settings in docker-compose.prod.yml to use 127.0.0.1, double the timeout and retries, and increase start_period to 90s to tolerate build-time CPU/IO load. - Change api dependency condition on kafka from service_healthy to service_started to prevent transient deployment blockages. Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
|
Warning Review limit reached
Your plan currently allows 1 review/hour. Refill in 52 minutes and 16 seconds. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more review capacity refills, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time. Please see our FAQ for further information. 📝 WalkthroughWalkthroughConsolidates dependency management on uv (pyproject.toml + uv.lock), updates README, CI, and Docker to use uv, adjusts docker-compose healthchecks, and adds CI-safe Alembic migration and production redeploy scripts with health polling. ChangesUnified Migration to uv and Deployment Automation
🎯 3 (Moderate) | ⏱️ ~22 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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 |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/code-quality.yml (1)
10-13:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDefine least-privilege workflow permissions explicitly.
.github/workflows/code-quality.ymldoesn’t define a top-levelpermissions:block, so it relies on defaultGITHUB_TOKENpermissions. Add explicit minimal permissions (e.g.,contents: read) to reduce unnecessary access.Suggested hardening
name: Code Quality +permissions: + contents: read on: push: branches: [ main, develop ]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/code-quality.yml around lines 10 - 13, Add an explicit top-level GitHub Actions permissions block to the workflow to enforce least privilege; in .github/workflows/code-quality.yml, at the root of the file (outside jobs), add a permissions: map (for the lint-and-typecheck job) with minimal rights such as contents: read (and any other narrowly required scopes) instead of relying on default GITHUB_TOKEN permissions so the lint-and-typecheck job runs with least privilege.Dockerfile (1)
6-37:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winFix broken venv relocation:
CMDcallsuvicornconsole script whose shebang may still point to/build/.venv.This Dockerfile builds the venv at
/build/.venvand copies it to/app/.venv, but venv-installed console scripts typically hardcode an absolute interpreter path in their shebang, soCMD ["uvicorn", ...](from/app/.venv/binviaPATH) can fail at startup.Safer fix
-FROM python:3.11-slim AS builder -WORKDIR /build +FROM python:3.11-slim AS builder +WORKDIR /app @@ -COPY --from=builder /build/.venv /app/.venv +COPY --from=builder /app/.venv /app/.venvOr invoke module form explicitly:
-CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] +CMD ["/app/.venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Dockerfile` around lines 6 - 37, The virtualenv is built at /build/.venv then copied to /app/.venv, but console-script shebangs (like the uvicorn wrapper invoked by CMD ["uvicorn", ...]) will still point to /build/.venv and may break; update the runtime usage to avoid relying on relocated shebangs by invoking Uvicorn as a module using the venv Python (e.g., run /app/.venv/bin/python -m uvicorn ...) or ensure shebangs are fixed after copy (rebuild or rewrite scripts in /app/.venv/bin), and adjust the Dockerfile CMD and any PATH handling to reference /app/.venv/bin/python -m uvicorn (or rewrite shebangs) instead of calling the uvicorn console script directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/db.sh:
- Around line 1-8: Add a shebang (e.g., #!/usr/bin/env bash) and enable strict
modes (set -euo pipefail) at the top of the script, and validate the
DATABASE_URL_DIRECT environment variable before running migrations (fail with a
clear stderr message and non-zero exit if DATABASE_URL_DIRECT is empty or unset)
so the alembic invocation (the docker compose ... alembic -x
sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head) cannot run against an
unintended target; keep the existing cd and docker compose usage but bail early
if the env check fails.
In @.github/scripts/run.sh:
- Around line 15-17: The printed timeout "up to 90s" is inconsistent with the
loop that currently does 12 iterations of a 5s sleep (~60s); update the
health-wait logic in .github/scripts/run.sh by either changing the loop bound
(the while condition using ITER) from 12 to 18 to actually wait ~90s, or change
the echoed message to "up to 60s" to match the existing loop; adjust the ITER
increment/sleep usage alongside the while [ $ITER -lt ... ] condition to keep
semantics correct.
- Around line 1-7: Add a POSIX shell shebang and enable strict mode (set -euo
pipefail and a safe IFS) at the top of the run.sh script, validate the target
directory before cd (checking existence and failing with a clear message), and
replace the non-deterministic "git pull origin main" with a deterministic
fetch+fast-forward/reset flow: git fetch origin main and then git -C <repo-dir>
reset --hard origin/main (or checkout and fast-forward) to avoid merges/prompts;
ensure commands use absolute paths or git -C to avoid relying on current working
directory and surface any git errors via set -e so failures abort safely.
In @.github/workflows/ci.yml:
- Around line 14-23: Replace the movable action tags with pinned commit SHAs:
locate the two uses: entries referencing "astral-sh/setup-uv@v5" and
"actions/setup-python@v5" in the CI workflow and change each to the
corresponding full immutable commit SHA for that action (e.g.,
astral-sh/setup-uv@<commit-sha> and actions/setup-python@<commit-sha>), ensuring
you pick the intended release commit so behavior remains the same and update any
lock/cache inputs if required.
In @.github/workflows/code-quality.yml:
- Around line 15-23: Replace the mutable GitHub Action tags with immutable
commit SHAs for both uses entries: change the astral-sh/setup-uv@v5 reference
(symbol: "astral-sh/setup-uv") and the actions/setup-python@v5 reference
(symbol: "actions/setup-python") to their corresponding pinned commit SHAs;
update the workflow to use those SHAs in place of `@v5` so CI runs are
reproducible and secure.
In `@docker-compose.prod.yml`:
- Line 64: The code currently sets the Kafka dependency to condition:
service_started but KafkaManager.start() performs all admin/producer/consumer
setup once and throws if Kafka is unreachable, causing Kafka initialization to
be skipped with no later retry; modify KafkaManager.start() to perform resumable
initialization with exponential backoff and bounded retries (or schedule a
background reconnect loop) that attempts admin/topic creation, producer
creation, and consumer subscriptions until success, and ensure any thrown
exceptions are propagated/logged but do not permanently mark Kafka as "skipped";
reference KafkaManager.start() for the initialization flow and
BaseConsumer._process_with_retry() as a model for retry/backoff behavior and
integrate the retry loop so the FastAPI lifespan no longer leaves consumers
unstarted when Kafka becomes available later.
In `@Dockerfile`:
- Line 9: The COPY instruction uses a mutable image reference "COPY
--from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/" which prevents reproducible
builds; update that COPY --from reference to a pinned immutable image (either a
released tag like ghcr.io/astral-sh/uv:<version> or the digest form
ghcr.io/astral-sh/uv@sha256:<digest>) so the build always pulls the exact same
image, then commit the updated Dockerfile.
In `@pyproject.toml`:
- Around line 5-117: The pyproject currently lists QA/dev tools (black,
coverage, isort, mypy, pytest, pytest-asyncio, pytest-cov, ruff) inside
[project].dependencies; remove those entries from the dependencies array and add
them under a dedicated dev dependency group (e.g. using PEP 735
[project.dependency-groups] or PEP 621 [project.optional-dependencies] with a
"dev" key) so production installs won't pull these packages; ensure you
reference the same package names (black, coverage, isort, mypy, pytest,
pytest-asyncio, pytest-cov, ruff) when creating the new dev group and keep all
other entries in the original dependencies list unchanged.
In `@README.md`:
- Around line 110-111: Remove the duplicate backend startup command in the
README by keeping only the canonical uvicorn invocation that includes host and
port; delete the redundant "uv run uvicorn app.main:app --reload" line and
retain "uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload" so
there's a single clear command shown.
- Around line 86-90: Add an explicit install step for the "uv" tool before the
existing "uv sync" instruction: update the "Local Development Setup" section to
include a short prerequisite line such as "Install uv (see
https://github.com/uvtool/uv or run your package manager install command)" and
ensure subsequent commands that use uv (e.g., "uv sync" and "uv run ...") follow
that new step so first-time users have the required tool installed.
---
Outside diff comments:
In @.github/workflows/code-quality.yml:
- Around line 10-13: Add an explicit top-level GitHub Actions permissions block
to the workflow to enforce least privilege; in
.github/workflows/code-quality.yml, at the root of the file (outside jobs), add
a permissions: map (for the lint-and-typecheck job) with minimal rights such as
contents: read (and any other narrowly required scopes) instead of relying on
default GITHUB_TOKEN permissions so the lint-and-typecheck job runs with least
privilege.
In `@Dockerfile`:
- Around line 6-37: The virtualenv is built at /build/.venv then copied to
/app/.venv, but console-script shebangs (like the uvicorn wrapper invoked by CMD
["uvicorn", ...]) will still point to /build/.venv and may break; update the
runtime usage to avoid relying on relocated shebangs by invoking Uvicorn as a
module using the venv Python (e.g., run /app/.venv/bin/python -m uvicorn ...) or
ensure shebangs are fixed after copy (rebuild or rewrite scripts in
/app/.venv/bin), and adjust the Dockerfile CMD and any PATH handling to
reference /app/.venv/bin/python -m uvicorn (or rewrite shebangs) instead of
calling the uvicorn console script directly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 06bb1df2-ea61-4993-98fb-4d2cffee25b5
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
.github/labeler.yml.github/scripts/db.sh.github/scripts/run.sh.github/workflows/ci.yml.github/workflows/code-quality.ymlDockerfileREADME.mddocker-compose.prod.ymlpyproject.tomlrequirements.txtrequirements/base.txtrequirements/dev.txtrequirements/prod.txt
💤 Files with no reviewable changes (1)
- requirements.txt
| set -e | ||
| echo "==> Navigating to project directory..." | ||
| cd ~/apps/spoken-api | ||
|
|
||
| echo "==> Running Alembic migrations..." | ||
| # Use -T for non-interactive shell in CI | ||
| docker compose -f docker-compose.prod.yml exec -T api \ | ||
| alembic -x sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head |
There was a problem hiding this comment.
Add shebang and strict env validation to prevent unsafe migration runs.
This script currently depends on caller shell semantics and does not fail fast on missing DATABASE_URL_DIRECT. That can cause execution failure or unintended migration target resolution.
Proposed fix
+#!/usr/bin/env bash
-set -e
+set -euo pipefail
+
+: "${DATABASE_URL_DIRECT:?DATABASE_URL_DIRECT is required}"
echo "==> Navigating to project directory..."
cd ~/apps/spoken-api🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/db.sh around lines 1 - 8, Add a shebang (e.g.,
#!/usr/bin/env bash) and enable strict modes (set -euo pipefail) at the top of
the script, and validate the DATABASE_URL_DIRECT environment variable before
running migrations (fail with a clear stderr message and non-zero exit if
DATABASE_URL_DIRECT is empty or unset) so the alembic invocation (the docker
compose ... alembic -x sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head)
cannot run against an unintended target; keep the existing cd and docker compose
usage but bail early if the env check fails.
| set -e | ||
|
|
||
| echo "==> Navigating to project directory..." | ||
| cd ~/apps/spoken-api | ||
|
|
||
| echo "==> Pulling latest code from main..." | ||
| git pull origin main |
There was a problem hiding this comment.
Make the deploy script deterministic and shell-safe.
For deployment automation, add a shebang/strict mode and replace git pull with a deterministic fast-forward/reset flow to avoid merge-state drift and prompt/failure edge cases.
Proposed fix
+#!/usr/bin/env bash
-set -e
+set -euo pipefail
+
+git fetch origin main
+git reset --hard origin/main
-
- echo "==> Pulling latest code from main..."
- git pull origin main
+ echo "==> Synced to origin/main."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/run.sh around lines 1 - 7, Add a POSIX shell shebang and
enable strict mode (set -euo pipefail and a safe IFS) at the top of the run.sh
script, validate the target directory before cd (checking existence and failing
with a clear message), and replace the non-deterministic "git pull origin main"
with a deterministic fetch+fast-forward/reset flow: git fetch origin main and
then git -C <repo-dir> reset --hard origin/main (or checkout and fast-forward)
to avoid merges/prompts; ensure commands use absolute paths or git -C to avoid
relying on current working directory and surface any git errors via set -e so
failures abort safely.
| echo "==> Waiting for container to be healthy (up to 90s)..." | ||
| ITER=0 | ||
| while [ $ITER -lt 12 ]; do |
There was a problem hiding this comment.
Health-wait window is inconsistent with the printed timeout.
The script says “up to 90s” but 12 * 5s waits about 60s. Align either the loop bound or the message to avoid misleading deploy diagnostics.
Proposed fix
- while [ $ITER -lt 12 ]; do
+ while [ $ITER -lt 18 ]; do🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/run.sh around lines 15 - 17, The printed timeout "up to 90s"
is inconsistent with the loop that currently does 12 iterations of a 5s sleep
(~60s); update the health-wait logic in .github/scripts/run.sh by either
changing the loop bound (the while condition using ITER) from 12 to 18 to
actually wait ~90s, or change the echoed message to "up to 60s" to match the
existing loop; adjust the ITER increment/sleep usage alongside the while [ $ITER
-lt ... ] condition to keep semantics correct.
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v5 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| cache: 'pip' | ||
| python-version-file: "pyproject.toml" | ||
| - name: Install dependencies |
There was a problem hiding this comment.
Pin GitHub Actions to immutable commit SHAs.
In .github/workflows/ci.yml (lines 15 and 20), astral-sh/setup-uv@v5 and actions/setup-python@v5 use movable @v5 tags; pin both uses: entries to full commit SHAs to avoid unexpected upstream changes.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 14 - 23, Replace the movable action
tags with pinned commit SHAs: locate the two uses: entries referencing
"astral-sh/setup-uv@v5" and "actions/setup-python@v5" in the CI workflow and
change each to the corresponding full immutable commit SHA for that action
(e.g., astral-sh/setup-uv@<commit-sha> and actions/setup-python@<commit-sha>),
ensuring you pick the intended release commit so behavior remains the same and
update any lock/cache inputs if required.
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v5 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| cache: 'pip' | ||
| python-version-file: "pyproject.toml" |
There was a problem hiding this comment.
Pin GitHub Actions to immutable commit SHAs (not mutable @v5 tags).
In .github/workflows/code-quality.yml, astral-sh/setup-uv@v5 (line 16) and actions/setup-python@v5 (line 21) use mutable tags; pin them to commit SHAs for reproducible and safer CI runs.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/code-quality.yml around lines 15 - 23, Replace the mutable
GitHub Action tags with immutable commit SHAs for both uses entries: change the
astral-sh/setup-uv@v5 reference (symbol: "astral-sh/setup-uv") and the
actions/setup-python@v5 reference (symbol: "actions/setup-python") to their
corresponding pinned commit SHAs; update the workflow to use those SHAs in place
of `@v5` so CI runs are reproducible and secure.
| condition: service_healthy | ||
| kafka: | ||
| condition: service_healthy | ||
| condition: service_started |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect startup-time Kafka usage and retry/backoff handling.
rg -n -C3 --iglob '*.py' '\b(KAFKA_BOOTSTRAP_SERVERS|AIOKafka|KafkaProducer|KafkaConsumer|bootstrap_servers)\b'
rg -n -C3 --iglob '*.py' '\b(retry|backoff|tenacity|sleep\(|wait_for|retrying)\b'Repository: Brints/spoken-api
Length of output: 12576
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Confirm depends_on condition at the referenced line
echo "== docker-compose.prod.yml around line 64 =="
nl -ba docker-compose.prod.yml | sed -n '50,80p'
# 2) Inspect API startup/lifespan hook where KafkaManager.start() is called (if present)
echo "== Searching for KafkaManager / get_kafka_manager usage =="
rg -n --hidden --iglob 'app/**/*.py' '\bget_kafka_manager\b|\bKafkaManager\b|kafka_manager\.start\(' app || true
# 3) Inspect KafkaManager.start() and initialization flow
echo "== app/kafka/manager.py relevant sections =="
nl -ba app/kafka/manager.py | sed -n '1,260p'
# 4) Inspect producer/consumer startup methods for retry/backoff
echo "== app/kafka/producer.py relevant sections =="
nl -ba app/kafka/producer.py | sed -n '1,220p'
echo "== app/kafka/consumer.py relevant sections =="
nl -ba app/kafka/consumer.py | sed -n '1,260p'
# 5) Inspect exception handling around Kafka startup in FastAPI/main (if found)
echo "== Searching for FastAPI lifespan / startup blocks =="
rg -n --hidden --iglob 'app/**/*.py' '\blifespan\b|startup\b|on_startup\b' app || true
# If app/main.py exists, show it (only if small enough)
if [ -f app/main.py ]; then
echo "== app/main.py =="
wc -l app/main.py
nl -ba app/main.py | sed -n '1,260p'
fiRepository: Brints/spoken-api
Length of output: 167
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# helper: print file with line numbers (works without nl)
print_with_numbers() {
local file="$1"
awk '{printf "%6d:%s\n", NR, $0}' "$file"
}
echo "== docker-compose.prod.yml around line 64 =="
if [ -f docker-compose.prod.yml ]; then
print_with_numbers docker-compose.prod.yml | sed -n '50,80p'
else
echo "Missing docker-compose.prod.yml"
fi
echo "== Searching for KafkaManager / get_kafka_manager usage =="
rg -n --hidden --iglob 'app/**/*.py' '\bget_kafka_manager\b|\bKafkaManager\b|kafka_manager\.start\(' app || true
rg -n --hidden --iglob 'app/**/*.py' '\b(lifespan|on_startup|startup)\b' app || true
echo "== app/kafka/manager.py =="
if [ -f app/kafka/manager.py ]; then
wc -l app/kafka/manager.py
# show the start/init path only
rg -n 'class KafkaManager|async def start|_init_topics|AIOKafkaAdminClient' app/kafka/manager.py
print_with_numbers app/kafka/manager.py | sed -n '1,260p'
else
echo "Missing app/kafka/manager.py"
fi
echo "== app/kafka/producer.py =="
if [ -f app/kafka/producer.py ]; then
wc -l app/kafka/producer.py
rg -n 'class KafkaProducer|async def start|def is_started' app/kafka/producer.py
print_with_numbers app/kafka/producer.py | sed -n '1,220p'
else
echo "Missing app/kafka/producer.py"
fi
echo "== app/kafka/consumer.py =="
if [ -f app/kafka/consumer.py ]; then
wc -l app/kafka/consumer.py
rg -n 'class BaseConsumer|async def start|_process_with_retry|async def _process_with_retry' app/kafka/consumer.py
print_with_numbers app/kafka/consumer.py | sed -n '1,320p'
else
echo "Missing app/kafka/consumer.py"
fi
# Inspect FastAPI entrypoint files if present
for f in app/main.py main.py app/app.py server.py; do
if [ -f "$f" ]; then
echo "== Inspecting $f =="
wc -l "$f"
rg -n --hidden '\blifespan\b|on_startup|startup|KafkaManager|get_kafka_manager|kafka_manager\.start\(' "$f" || true
print_with_numbers "$f" | sed -n '1,260p'
fi
doneRepository: Brints/spoken-api
Length of output: 31198
Handle Kafka not-ready startup explicitly when using service_started.
docker-compose.prod.yml switches Kafka dependency to condition: service_started, but KafkaManager.start() performs topic/admin, producer, and consumer initialization with no startup retry/backoff—if Kafka isn’t reachable, startup throws and Kafka initialization is skipped (FastAPI lifespan catches the exception and logs “Kafka startup skipped”). There’s no later background retry to start Kafka, so the API can come up without running consumers; message retry/backoff only exists for already-consuming message handling (BaseConsumer._process_with_retry()).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docker-compose.prod.yml` at line 64, The code currently sets the Kafka
dependency to condition: service_started but KafkaManager.start() performs all
admin/producer/consumer setup once and throws if Kafka is unreachable, causing
Kafka initialization to be skipped with no later retry; modify
KafkaManager.start() to perform resumable initialization with exponential
backoff and bounded retries (or schedule a background reconnect loop) that
attempts admin/topic creation, producer creation, and consumer subscriptions
until success, and ensure any thrown exceptions are propagated/logged but do not
permanently mark Kafka as "skipped"; reference KafkaManager.start() for the
initialization flow and BaseConsumer._process_with_retry() as a model for
retry/backoff behavior and integrate the retry loop so the FastAPI lifespan no
longer leaves consumers unstarted when Kafka becomes available later.
| WORKDIR /build | ||
|
|
||
| # Copy uv binary from the official image | ||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show Dockerfile around the referenced lines
if [ -f Dockerfile ]; then
echo "== Dockerfile (lines 1-40) =="
nl -ba Dockerfile | sed -n '1,40p'
else
echo "Dockerfile not found at repo root"
exit 1
fi
# Find all occurrences of the uv image/tag
echo "== Search for ghcr.io/astral-sh/uv =="
rg -n "ghcr\.io/astral-sh/uv(?::[^ \t]+)?" . || true
echo "== Search for :latest usage =="
rg -n "ghcr\.io/astral-sh/uv:latest" . || trueRepository: Brints/spoken-api
Length of output: 152
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show Dockerfile around the referenced lines
if [ -f Dockerfile ]; then
echo "== Dockerfile (lines 1-60) =="
cat -n Dockerfile | sed -n '1,60p'
else
echo "Dockerfile not found at repo root"
exit 1
fi
echo "== Search for ghcr.io/astral-sh/uv =="
rg -n "ghcr\.io/astral-sh/uv" . || true
echo "== Search for ghcr.io/astral-sh/uv:latest specifically =="
rg -n "ghcr\.io/astral-sh/uv:latest" . || true
echo "== Search for any COPY --from=ghcr.io/astral-sh/uv:... =="
rg -n "COPY\s+--from=ghcr\.io/astral-sh/uv:[^\s]+" Dockerfile || trueRepository: Brints/spoken-api
Length of output: 2608
🌐 Web query:
ghcr.io/astral-sh/uv latest tag mutable digest pin
💡 Result:
ghcr.io/astral-sh/uv:latest tag is intended as a “floating”/convenience tag, so it should be treated as mutable; for reproducibility you should pin by image digest (or at least by a specific version tag like:0.11.16). The uv Docker docs explicitly recommend pinning to a specific uv version and state that pinning to a specific SHA256 digest is best practice, because tags can be moved across different commit SHAs [1]. Practical ways to “digest pin”: 1) Pin by digest directly in Dockerfile/COPY, e.g. replace:latest with ghcr.io/astral-sh/uv@sha256: (docs show this pattern for the image) [1]. 2) Derive the digest for:latest at build/CI time using a registry query tool, then use that digest for the actual copy/deploy step (the docs show gh attestation verify resolving an OCI reference to a digest for verification purposes, e.g. “Resolved digest sha256:…” for oci://ghcr.io/astral-sh/uv:latest) [1]. Note: your exact digest for today’s:latest is not stated in the sources we found; you must resolve it from GHCR for your target platform (the registry tags usually map to an image manifest/index that has a digest) [1].
Citations:
Pin ghcr.io/astral-sh/uv to an immutable digest (Dockerfile line 9)
Dockerfile uses COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/; :latest is mutable, so pin to a specific version tag or ghcr.io/astral-sh/uv@sha256:<digest> for reproducible/auditable builds.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Dockerfile` at line 9, The COPY instruction uses a mutable image reference
"COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/" which prevents
reproducible builds; update that COPY --from reference to a pinned immutable
image (either a released tag like ghcr.io/astral-sh/uv:<version> or the digest
form ghcr.io/astral-sh/uv@sha256:<digest>) so the build always pulls the exact
same image, then commit the updated Dockerfile.
| dependencies = [ | ||
| "aioboto3==15.5.0", | ||
| "aiobotocore==2.25.1", | ||
| "aiofiles==25.1.0", | ||
| "aiohappyeyeballs==2.6.1", | ||
| "aiohttp==3.13.3", | ||
| "aioitertools==0.13.0", | ||
| "aiokafka==0.13.0", | ||
| "aiosignal==1.4.0", | ||
| "alembic==1.18.4", | ||
| "annotated-doc==0.0.4", | ||
| "annotated-types==0.7.0", | ||
| "anyio==4.12.1", | ||
| "async-timeout==5.0.1", | ||
| "asyncpg==0.31.0", | ||
| "attrs==25.4.0", | ||
| "bcrypt==5.0.0", | ||
| "black==26.3.1", | ||
| "boto3==1.40.61", | ||
| "botocore==1.40.61", | ||
| "certifi==2026.2.25", | ||
| "cffi==2.0.0", | ||
| "charset-normalizer==3.4.5", | ||
| "click==8.3.1", | ||
| "cloudinary==1.44.1", | ||
| "colorama==0.4.6", | ||
| "coverage==7.13.4", | ||
| "cryptography==46.0.5", | ||
| "deepgram-sdk==6.0.1", | ||
| "deprecated==1.3.1", | ||
| "distro==1.9.0", | ||
| "dnspython==2.8.0", | ||
| "ecdsa==0.19.1", | ||
| "email-validator==2.3.0", | ||
| "fastapi==0.135.1", | ||
| "fastapi-cli==0.0.24", | ||
| "fastapi-cloud-cli==0.15.0", | ||
| "fastar==0.8.0", | ||
| "frozenlist==1.8.0", | ||
| "greenlet==3.3.2", | ||
| "h11==0.16.0", | ||
| "hiredis==3.3.0", | ||
| "httpcore==1.0.9", | ||
| "httptools==0.7.1", | ||
| "httpx==0.28.1", | ||
| "idna==3.11", | ||
| "iniconfig==2.3.0", | ||
| "isort==8.0.1", | ||
| "itsdangerous==2.2.0", | ||
| "jinja2==3.1.6", | ||
| "jiter==0.13.0", | ||
| "jmespath==1.1.0", | ||
| "librt==0.8.1", | ||
| "limits==5.8.0", | ||
| "mailgun2==2.0.1", | ||
| "mako==1.3.10", | ||
| "markdown-it-py==4.0.0", | ||
| "markupsafe==3.0.3", | ||
| "mdurl==0.1.2", | ||
| "multidict==6.7.1", | ||
| "mypy==1.19.1", | ||
| "mypy-extensions==1.1.0", | ||
| "openai==2.26.0", | ||
| "packaging==26.0", | ||
| "passlib==1.7.4", | ||
| "pathspec==1.0.4", | ||
| "platformdirs==4.9.4", | ||
| "pluggy==1.6.0", | ||
| "propcache==0.4.1", | ||
| "psycopg2-binary==2.9.11", | ||
| "pyasn1==0.6.2", | ||
| "pycparser==3.0", | ||
| "pydantic==2.12.5", | ||
| "pydantic-core==2.41.5", | ||
| "pydantic-extra-types==2.11.0", | ||
| "pydantic-settings==2.13.1", | ||
| "pygments==2.19.2", | ||
| "pytest==9.0.2", | ||
| "pytest-asyncio==1.3.0", | ||
| "pytest-cov==7.0.0", | ||
| "python-dateutil==2.9.0.post0", | ||
| "python-dotenv==1.2.2", | ||
| "python-jose==3.5.0", | ||
| "python-multipart==0.0.22", | ||
| "pytokens==0.4.1", | ||
| "pyyaml==6.0.3", | ||
| "redis==7.4.0", | ||
| "requests==2.32.5", | ||
| "resend==2.23.0", | ||
| "rich==14.3.3", | ||
| "rich-toolkit==0.19.7", | ||
| "rignore==0.7.6", | ||
| "rsa==4.9.1", | ||
| "ruff==0.15.6", | ||
| "s3transfer==0.14.0", | ||
| "sentry-sdk==2.54.0", | ||
| "shellingham==1.5.4", | ||
| "six==1.17.0", | ||
| "slowapi==0.1.9", | ||
| "sniffio==1.3.1", | ||
| "sqlalchemy==2.0.48", | ||
| "starlette==0.52.1", | ||
| "tqdm==4.67.3", | ||
| "typer==0.24.1", | ||
| "typing-extensions==4.15.0", | ||
| "typing-inspection==0.4.2", | ||
| "urllib3==2.6.3", | ||
| "uvicorn==0.41.0", | ||
| "watchfiles==1.1.1", | ||
| "websockets==16.0", | ||
| "wrapt==1.17.3", | ||
| "yarl==1.23.0", | ||
| ] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Dev/test tools currently in [project].dependencies:"
rg -n '^\s*"(black|coverage|isort|mypy|pytest|pytest-asyncio|pytest-cov|ruff)==.*"' pyproject.toml
echo "Check whether a dedicated dev group exists:"
rg -n '^\[(dependency-groups|project\.optional-dependencies)\]' pyproject.toml || trueRepository: Brints/spoken-api
Length of output: 361
Split dev/test tooling out of [project].dependencies.
pyproject.toml currently includes black, coverage, isort, mypy, pytest, pytest-asyncio, pytest-cov, and ruff in [project].dependencies, and there’s no [dependency-groups] / [project.optional-dependencies] section to exclude them. Move these into a dev dependency group (PEP 735) so production installs don’t pull QA/test tools.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyproject.toml` around lines 5 - 117, The pyproject currently lists QA/dev
tools (black, coverage, isort, mypy, pytest, pytest-asyncio, pytest-cov, ruff)
inside [project].dependencies; remove those entries from the dependencies array
and add them under a dedicated dev dependency group (e.g. using PEP 735
[project.dependency-groups] or PEP 621 [project.optional-dependencies] with a
"dev" key) so production installs won't pull these packages; ensure you
reference the same package names (black, coverage, isort, mypy, pytest,
pytest-asyncio, pytest-cov, ruff) when creating the new dev group and keep all
other entries in the original dependencies list unchanged.
| ### 4. Local Development Setup | ||
| It is highly recommended to use the `uv` tool for fast virtual environment and dependency management: | ||
| ```bash | ||
| python -m venv .venv | ||
| source .venv/bin/activate # On Windows: .venv\Scripts\activate | ||
| pip install -r requirements.txt | ||
| uv sync | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show README.md around the reported lines
echo "---- README.md: lines 70-120 ----"
nl -ba README.md | sed -n '70,120p'
# Find all mentions of uv
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true
# Look for any install/setup instructions for uv
echo "---- rg install/setup keywords for uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).*uv|uv.*(install|setup|prerequisite)|curl.*uv|brew.*uv|pipx.*uv" README.md || trueRepository: Brints/spoken-api
Length of output: 156
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- README.md: lines 70-120 ----"
awk 'NR>=70 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true
echo "---- rg install/setup keywords around uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).{0,40}uv|uv.{0,40}(install|setup|prerequisite)|curl.{0,40}uv|brew.{0,40}uv|pipx.{0,40}uv" README.md || trueRepository: Brints/spoken-api
Length of output: 2144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- README.md: lines 70-120 ----"
awk 'NR>=70 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true
echo "---- rg install/setup keywords around uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).{0,40}uv|uv.{0,40}(install|setup|prerequisite)|curl.{0,40}uv|brew.{0,40}uv|pipx.{0,40}uv" README.md || trueRepository: Brints/spoken-api
Length of output: 2144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- README.md: lines 1-120 ----"
awk 'NR>=1 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- README.md: lines 120-210 ----"
awk 'NR>=120 && NR<=210 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- README.md: lines 210-260 ----"
awk 'NR>=210 && NR<=260 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- rg 'uv' in README.md (full match context) ----"
rg -n --context=2 "\buv\b" README.md || trueRepository: Brints/spoken-api
Length of output: 9579
Add an explicit “Install uv” step before running uv sync in the Local Development Setup.
The README recommends uv sync but doesn’t include any prerequisite/install instructions for uv (even though other steps rely on uv run ...). Add a short install step (or link) so first-time users don’t fail on setup.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 86 - 90, Add an explicit install step for the "uv"
tool before the existing "uv sync" instruction: update the "Local Development
Setup" section to include a short prerequisite line such as "Install uv (see
https://github.com/uvtool/uv or run your package manager install command)" and
ensure subsequent commands that use uv (e.g., "uv sync" and "uv run ...") follow
that new step so first-time users have the required tool installed.
| uv run uvicorn app.main:app --reload | ||
| uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload |
There was a problem hiding this comment.
Remove duplicate backend start command.
Line 110 and Line 111 document two startup commands for the same purpose; keep one canonical command to avoid confusion.
Doc cleanup
-uv run uvicorn app.main:app --reload
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uv run uvicorn app.main:app --reload | |
| uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload | |
| uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 110 - 111, Remove the duplicate backend startup
command in the README by keeping only the canonical uvicorn invocation that
includes host and port; delete the redundant "uv run uvicorn app.main:app
--reload" line and retain "uv run uvicorn app.main:app --host 0.0.0.0 --port
8000 --reload" so there's a single clear command shown.
…toml - Merge remote branch 'origin/main' into fix/audio_transcribe_pipeline. - Remove duplicate `version` field from the bottom of the `[project.dependencies]` block in `pyproject.toml` (introduced by Git's 3-way merge alignment) to prevent setup-python workflow parser crashes. - Maintain version "1.18.8" at the top of the project metadata. Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
Update [tool.isort] configuration in pyproject.toml to ignore virtual environment directories, preventing CI/CD quality check failures from scanning third-party site-packages. Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
Summary by CodeRabbit
Chores
uvacross CI/CD pipelines, Docker builds, and development workflows.uvfor Python tooling and dependency resolution.Documentation
uvfor consistent dependency and developer tooling management.