# Upgrade a FastAPI App to Python 3.14

Yes. An agent moves your FastAPI app to Python 3.14, runs pytest with warnings as errors, fixes each deprecation and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/upgrade-python-version/fastapi/), in a Project that has your repository.

## What the agent does

In a FastAPI service like testdrivenio/fastapi-tdd-docker, the Python version is pinned in container images, not in pyproject.toml. project/pyproject.toml holds only the aerich migration settings. project/Dockerfile and both stages of project/Dockerfile.prod start FROM python:3.13.3-slim-bookworm, and the GitHub Actions workflow in .github/workflows/main.yml builds Dockerfile.prod and runs pytest, flake8, black and isort inside the built container. So the agent changes three FROM lines to a 3.14 slim image, checks that the Debian release in the tag still offers the apt packages the Dockerfiles install (netcat-traditional, gcc, postgresql), and leaves the workflow's commands alone because they inherit the image.

Next it reinstalls project/requirements-dev.txt, which pulls in project/requirements.txt, on Python 3.14. The pins are exact: fastapi==0.115.12, tortoise-orm==0.25.0, asyncpg==0.30.0, uvicorn==0.34.1, pydantic-settings==2.8.1, newspaper3k==0.2.8 and lxml-html-clean==0.4.2, plus pytest==8.3.5 and black==25.1.0 for development. asyncpg and the lxml that newspaper3k depends on ship compiled extensions, so the agent reads pip's output for any package that falls back to building from source and moves only those pins to the nearest release with a cp314 wheel.

Then it runs the suite the way the prompt asks, with python -m pytest -W error, against a Postgres started from project/db/Dockerfile (postgres:17) and the DATABASE_TEST_URL that docker-compose.yml sets. Two deprecations are already visible in the test files. tests/test_summaries_unit.py builds expected payloads with datetime.utcnow(), which Python has deprecated since 3.12, and both test modules post bodies with data=json.dumps(...), which httpx 0.28 warns about in favour of content= or json=. With warnings as errors these fail first, and the agent rewrites them to datetime.now(timezone.utc) and json= without changing what each test asserts.

It also checks warnings the suite does not trigger on its own: the legacy ENV key value form and the lowercase as builder in Dockerfile.prod, which BuildKit reports when the image is built, and app/config.py, where Settings declares testing: bool = 0 and database_url: AnyUrl = None. Any change outside a version pin is listed separately in the pull request so a reviewer can tell an upgrade fix from a cleanup.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "codex",
  "input": "Upgrade this FastAPI project to Python 3.14. Change every place the version is pinned: the FROM lines in project/Dockerfile and both stages of project/Dockerfile.prod, and anything in pyproject or CI. Start Postgres from project/db, set DATABASE_TEST_URL, reinstall requirements-dev.txt on 3.14, and bump only the pins that have no 3.14 wheel or fail to install. Run python -m pytest -W error and fix each failure or deprecation in the code or tests, such as datetime.utcnow() and httpx data= bodies, without weakening any assertion. Build Dockerfile.prod and run flake8, black --check and isort --check-only as CI does. Open a pull request that lists each pin change and each fix with the warning it removed.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

The pull request touches the two Dockerfiles, requirements.txt or requirements-dev.txt where a pin had to move, and the test and application files where a warning was fixed. Its description has a table of pins (old version, new version, reason such as no cp314 wheel) and a list of deprecations, each with the warning text pytest printed and the file it came from. CI on the branch builds the 3.14 image and runs the same pytest, flake8, black and isort steps the workflow already had.

A reviewer checks three things: that the base image tag is the one the team wants to deploy, that each bumped package's release notes do not change behaviour the app relies on (Tortoise ORM and asyncpg in particular), and that the test changes swap a deprecated call for its replacement rather than loosen an assertion. The session transcript, read with GET /o/{org}/sessions/{id}/messages, shows the pip and pytest output each change was based on.

## Run it on every new Python release

When a Python minor release lands, your scheduler sends one POST /o/{org}/sessions request per service, each with its repository in resources and the prompt above with the target version filled in. Send an Idempotency-Key per repository and version, so a retried request returns the run already started instead of opening a second pull request.

Webhooks report each run as it finishes. Services whose dependencies have no wheels yet come back with a list of the blocking packages instead of a merged change, and you can send the same session a follow-up a few weeks later to try again from where it stopped.

## Why run it on Rush?

Trying a new Python on a FastAPI service means a second interpreter, a Postgres container and a reinstall of every compiled dependency, which is slow to set up and easy to leave half-done on a laptop. In a Rush sandbox the agent installs Python 3.14 next to the old one, runs the suite on both, and keeps every pip, pytest and docker build command with its output in the run's record.

Most teams have more than one service on the same stack, and the deprecations repeat. Starting one run per repository lets them proceed side by side up to the key's concurrency limit, each in its own sandbox, and each one returns a pull request you can merge or a clear list of the packages that still block the upgrade.

## Frequently asked questions

### My project pins Python only in the Dockerfile. Will the agent find it?

Yes. The prompt names every place a version can be pinned, and the agent searches the Dockerfiles, pyproject.toml, CI workflows and files like .python-version. In this example the only pins are three FROM lines, and the description says no pyproject or CI pin existed.

### Why run pytest with -W error?

A deprecation that only prints a warning today becomes an error in a later release. Turning warnings into errors makes the suite fail on each one, so the agent fixes them while it is already working on the upgrade. Warnings that come from inside a third-party package are listed, not silenced.

### What if a dependency has no Python 3.14 wheel yet?

The agent tries the nearest newer release that installs. If none does, it stops and reports the package and the build error instead of vendoring a patch or pinning an unreleased commit, so you decide whether to wait or replace it.

### Will it change my CI workflow?

Only where a Python version is written into it. In this example the workflow builds Dockerfile.prod and runs commands inside the container, so updating the image is enough and the workflow file stays as it is.
