Skip to content

chore(narratives): move the agent to uv, ruff, mypy, and pytest on Python 3.14 - #478

Open
nick-nlb wants to merge 9 commits into
datacommonsorg:narratives-devfrom
nick-nlb:narr-1-toolchain
Open

nick-nlb wants to merge 9 commits into
datacommonsorg:narratives-devfrom
nick-nlb:narr-1-toolchain

Conversation

@nick-nlb

@nick-nlb nick-nlb commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Overview

The Narratives agent had only an unpinned requirements.txt (no lockfile), no configured formatter, linter, or type checker, and contained standalone test scripts but no collectable test suite. This PR replaces the Python toolchain with uv, ruff, mypy, and pytest while leaving the application's runtime behavior untouched.

The quality tooling closes the gaps on the Python side of the application:

  • ruff (80 columns, formatting + linting) replaces .pylintrc and .style.yapf.
  • mypy --strict brings static type checking to the Python side alongside the UI's existing tsc --noEmit gate.
  • pytest replaces the five standalone scripts in agent/tests/ with six colocated *_test.py modules under agent/src/narratives_agent/.

Approach taken on mypy: The backend architecture is being migrated and most existing modules are rewritten, deleted, or updated in the changes that follow this one. These modules are temporarily exempt from strict typing (to prevent duplicate work).

Package rename: The agent's Python package was previously named src, so import src.config resolved only when the working directory happened to be agent/. Renaming the package to narratives_agent (agent/src/narratives_agent/) makes it installable and importable from any working directory.

Related Issues

None. Implements stage 1 (toolchain baseline) leading up to the FastAPI migration. See follow-ups.

Changes Made

  • Packaging & container (agent/pyproject.toml, agent/uv.lock, agent/Dockerfile): Replaces requirements.txt with pyproject.toml and uv.lock (pinned to public PyPI), upgrades the container base image to python:3.14-slim with uv sync --frozen, and adds .dockerignore / .gcloudignore to keep .venv and local artifacts out of build uploads.
  • Package layout (agent/src/narratives_agent/): Moves agent/src/* into an installable narratives_agent package, updates imports and root path resolution, and renames get_current_datetime_ist to get_current_datetime.
  • Formatting, linting, & type checking (agent/pyproject.toml): Configures ruff (80 columns) and mypy --strict (with temporary overrides for legacy modules), formats the package, fixes remaining lint findings, and deletes .pylintrc and .style.yapf.
  • Test suite (agent/src/narratives_agent/**/*_test.py): Migrates the five standalone test scripts in agent/tests/ to 44 pytest cases across six colocated *_test.py files using monkeypatch fixtures.
  • Validation & docs (cloudbuild/pr-validate.yaml, deploy.sh, AGENTS.md, CODING_GUIDELINES.md): Updates pr-validate.yaml and deploy.sh preflight checks for Python 3.14 and uv, and documents the toolchain and mypy standards in AGENTS.md and CODING_GUIDELINES.md.

Testing Done

From narratives/agent/:

  • uv sync --frozen
  • uv run ruff format --check . (19 files formatted)
  • uv run ruff check . (All checks passed)
  • uv run mypy (Success: no issues found in 8 source files)
  • uv run pytest (44 passed)

From narratives/ui/:

  • npx tsc --noEmit
  • npx vitest run (4 test files, 29 tests passed)

Verification:

  • Unit tests passed
  • Integration tests passed
  • Manual verification
  • Any updated goldens or fixtures were reviewed and are intentional

Risk & Rollback

None — runtime behavior is unchanged. Rollback is a clean revert of the PR commits and redeploying the previous agent image tag.

Follow-ups

The mypy exemptions in agent/pyproject.toml are retired in the subsequent PRs:

  • 2: removals: deletes unused workflows and routes along with their mypy override entries.
  • 3: test coverage: adds route and workflow test coverage ahead of the framework swap.
  • 4: FastAPI: rewrites the remaining modules under FastAPI.

Checklist

  • I have read AGENTS.md and followed CODING_GUIDELINES.md, plus FRONTEND.md for UI changes.
  • I have run the app's lint, test, and build commands, as documented in that application's guide.
  • I have commented my code, particularly in hard-to-understand areas.
  • My changes generate no new warnings.

Note: Only Maintainers can approve and merge PRs. Expected initial review time: 3 business days.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request modernizes the narratives agent toolchain by migrating to uv, targeting Python 3.14, and restructuring the codebase into a proper narratives_agent package. It also introduces strict mypy type-checking, adds pytest unit tests, and updates the Dockerfile, CI pipeline, and documentation. However, three critical syntax errors were introduced in narratives/agent/src/narratives_agent/mcp/data_utils.py where multiple exceptions are caught using comma separation instead of a tuple, which will cause SyntaxErrors at runtime in Python 3.

Comment thread narratives/agent/src/narratives_agent/mcp/data_utils.py
Comment thread narratives/agent/src/narratives_agent/mcp/data_utils.py
Comment thread narratives/agent/src/narratives_agent/mcp/data_utils.py
@nick-nlb
nick-nlb marked this pull request as ready for review September 21, 2026 22:28
@nick-nlb
nick-nlb requested review from a team, beets and juliawu as code owners September 21, 2026 22:28

@beets beets left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM - thanks for the refactor!

# This module is `agent/src/narratives_agent/config.py`, so `agent/` is three
# levels up. Anything resolving a path against the agent directory should read
# this rather than counting parents of its own `__file__`.
AGENT_ROOT = Path(__file__).resolve().parents[2]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Counting Path(__file__).resolve().parents[2] is brittle now that narratives_agent is an installable package — it only resolves to /app in the container because uv sync defaults to an editable install (/app/src/narratives_agent/config.py). If --no-editable or a wheel install is ever used, __file__ moves into .venv/lib/python3.14/site-packages/ and parents[2] silently resolves to /app/.venv/lib instead of /app.

Consider honoring an AGENT_ROOT environment variable (and setting ENV AGENT_ROOT=/app in Dockerfile) with parents[2] only as the local-dev fallback:

Suggested change
AGENT_ROOT = Path(__file__).resolve().parents[2]
AGENT_ROOT = Path(
os.environ.get("AGENT_ROOT") or Path(__file__).resolve().parents[2]
)

Comment on lines +41 to +42
When `DATA_PLANE_AUTH=none` is set in the environment, `attach_auth` exits
early without attaching any headers. Clearing `DATA_PLANE_AUTH` ensures

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: attach_auth in gcp_auth.py:114 checks os.environ.get("DATA_PLANE_AUTH", "auto").lower() == "off" rather than "none".

Suggested change
When `DATA_PLANE_AUTH=none` is set in the environment, `attach_auth` exits
early without attaching any headers. Clearing `DATA_PLANE_AUTH` ensures
When `DATA_PLANE_AUTH=off` is set in the environment, `attach_auth` exits
early without attaching any headers. Clearing `DATA_PLANE_AUTH` ensures

Consider also adding a quick unit test to pin the DATA_PLANE_AUTH=off opt-out path:

def test_data_plane_auth_off_attaches_no_credential(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setenv("DATA_PLANE_AUTH", "off")
    headers: dict[str, str] = {}
    gcp_auth.attach_auth(headers, "https://api.datacommons.org/mcp")
    assert headers == {}

Comment on lines +88 to +95
name: python:3.14-slim
entrypoint: bash
args:
- '-c'
- |
set -e
cd agent
pip install --quiet -r requirements.txt
python tests/test_mcp_session.py
python tests/test_backend_generations.py
python tests/test_auth_selection.py
python tests/test_prompt_rendering.py
pip install --quiet uv==0.10.2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Optional nit: Using the official pre-baked uv + Python 3.14 image avoids running pip install --quiet uv==0.10.2 on every Cloud Build validation run:

Suggested change
name: python:3.14-slim
entrypoint: bash
args:
- '-c'
- |
set -e
cd agent
pip install --quiet -r requirements.txt
python tests/test_mcp_session.py
python tests/test_backend_generations.py
python tests/test_auth_selection.py
python tests/test_prompt_rendering.py
pip install --quiet uv==0.10.2
name: ghcr.io/astral-sh/uv:0.10.2-python3.14-bookworm-slim
entrypoint: bash
args:
- '-c'
- |
set -e
cd agent

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.

2 participants