Skip to content

chore: update python tooling#4426

Open
cemreinanc wants to merge 8 commits intomainfrom
chore/update-python-tooling
Open

chore: update python tooling#4426
cemreinanc wants to merge 8 commits intomainfrom
chore/update-python-tooling

Conversation

@cemreinanc
Copy link
Contributor

@cemreinanc cemreinanc commented Feb 25, 2026

CI Backend Checks Setup Phase:

Before After
CleanShot 2026-02-25 at 15 48 40@2x CleanShot 2026-02-25 at 15 49 13@2x
~60 sec. ~11 sec.

closes #4097

Summary by CodeRabbit

  • Refactor

    • Migrated project build system from Poetry to UV package manager, updating all CI/CD workflows and container configurations.
    • Replaced code linting and formatting tools with Ruff, updating development workflows and documentation accordingly.
  • Documentation

    • Updated setup and deployment instructions to reflect new development tooling.
  • Style

    • Normalized code formatting across the project with trailing commas and line wrapping adjustments.
    • Removed unnecessary blank lines for consistency.

cemreinanc and others added 6 commits February 25, 2026 15:29
Convert both pyproject.toml files from Poetry to PEP 621 format,
add Ruff config replacing Black + Flake8, delete .flake8, and swap
poetry.lock files for uv.lock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automated formatting pass with `ruff format .` to establish
Ruff as the project formatter (replacing Black).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Poetry with UV in the main Dockerfile backend_deps stage.
Simplify screenshot Dockerfile by removing pyenv and build-essential
dependencies — UV handles Python version management directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Poetry with UV in unit_tests.yml and integration_tests.yml
workflows, using astral-sh/setup-uv@v5 with built-in caching.
Replace all poetry run with uv run in shell scripts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace pyenv/Poetry setup instructions with UV, and update all
poetry run commands to uv run throughout the documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

The PR migrates the project from Poetry to UV for Python dependency management and from Black/Flake8 to Ruff for linting and formatting. Configuration files, CI/CD workflows, Dockerfiles, documentation, and scripts are updated accordingly, along with numerous minor formatting adjustments throughout the codebase.

Changes

Cohort / File(s) Summary
Build System & Package Configuration
pyproject.toml, screenshot/pyproject.toml
Migrates from Poetry-specific [tool.poetry] format to PEP 621 [project] layout with consolidated dependencies and new Ruff linting configuration under [tool.ruff].
Dependency Management Configuration
.flake8
Removes flake8 configuration block entirely, replacing with Ruff-based linting approach.
Docker Build Files
Dockerfile, screenshot/Dockerfile
Replaces Poetry venv setup with UV toolchain; updates dependency installation from poetry install to uv sync --frozen and configures UV environment variables.
CI/CD Workflows
.github/workflows/unit_tests.yml, .github/workflows/integration_tests.yml
Replaces Poetry-based Python setup with astral-sh/setup-uv action; updates all dependency and tooling commands (Black/Flake8) to Ruff equivalents via uv run.
Startup & Script Files
screenshot/start.sh, scripts/create_minimal_db.sh, scripts/tests/run_integration_tests.sh
Updates command execution from poetry run to uv run for Django management commands and service startup.
Application Code - Formatting
manage.py, metaculus_web/settings.py, comments/management/commands/update_top_comments_of_week_batch.py, fab_management/..., posts/admin.py, questions/models.py, scoring/..., users/services/bots_management.py, utils/...
Minor formatting adjustments: consolidated string literals, trailing commas, quote style fixes, and line wrapping for readability with no functional changes.
Test Code - Formatting
tests/unit/test_coherence/factories.py, tests/unit/test_comments/factories.py, tests/unit/test_projects/factories.py, tests/unit/test_questions/factories.py, tests/unit/test_scoring/factories.py, tests/unit/test_users/factories.py, tests/unit/.../test_*.py
Adds trailing commas to multi-line function signatures and call arguments; removes blank lines within class/method definitions; all formatting changes only.
Documentation
README.md, Claude.md
Updates Python task runner references from Poetry to UV in setup, migration, and test execution instructions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • elisescu

Poem

🐰 From Poetry's verse to UV's swift flight,
We trade the old ballads for speeds taking flight!
With Ruff now our guide through the linting domain,
Our builds run much faster—no more slow refrain.
Hoppy migrations, cleaner code lines divine!

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Extensive formatting changes (trailing commas, line wrapping, blank line removals) throughout test files and application code appear to be incidental to the primary tooling migration objective. Separate formatting-only changes (trailing commas, whitespace adjustments in factories/tests) from the core tooling migration to improve clarity and review focus. These formatting changes should be in a dedicated commit or pull request.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: update python tooling' accurately describes the main objective of replacing Poetry/Black/Flake8 with uv/Ruff, which is the central theme across all changes in the pull request.
Linked Issues check ✅ Passed All coding requirements from issue #4097 are met: Poetry replaced with uv across workflows, pyproject.toml migrated to PEP 621 format with Ruff config, .flake8 removed, Docker images updated, CI workflows modified, and documentation updated to reflect the new tooling.

✏️ 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 chore/update-python-tooling

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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4426-chore-update-python-tooling-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:chore-update-python-tooling-6dd29bd
🗄️ PostgreSQL NeonDB branch preview/pr-4426-chore-update-python-tooling
Redis Fly Redis mtc-redis-pr-4426-chore-update-python-tooling

Details

  • Commit: 6dd29bda8390d8fbba7658cfa33479e15e247d73
  • Branch: chore/update-python-tooling
  • Fly App: metaculus-pr-4426-chore-update-python-tooling

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
manage.py (1)

10-14: ⚠️ Potential issue | 🟡 Minor

Misplaced docstring becomes dead code.

The docstring on line 13 appears after load_dotenv(), so Python treats it as a string literal expression (no-op) rather than the function's docstring. Move it before any executable code to be recognized as main.__doc__.

Proposed fix
 def main():
+    """Run administrative tasks."""
     load_dotenv()
-
-    """Run administrative tasks."""
     os.environ.setdefault("DJANGO_SETTINGS_MODULE", "metaculus_web.settings")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@manage.py` around lines 10 - 14, The docstring for main() is placed after
executable code (load_dotenv()) so it is treated as a no-op string instead of
main.__doc__; move the triple-quoted docstring so it appears immediately after
the def main(): line (before calling load_dotenv()) so Python recognizes it as
the function docstring; update the main() function (which contains load_dotenv()
and os.environ.setdefault("DJANGO_SETTINGS_MODULE", "metaculus_web.settings"))
accordingly.
questions/views.py (1)

159-172: ⚠️ Potential issue | 🟡 Minor

Bug: Error message will always show None instead of the actual question ID.

The validation at line 171-172 runs after withdrawal["question"] is overwritten with the (possibly None) question object at line 161. When question is None, the error message prints "Wrong question id None" instead of the original invalid ID.

Compare with bulk_create_forecasts_api_view where the check happens before the overwrite (lines 109-112).

Proposed fix: validate before overwriting
     for withdrawal in validated_data:
         question = questions_map.get(withdrawal["question"])
-        withdrawal["question"] = question  # used in withdraw_foreacst_bulk
         withdraw_at = withdrawal.get("withdraw_at", now)
         withdrawal["withdraw_at"] = withdraw_at  # used in withdraw_foreacst_bulk

         if now > withdraw_at:
             return Response(
                 {"error": f"Withdrawal time {withdraw_at} cannot be in the past"},
                 status=status.HTTP_405_METHOD_NOT_ALLOWED,
             )

         if not question:
             raise ValidationError(f"Wrong question id {withdrawal['question']}")

+        withdrawal["question"] = question  # used in withdraw_foreacst_bulk
+
         # Check permissions
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@questions/views.py` around lines 159 - 172, The code overwrites
withdrawal["question"] with the question object before validating existence, so
the ValidationError prints None instead of the original id; change the order in
the loop in questions/views.py to first check question =
questions_map.get(withdrawal["question"]) and if not question raise
ValidationError using the original withdrawal["question"] value, then set
withdrawal["question"] = question (so withdraw_foreacst_bulk sees the object);
keep the withdraw_at logic and the now > withdraw_at check as-is but ensure
validation happens before the overwrite.
🧹 Nitpick comments (5)
screenshot/Dockerfile (2)

26-27: Consider adding --no-dev to exclude dev dependencies.

Unlike the main Dockerfile which uses uv sync --frozen --no-dev, this uses uv sync --frozen which will include dev dependencies (e.g., ruff). For a production screenshot service, dev dependencies are likely unnecessary and would increase image size.

Proposed fix
-RUN uv sync --frozen
+RUN uv sync --frozen --no-dev
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@screenshot/Dockerfile` around lines 26 - 27, Change the uv sync invocation to
exclude dev dependencies: replace the RUN command that calls "uv sync --frozen"
with a call including "--no-dev" (i.e., "uv sync --frozen --no-dev") so dev
packages like ruff are not installed in the production screenshot image; keep
the subsequent "RUN uv run playwright install chromium --with-deps" as-is to
ensure browser deps are installed.

8-8: Pin the UV image version for reproducibility.

Same recommendation as the main Dockerfile: using :latest may cause inconsistent builds. Consider pinning to a specific version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@screenshot/Dockerfile` at line 8, The Dockerfile uses the image reference
ghcr.io/astral-sh/uv:latest in the COPY --from=ghcr.io/astral-sh/uv:latest /uv
/uvx /usr/local/bin/ instruction; pin that image to a specific semantic version
tag or immutable digest (e.g., replace :latest with a concrete tag or
`@sha256`:...) so builds are reproducible and update the COPY --from reference
accordingly.
pyproject.toml (1)

92-93: Consider documenting the high McCabe complexity threshold.

max-complexity = 56 is significantly higher than the default of 10. While this is likely intentional to avoid breaking existing code during the tooling migration, consider adding a comment explaining this choice or creating a follow-up task to gradually reduce it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 92 - 93, The McCabe threshold under
[tool.ruff.lint.mccabe] is set to an unusually high value (max-complexity = 56);
add a brief inline comment next to this setting explaining why 56 was chosen
(e.g., to ease migration) and/or add a TODO with a reference to a new follow-up
task/issue to gradually lower the threshold to a safer default, ensuring the
comment references "max-complexity" and the "[tool.ruff.lint.mccabe]" section so
reviewers can find and track the rationale.
scripts/tests/run_integration_tests.sh (1)

20-22: Minor: Extra space in command.

Line 20 has a double space: uv run ./manage.py. This doesn't affect functionality but is inconsistent with the other commands.

Proposed fix
-uv run  ./manage.py collectstatic --noinput
+uv run ./manage.py collectstatic --noinput
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/tests/run_integration_tests.sh` around lines 20 - 22, Remove the
extra double space in the collectstatic command so it matches the other
commands' formatting: change the invocation in the script where the line uses
"uv run  ./manage.py collectstatic --noinput" to use a single space after "run"
(i.e., "uv run ./manage.py collectstatic --noinput") to ensure consistent
spacing with the other commands like the gunicorn and rundramatiq lines.
README.md (1)

102-115: Consider adding migration guidance for existing Poetry users.

The UV setup instructions are technically correct and will work well for new contributors. However, existing developers migrating from Poetry might benefit from additional context:

  • Cleanup steps (removing .venv, clearing Poetry cache)
  • Brief note that this replaces Poetry
  • Link to UV documentation for reference
📝 Suggested enhancement for existing users

Consider adding a note after line 103:

 ## UV and Python & Dependencies
 You'll need to install python version 3.12.3 (or higher), and we use [uv](https://docs.astral.sh/uv/) for dependency and Python version management.
+
+> **Note for existing contributors:** If you previously used Poetry, you can remove your old `.venv` directory before running `uv sync`.
 
 Install uv:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 102 - 115, Add migration guidance for existing Poetry
users in the "UV and Python & Dependencies" section: explain that uv replaces
Poetry, recommend cleanup steps (remove project .venv, optional pipx/virtualenv
remnants, and clear Poetry cache via `poetry cache clear --all`), advise
reinstalling deps with `uv sync`, and include a short pointer/link to the UV
docs for more details; place this note directly after the uv installation
instructions so migrating devs see it next to the `uv sync` step.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile`:
- Around line 44-47: The Dockerfile is copying artifacts from the UV builder
image using the floating tag ghcr.io/astral-sh/uv:latest which makes builds
non-reproducible; change the COPY --from reference in the lines that use
ghcr.io/astral-sh/uv:latest to a specific stable tag (for example
ghcr.io/astral-sh/uv:0.10.6) so COPY --from=... /uv /uvx /usr/local/bin/ pulls
from a pinned UV image, and update any related build docs or CI that rely on the
UV tag to the same pinned version.

In `@README.md`:
- Line 103: Update the README sentence that currently says "python version
3.12.3 (or higher)" to match the constraint in pyproject.toml (requires-python =
">=3.12,<3.13"); specifically replace the phrase with either "Python 3.12.x" or
"Python 3.12 (>=3.12, <3.13)" so the README and the pyproject.toml's
requires-python declaration are consistent.

---

Outside diff comments:
In `@manage.py`:
- Around line 10-14: The docstring for main() is placed after executable code
(load_dotenv()) so it is treated as a no-op string instead of main.__doc__; move
the triple-quoted docstring so it appears immediately after the def main(): line
(before calling load_dotenv()) so Python recognizes it as the function
docstring; update the main() function (which contains load_dotenv() and
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "metaculus_web.settings"))
accordingly.

In `@questions/views.py`:
- Around line 159-172: The code overwrites withdrawal["question"] with the
question object before validating existence, so the ValidationError prints None
instead of the original id; change the order in the loop in questions/views.py
to first check question = questions_map.get(withdrawal["question"]) and if not
question raise ValidationError using the original withdrawal["question"] value,
then set withdrawal["question"] = question (so withdraw_foreacst_bulk sees the
object); keep the withdraw_at logic and the now > withdraw_at check as-is but
ensure validation happens before the overwrite.

---

Nitpick comments:
In `@pyproject.toml`:
- Around line 92-93: The McCabe threshold under [tool.ruff.lint.mccabe] is set
to an unusually high value (max-complexity = 56); add a brief inline comment
next to this setting explaining why 56 was chosen (e.g., to ease migration)
and/or add a TODO with a reference to a new follow-up task/issue to gradually
lower the threshold to a safer default, ensuring the comment references
"max-complexity" and the "[tool.ruff.lint.mccabe]" section so reviewers can find
and track the rationale.

In `@README.md`:
- Around line 102-115: Add migration guidance for existing Poetry users in the
"UV and Python & Dependencies" section: explain that uv replaces Poetry,
recommend cleanup steps (remove project .venv, optional pipx/virtualenv
remnants, and clear Poetry cache via `poetry cache clear --all`), advise
reinstalling deps with `uv sync`, and include a short pointer/link to the UV
docs for more details; place this note directly after the uv installation
instructions so migrating devs see it next to the `uv sync` step.

In `@screenshot/Dockerfile`:
- Around line 26-27: Change the uv sync invocation to exclude dev dependencies:
replace the RUN command that calls "uv sync --frozen" with a call including
"--no-dev" (i.e., "uv sync --frozen --no-dev") so dev packages like ruff are not
installed in the production screenshot image; keep the subsequent "RUN uv run
playwright install chromium --with-deps" as-is to ensure browser deps are
installed.
- Line 8: The Dockerfile uses the image reference ghcr.io/astral-sh/uv:latest in
the COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
instruction; pin that image to a specific semantic version tag or immutable
digest (e.g., replace :latest with a concrete tag or `@sha256`:...) so builds are
reproducible and update the COPY --from reference accordingly.

In `@scripts/tests/run_integration_tests.sh`:
- Around line 20-22: Remove the extra double space in the collectstatic command
so it matches the other commands' formatting: change the invocation in the
script where the line uses "uv run  ./manage.py collectstatic --noinput" to use
a single space after "run" (i.e., "uv run ./manage.py collectstatic --noinput")
to ensure consistent spacing with the other commands like the gunicorn and
rundramatiq lines.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8ac136 and a46bb98.

⛔ Files ignored due to path filters (4)
  • poetry.lock is excluded by !**/*.lock
  • screenshot/poetry.lock is excluded by !**/*.lock
  • screenshot/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (45)
  • .flake8
  • .github/workflows/integration_tests.yml
  • .github/workflows/unit_tests.yml
  • Claude.md
  • Dockerfile
  • README.md
  • comments/management/commands/update_top_comments_of_week_batch.py
  • fab_management/utils.py
  • fab_management/views.py
  • manage.py
  • metaculus_web/settings.py
  • misc/services/itn.py
  • posts/admin.py
  • projects/views/communities.py
  • pyproject.toml
  • questions/management/commands/build_forecasts.py
  • questions/models.py
  • questions/serializers/common.py
  • questions/tasks.py
  • questions/views.py
  • scoring/jobs.py
  • scoring/management/commands/update_global_bot_leaderboard.py
  • screenshot/Dockerfile
  • screenshot/pyproject.toml
  • screenshot/start.sh
  • scripts/create_minimal_db.sh
  • scripts/tests/run_integration_tests.sh
  • tests/unit/test_coherence/factories.py
  • tests/unit/test_comments/factories.py
  • tests/unit/test_notifications/factories.py
  • tests/unit/test_posts/factories.py
  • tests/unit/test_projects/factories.py
  • tests/unit/test_questions/factories.py
  • tests/unit/test_questions/test_views.py
  • tests/unit/test_scoring/factories.py
  • tests/unit/test_scoring/test_score_math.py
  • tests/unit/test_scoring/test_utils.py
  • tests/unit/test_users/factories.py
  • tests/unit/test_users/test_views.py
  • tests/unit/test_utils/test_the_math/test_aggregations.py
  • tests/unit/test_utils/test_the_math/test_formulas.py
  • users/services/bots_management.py
  • utils/models.py
  • utils/tasks.py
  • utils/the_math/aggregations.py
💤 Files with no reviewable changes (8)
  • tests/unit/test_utils/test_the_math/test_aggregations.py
  • .flake8
  • tests/unit/test_utils/test_the_math/test_formulas.py
  • tests/unit/test_scoring/test_utils.py
  • tests/unit/test_questions/test_views.py
  • questions/management/commands/build_forecasts.py
  • fab_management/views.py
  • utils/the_math/aggregations.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
questions/views.py (1)

159-172: ⚠️ Potential issue | 🟠 Major

Fix logic bug: question validation happens after reassignment.

Pre-existing issue (not introduced by this PR): The check if not question: on line 171 occurs after withdrawal["question"] = question on line 161. When question is None, the error message at line 172 displays "Wrong question id None" instead of the actual invalid ID.

Compare with bulk_create_forecasts_api_view (lines 106-112), where the validation happens before reassignment.

🐛 Proposed fix to check before reassignment
 for withdrawal in validated_data:
     question = questions_map.get(withdrawal["question"])
+
+    if not question:
+        raise ValidationError(f"Wrong question id {withdrawal['question']}")
+
     withdrawal["question"] = question  # used in withdraw_foreacst_bulk
     withdraw_at = withdrawal.get("withdraw_at", now)
     withdrawal["withdraw_at"] = withdraw_at  # used in withdraw_foreacst_bulk

     if now > withdraw_at:
         return Response(
             {"error": f"Withdrawal time {withdraw_at} cannot be in the past"},
             status=status.HTTP_405_METHOD_NOT_ALLOWED,
         )

-    if not question:
-        raise ValidationError(f"Wrong question id {withdrawal['question']}")
-
     # Check permissions
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@questions/views.py` around lines 159 - 172, The validation for question
existence is done after overwriting withdrawal["question"] with the resolved
object, causing the error message to show "None" instead of the original id;
move the check "if not question:" to before "withdrawal['question'] = question"
and raise ValidationError using the original id (e.g., use the pre-reassignment
withdrawal["question"] value) so the message shows the invalid id; keep the
withdraw_at handling and the now > withdraw_at Response logic unchanged and
ensure withdraw_foreacst_bulk still receives the resolved question object.
♻️ Duplicate comments (2)
README.md (1)

103-103: ⚠️ Potential issue | 🟡 Minor

Update README to clarify Python version constraints.

Line 103 states "python version 3.12.3 (or higher)", but pyproject.toml specifies requires-python = ">=3.12,<3.13". The "(or higher)" phrasing is misleading since versions 3.13+ are not supported. Consider updating to "Python 3.12.x" for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 103, Update the README phrasing to match the
pyproject.toml Python constraint: replace "python version 3.12.3 (or higher)"
with a clear statement like "Python 3.12.x (>=3.12,<3.13)" or "Python
>=3.12,<3.13" so it reflects the requires-python = ">=3.12,<3.13" setting in
pyproject.toml and removes the misleading "or higher" wording.
Dockerfile (1)

44-47: ⚠️ Potential issue | 🟡 Minor

Pin the UV image version to ensure build reproducibility.

Using ghcr.io/astral-sh/uv:latest risks inconsistent builds as UV releases frequently. Pin to a specific stable version (e.g., ghcr.io/astral-sh/uv:0.6.6 or the version used locally) for reproducible builds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 44 - 47, Replace the floating image tag in the
Dockerfile COPY line that references ghcr.io/astral-sh/uv:latest with an
explicit, pinned UV release (for example ghcr.io/astral-sh/uv:0.6.6) to
guarantee reproducible builds; update the COPY
--from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ line to use the
chosen version and search for any other occurrences of
ghcr.io/astral-sh/uv:latest in the Dockerfile to change them as well.
🧹 Nitpick comments (4)
screenshot/pyproject.toml (1)

8-8: Tight version constraint for FastAPI.

fastapi>=0.109.1,<0.110 restricts to only the 0.109.x patch releases. If this is intentional for stability, it's fine, but it may prevent receiving security fixes in newer minor versions. Consider relaxing to fastapi>=0.109.1,<1 unless there's a known incompatibility with newer versions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@screenshot/pyproject.toml` at line 8, The FastAPI dependency in
pyproject.toml is pinned too narrowly as "fastapi>=0.109.1,<0.110"; relax the
constraint to allow newer minor/major releases (for example change to
"fastapi>=0.109.1,<1") unless there is a known incompatibility, so update the
version spec for the FastAPI dependency line accordingly to permit receiving
security/feature fixes while still bounding breaking changes.
pyproject.toml (2)

92-93: Unusually high cyclomatic complexity threshold.

max-complexity = 56 is significantly higher than typical values (10-15). This effectively disables complexity warnings for most code. Was this value intentionally preserved from the previous Flake8 configuration, or should it be lowered to enforce better code quality going forward?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 92 - 93, The mccabe cyclomatic complexity
threshold is set too high; update the [tool.ruff.lint.mccabe] section to lower
max-complexity (e.g., 10–15, commonly 12) to re-enable useful complexity
warnings. Locate the max-complexity setting under the [tool.ruff.lint.mccabe]
table and change the value from 56 to a sensible target (suggest 12) so Ruff
enforces reasonable complexity limits going forward.

67-67: Consider adding an upper bound for ruff.

Unlike other dependencies in this file, ruff>=0.11.0 lacks an upper bound. Ruff releases frequently and occasionally introduces breaking lint rule changes. Adding an upper bound (e.g., ruff>=0.11.0,<0.12) would provide more predictable CI behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` at line 67, The dependency declaration for ruff
("ruff>=0.11.0") has no upper bound; update the requirement in pyproject.toml to
include a conservative upper bound (for example change "ruff>=0.11.0" to
"ruff>=0.11.0,<0.12") so frequent ruff releases won't unexpectedly break CI;
locate the ruff entry in the dependencies list and add the chosen "<0.12" upper
constraint.
screenshot/Dockerfile (1)

26-26: Consider using --no-dev for production builds.

uv sync --frozen will install all dependencies including dev dependencies. For a production Docker image, adding --no-dev would reduce image size and attack surface by excluding development tools.

Proposed fix
-RUN uv sync --frozen
+RUN uv sync --frozen --no-dev
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@screenshot/Dockerfile` at line 26, The Dockerfile RUN step currently uses the
command "uv sync --frozen" which installs dev dependencies; update the RUN
instruction that invokes uv sync to include the production flag (add "--no-dev")
so it runs "uv sync --frozen --no-dev" (or equivalent uv production install
flag) to avoid installing dev dependencies and shrink the production image.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@screenshot/Dockerfile`:
- Line 8: The Dockerfile uses an unpinned image reference in the COPY
instruction ("COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx
/usr/local/bin/"), which makes builds non-reproducible; change the image tag to
a specific, stable version (e.g., ghcr.io/astral-sh/uv:0.10.6) so the COPY
--from source is pinned and the build is reproducible.

---

Outside diff comments:
In `@questions/views.py`:
- Around line 159-172: The validation for question existence is done after
overwriting withdrawal["question"] with the resolved object, causing the error
message to show "None" instead of the original id; move the check "if not
question:" to before "withdrawal['question'] = question" and raise
ValidationError using the original id (e.g., use the pre-reassignment
withdrawal["question"] value) so the message shows the invalid id; keep the
withdraw_at handling and the now > withdraw_at Response logic unchanged and
ensure withdraw_foreacst_bulk still receives the resolved question object.

---

Duplicate comments:
In `@Dockerfile`:
- Around line 44-47: Replace the floating image tag in the Dockerfile COPY line
that references ghcr.io/astral-sh/uv:latest with an explicit, pinned UV release
(for example ghcr.io/astral-sh/uv:0.6.6) to guarantee reproducible builds;
update the COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ line
to use the chosen version and search for any other occurrences of
ghcr.io/astral-sh/uv:latest in the Dockerfile to change them as well.

In `@README.md`:
- Line 103: Update the README phrasing to match the pyproject.toml Python
constraint: replace "python version 3.12.3 (or higher)" with a clear statement
like "Python 3.12.x (>=3.12,<3.13)" or "Python >=3.12,<3.13" so it reflects the
requires-python = ">=3.12,<3.13" setting in pyproject.toml and removes the
misleading "or higher" wording.

---

Nitpick comments:
In `@pyproject.toml`:
- Around line 92-93: The mccabe cyclomatic complexity threshold is set too high;
update the [tool.ruff.lint.mccabe] section to lower max-complexity (e.g., 10–15,
commonly 12) to re-enable useful complexity warnings. Locate the max-complexity
setting under the [tool.ruff.lint.mccabe] table and change the value from 56 to
a sensible target (suggest 12) so Ruff enforces reasonable complexity limits
going forward.
- Line 67: The dependency declaration for ruff ("ruff>=0.11.0") has no upper
bound; update the requirement in pyproject.toml to include a conservative upper
bound (for example change "ruff>=0.11.0" to "ruff>=0.11.0,<0.12") so frequent
ruff releases won't unexpectedly break CI; locate the ruff entry in the
dependencies list and add the chosen "<0.12" upper constraint.

In `@screenshot/Dockerfile`:
- Line 26: The Dockerfile RUN step currently uses the command "uv sync --frozen"
which installs dev dependencies; update the RUN instruction that invokes uv sync
to include the production flag (add "--no-dev") so it runs "uv sync --frozen
--no-dev" (or equivalent uv production install flag) to avoid installing dev
dependencies and shrink the production image.

In `@screenshot/pyproject.toml`:
- Line 8: The FastAPI dependency in pyproject.toml is pinned too narrowly as
"fastapi>=0.109.1,<0.110"; relax the constraint to allow newer minor/major
releases (for example change to "fastapi>=0.109.1,<1") unless there is a known
incompatibility, so update the version spec for the FastAPI dependency line
accordingly to permit receiving security/feature fixes while still bounding
breaking changes.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8ac136 and a46bb98.

⛔ Files ignored due to path filters (4)
  • poetry.lock is excluded by !**/*.lock
  • screenshot/poetry.lock is excluded by !**/*.lock
  • screenshot/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (45)
  • .flake8
  • .github/workflows/integration_tests.yml
  • .github/workflows/unit_tests.yml
  • Claude.md
  • Dockerfile
  • README.md
  • comments/management/commands/update_top_comments_of_week_batch.py
  • fab_management/utils.py
  • fab_management/views.py
  • manage.py
  • metaculus_web/settings.py
  • misc/services/itn.py
  • posts/admin.py
  • projects/views/communities.py
  • pyproject.toml
  • questions/management/commands/build_forecasts.py
  • questions/models.py
  • questions/serializers/common.py
  • questions/tasks.py
  • questions/views.py
  • scoring/jobs.py
  • scoring/management/commands/update_global_bot_leaderboard.py
  • screenshot/Dockerfile
  • screenshot/pyproject.toml
  • screenshot/start.sh
  • scripts/create_minimal_db.sh
  • scripts/tests/run_integration_tests.sh
  • tests/unit/test_coherence/factories.py
  • tests/unit/test_comments/factories.py
  • tests/unit/test_notifications/factories.py
  • tests/unit/test_posts/factories.py
  • tests/unit/test_projects/factories.py
  • tests/unit/test_questions/factories.py
  • tests/unit/test_questions/test_views.py
  • tests/unit/test_scoring/factories.py
  • tests/unit/test_scoring/test_score_math.py
  • tests/unit/test_scoring/test_utils.py
  • tests/unit/test_users/factories.py
  • tests/unit/test_users/test_views.py
  • tests/unit/test_utils/test_the_math/test_aggregations.py
  • tests/unit/test_utils/test_the_math/test_formulas.py
  • users/services/bots_management.py
  • utils/models.py
  • utils/tasks.py
  • utils/the_math/aggregations.py
💤 Files with no reviewable changes (8)
  • tests/unit/test_utils/test_the_math/test_formulas.py
  • .flake8
  • utils/the_math/aggregations.py
  • fab_management/views.py
  • tests/unit/test_utils/test_the_math/test_aggregations.py
  • tests/unit/test_questions/test_views.py
  • questions/management/commands/build_forecasts.py
  • tests/unit/test_scoring/test_utils.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
questions/views.py (1)

159-173: ⚠️ Potential issue | 🟡 Minor

Preserve the original invalid question ID in the error message.

At Line 161, withdrawal["question"] is overwritten before validation, so Line 172 can emit None instead of the submitted ID.

💡 Suggested fix
     # Replacing prefetched optimized questions
     for withdrawal in validated_data:
-        question = questions_map.get(withdrawal["question"])
-        withdrawal["question"] = question  # used in withdraw_foreacst_bulk
+        question_id = withdrawal["question"]
+        question = questions_map.get(question_id)
         withdraw_at = withdrawal.get("withdraw_at", now)
         withdrawal["withdraw_at"] = withdraw_at  # used in withdraw_foreacst_bulk

         if now > withdraw_at:
             return Response(
                 {"error": f"Withdrawal time {withdraw_at} cannot be in the past"},
                 status=status.HTTP_405_METHOD_NOT_ALLOWED,
             )

         if not question:
-            raise ValidationError(f"Wrong question id {withdrawal['question']}")
+            raise ValidationError(f"Wrong question id {question_id}")
+        withdrawal["question"] = question  # used in withdraw_foreacst_bulk
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@questions/views.py` around lines 159 - 173, The loop overwrites
withdrawal["question"] with the resolved Question object before validating
existence, so the ValidationError may report None; before assigning
withdrawal["question"] = question in the loop over validated_data (and before
using withdraw_at), save the original id (e.g., orig_qid =
withdrawal.get("question")) and use that original id in the ValidationError
message (referencing validated_data, questions_map, withdrawal, and the
withdraw_foreacst_bulk usage) so the error shows the submitted ID instead of the
replaced value.
♻️ Duplicate comments (1)
Dockerfile (1)

44-44: ⚠️ Potential issue | 🟡 Minor

Pin UV image to an immutable reference.

Line 44 still uses a floating minor tag (ghcr.io/astral-sh/uv:0.10), which can change over time and break reproducibility. Prefer a patch tag plus digest.

🔧 Suggested update
+ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.10.6
-COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /usr/local/bin/
+COPY --from=${UV_IMAGE} /uv /uvx /usr/local/bin/
Is ghcr.io/astral-sh/uv:0.10 a moving tag, and what is the recommended way to pin uv images for reproducible Docker builds (specific version tag vs digest)?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 44, The Dockerfile uses a floating minor tag in the build
stage COPY (--from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /usr/local/bin/) which
harms reproducibility; update the image reference to an immutable value by
replacing ghcr.io/astral-sh/uv:0.10 with a specific patch tag or, preferably,
the image digest (sha256:...) so the COPY --from= reference points to a fixed
immutable image; locate the COPY --from line and substitute the tag with the
recommended patch-level tag or the full `@sha256` digest.
🧹 Nitpick comments (1)
.github/workflows/integration_tests.yml (1)

55-57: Pin setup-uv to an exact patch version.

The version: "0.10.x" selector on line 55 allows any patch version within 0.10 to be used, which can cause non-deterministic CI behavior if a new patch is released between runs. Pinning to a specific patch keeps CI reproducible and makes tool upgrades explicit.

Proposed change
-          version: "0.10.x"
+          version: "0.10.6"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/integration_tests.yml around lines 55 - 57, The workflow
uses the setup-uv action with a floating patch selector ("version: \"0.10.x\""),
which can lead to non-deterministic CI; update the setup for the setup-uv action
by replacing the version selector "0.10.x" with a specific patch version (e.g.,
"0.10.3") so the action is pinned to an exact release; locate the block
containing version: "0.10.x" for the setup-uv step and set the version field to
the desired exact patch string to make CI reproducible and upgrades explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pyproject.toml`:
- Line 17: Update the dependency constraint for django-sql-utils so it is pinned
exactly to 0.7.0 instead of a range; locate the dependency entry
"django-sql-utils>=0.7.0,<0.8" and replace it with an exact spec for version
0.7.0 to prevent silent patch-level upgrades and ensure compatibility with
Django 5.2.

In `@screenshot/Dockerfile`:
- Around line 11-14: The Dockerfile currently runs an unverified remote script
via "curl | sh" to install the Heroku CLI; replace that unsafe step in the RUN
layer with a verified installation flow: either install a pinned Heroku CLI via
npm (e.g., run npm install -g heroku@<version> after installing node/npm) or
download the official Heroku Linux tarball, fetch its published checksum or
signature, verify the checksum/signature, extract and install the binary, and
then purge build artifacts (replacing the existing curl/pipe and subsequent
purge). Update the RUN block in the Dockerfile to perform the chosen verified
method (download+verify+extract or npm install with a pinned version) and remove
the curl|sh pipeline to eliminate the supply-chain risk.

---

Outside diff comments:
In `@questions/views.py`:
- Around line 159-173: The loop overwrites withdrawal["question"] with the
resolved Question object before validating existence, so the ValidationError may
report None; before assigning withdrawal["question"] = question in the loop over
validated_data (and before using withdraw_at), save the original id (e.g.,
orig_qid = withdrawal.get("question")) and use that original id in the
ValidationError message (referencing validated_data, questions_map, withdrawal,
and the withdraw_foreacst_bulk usage) so the error shows the submitted ID
instead of the replaced value.

---

Duplicate comments:
In `@Dockerfile`:
- Line 44: The Dockerfile uses a floating minor tag in the build stage COPY
(--from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /usr/local/bin/) which harms
reproducibility; update the image reference to an immutable value by replacing
ghcr.io/astral-sh/uv:0.10 with a specific patch tag or, preferably, the image
digest (sha256:...) so the COPY --from= reference points to a fixed immutable
image; locate the COPY --from line and substitute the tag with the recommended
patch-level tag or the full `@sha256` digest.

---

Nitpick comments:
In @.github/workflows/integration_tests.yml:
- Around line 55-57: The workflow uses the setup-uv action with a floating patch
selector ("version: \"0.10.x\""), which can lead to non-deterministic CI; update
the setup for the setup-uv action by replacing the version selector "0.10.x"
with a specific patch version (e.g., "0.10.3") so the action is pinned to an
exact release; locate the block containing version: "0.10.x" for the setup-uv
step and set the version field to the desired exact patch string to make CI
reproducible and upgrades explicit.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8ac136 and 6dd29bd.

⛔ Files ignored due to path filters (4)
  • poetry.lock is excluded by !**/*.lock
  • screenshot/poetry.lock is excluded by !**/*.lock
  • screenshot/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (45)
  • .flake8
  • .github/workflows/integration_tests.yml
  • .github/workflows/unit_tests.yml
  • Claude.md
  • Dockerfile
  • README.md
  • comments/management/commands/update_top_comments_of_week_batch.py
  • fab_management/utils.py
  • fab_management/views.py
  • manage.py
  • metaculus_web/settings.py
  • misc/services/itn.py
  • posts/admin.py
  • projects/views/communities.py
  • pyproject.toml
  • questions/management/commands/build_forecasts.py
  • questions/models.py
  • questions/serializers/common.py
  • questions/tasks.py
  • questions/views.py
  • scoring/jobs.py
  • scoring/management/commands/update_global_bot_leaderboard.py
  • screenshot/Dockerfile
  • screenshot/pyproject.toml
  • screenshot/start.sh
  • scripts/create_minimal_db.sh
  • scripts/tests/run_integration_tests.sh
  • tests/unit/test_coherence/factories.py
  • tests/unit/test_comments/factories.py
  • tests/unit/test_notifications/factories.py
  • tests/unit/test_posts/factories.py
  • tests/unit/test_projects/factories.py
  • tests/unit/test_questions/factories.py
  • tests/unit/test_questions/test_views.py
  • tests/unit/test_scoring/factories.py
  • tests/unit/test_scoring/test_score_math.py
  • tests/unit/test_scoring/test_utils.py
  • tests/unit/test_users/factories.py
  • tests/unit/test_users/test_views.py
  • tests/unit/test_utils/test_the_math/test_aggregations.py
  • tests/unit/test_utils/test_the_math/test_formulas.py
  • users/services/bots_management.py
  • utils/models.py
  • utils/tasks.py
  • utils/the_math/aggregations.py
💤 Files with no reviewable changes (8)
  • tests/unit/test_questions/test_views.py
  • tests/unit/test_scoring/test_utils.py
  • .flake8
  • tests/unit/test_utils/test_the_math/test_aggregations.py
  • fab_management/views.py
  • utils/the_math/aggregations.py
  • tests/unit/test_utils/test_the_math/test_formulas.py
  • questions/management/commands/build_forecasts.py

Copy link
Contributor

@elisescu elisescu left a comment

Choose a reason for hiding this comment

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

LGTM and this is great!
Maybe split it in two separate PRs, one that updates the tooling to use uv and the other that reformats the code? This would make it possible for people to rebase their existing PRs in an easier way, i.e. get fewer merge conflicts.
But ask the rest and if they don't have PRs waiting to be merged, then no need to split it.

Copy link
Contributor

@lsabor lsabor left a comment

Choose a reason for hiding this comment

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

LGTM
I agree with Elis about splitting the uv dependency from the Ruff dependency as 2 PRs.
But on that note I don't have any large PRs that would be hurt from the formatting change, so if this is a convenient time - let's just go for it. Definitely double check with Hlib and Nikita about any outstanding major PRs.

Copy link
Contributor

@hlbmtc hlbmtc left a comment

Choose a reason for hiding this comment

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

Nice!

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.

Consider Switching From Poetry To UV

4 participants