Use Cases (Updated: 6/2/2026)

Claude Code Python Guide: uv, pytest, Ruff, FastAPI for Beginners

Build a Python project with Claude Code, uv, pyproject.toml, pytest, Ruff, FastAPI, and safe iteration.

Claude Code Python Guide: uv, pytest, Ruff, FastAPI for Beginners

The hard part of using Claude Code for Python is rarely the first generated file. The hard part is knowing whether the project is actually healthy: the environment is reproducible, imports work, tests run, formatting is consistent, and the next change can be reviewed without guessing.

This guide builds a small Python starter workflow for beginners. We will use uv or the standard venv, keep project settings in pyproject.toml, write a tiny FastAPI app and an optional CLI, add pytest, run ruff, and then use Claude Code for small, safe iterations.

Keep the official docs nearby because tool behavior changes. Start with the Claude Code overview, the Python Packaging User Guide for writing pyproject.toml, Astral’s uv installation docs, pytest getting started, the Ruff documentation, and the FastAPI first steps.

Start with a small definition of done

Claude Code can edit a codebase, run commands, and explain failures, but beginners still need a small target. Do not ask for authentication, a database, Docker, background jobs, and CI in the first prompt. That creates too many moving parts, and every error looks mysterious.

For a first Python project, the definition of done can be this:

  • uv run pytest passes
  • uv run ruff check . and uv run ruff format . run cleanly
  • FastAPI can create and list one task
  • The optional CLI can add one task from the terminal
  • Claude Code reports which commands it ran
flowchart LR
  A["Task brief"] --> B["uv or venv"]
  B --> C["pyproject.toml"]
  C --> D["src implementation"]
  D --> E["pytest"]
  E --> F["ruff"]
  F --> G["Small Claude Code iteration"]

Think of the brief as the work order for the coding agent. It should say what to build, what files it may touch, which Python version to target, and how to prove the result. If you use terms such as harness, explain them as “the scaffold the agent works inside” so junior developers can review the plan.

Give Claude Code a concrete brief

A good first prompt does not need to be poetic. It needs boundaries.

Add a small Python 3.12 task management API to this repository.

Requirements:
- Use uv for package management and keep settings in pyproject.toml.
- Put implementation code under src/task_api/ and tests under tests/.
- Implement POST /tasks and GET /tasks with FastAPI.
- Write pytest coverage for the happy path and a 404 path.
- Make ruff check and ruff format pass.
- Before editing, show the plan. After editing, report the commands you ran.

Allowed files:
- pyproject.toml
- src/task_api/**
- tests/**

This prompt turns Claude Code into an implementation partner, not just a code generator. It also gives you a review surface. If the diff touches a frontend folder or rewrites unrelated files, you can reject it immediately.

For durable repository rules, pair this with CLAUDE.md best practices. When the API grows beyond two endpoints, continue with API development with Claude Code. For test strategy, use Claude Code testing strategies.

Create the environment with uv or venv

For new learning projects in 2026, uv is a practical default because it creates environments quickly, manages dependencies, and keeps the workflow easy to explain. Some company laptops may not allow new tools, so the standard library venv fallback is still worth documenting.

On macOS or Linux:

mkdir task-api
cd task-api
uv init --app --python 3.12
uv add "fastapi[standard]"
uv add --dev pytest ruff
mkdir -p src/task_api tests
touch src/task_api/__init__.py

On Windows PowerShell:

mkdir task-api
cd task-api
uv init --app --python 3.12
uv add "fastapi[standard]"
uv add --dev pytest ruff
New-Item -ItemType Directory -Force src/task_api, tests
New-Item -ItemType File -Force src/task_api/__init__.py

If uv is not available, use venv:

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "fastapi[standard]" pytest ruff
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install "fastapi[standard]" pytest ruff

The important decision is not whether uv is fashionable. The important decision is that the project has one documented way to install, run, test, and format. Tell Claude Code which path you chose before it edits files.

Keep Python settings in pyproject.toml

Beginners often inherit Python projects with requirements.txt, setup.cfg, pytest.ini, .flake8, and .ruff.toml all competing for attention. For a small starter, consolidate the important settings in pyproject.toml.

[project]
name = "task-api"
version = "0.1.0"
description = "Small FastAPI and CLI sample for Claude Code practice"
requires-python = ">=3.11"
dependencies = [
  "fastapi[standard]>=0.115.0",
]

[project.scripts]
task-api = "task_api.cli:main"

[dependency-groups]
dev = [
  "pytest>=8.0.0",
  "ruff>=0.8.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/task_api"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]

[tool.ruff]
line-length = 100
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]

Now you can ask Claude Code to follow one source of truth. That makes reviews easier: dependency changes, test discovery, import paths, and lint policy are visible in one file.

Add a runnable FastAPI skeleton

Place this in src/task_api/main.py. It uses an in-memory dictionary, so it is not production storage. That is intentional. The goal is to learn request validation, response models, error handling, and tests before adding a database.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="Task API")


class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=80)


class Task(BaseModel):
    id: int
    title: str
    done: bool = False


_tasks: dict[int, Task] = {}
_next_id = 1


@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate) -> Task:
    global _next_id
    task = Task(id=_next_id, title=payload.title)
    _tasks[task.id] = task
    _next_id += 1
    return task


@app.get("/tasks", response_model=list[Task])
def list_tasks() -> list[Task]:
    return list(_tasks.values())


@app.patch("/tasks/{task_id}/done", response_model=Task)
def mark_done(task_id: int) -> Task:
    task = _tasks.get(task_id)
    if task is None:
        raise HTTPException(status_code=404, detail="Task not found")
    updated = task.model_copy(update={"done": True})
    _tasks[task_id] = updated
    return updated

Run it:

uv run fastapi dev src/task_api/main.py

Then open http://127.0.0.1:8000/docs and create a task through the generated documentation. Once this works, adding SQLite, PostgreSQL, authentication, or deployment becomes a series of smaller changes instead of one large mystery.

If you also want a command-line entry point, place this in src/task_api/cli.py.

import argparse
import json
from pathlib import Path

DB_PATH = Path("tasks.json")


def load_tasks() -> list[dict[str, object]]:
    if not DB_PATH.exists():
        return []
    return json.loads(DB_PATH.read_text(encoding="utf-8"))


def save_tasks(tasks: list[dict[str, object]]) -> None:
    DB_PATH.write_text(json.dumps(tasks, ensure_ascii=False, indent=2), encoding="utf-8")


def add_task(title: str) -> dict[str, object]:
    tasks = load_tasks()
    task = {"id": len(tasks) + 1, "title": title, "done": False}
    tasks.append(task)
    save_tasks(tasks)
    return task


def main() -> None:
    parser = argparse.ArgumentParser(description="Task CLI")
    subparsers = parser.add_subparsers(dest="command", required=True)

    add_parser = subparsers.add_parser("add")
    add_parser.add_argument("title")

    args = parser.parse_args()
    if args.command == "add":
        task = add_task(args.title)
        print(f"Added #{task['id']}: {task['title']}")


if __name__ == "__main__":
    main()
uv run task-api add "write pytest"

Add pytest and Ruff before asking for more features

Tests make Claude Code useful because every later change has a proof command. Add tests/test_main.py.

import pytest
from fastapi.testclient import TestClient

from task_api import main
from task_api.main import app


@pytest.fixture(autouse=True)
def clean_tasks() -> None:
    main._tasks.clear()
    main._next_id = 1


def test_create_and_list_tasks() -> None:
    client = TestClient(app)

    response = client.post("/tasks", json={"title": "Write pytest"})
    assert response.status_code == 201
    assert response.json()["title"] == "Write pytest"

    list_response = client.get("/tasks")
    assert list_response.status_code == 200
    assert len(list_response.json()) == 1


def test_mark_done_returns_404_for_missing_task() -> None:
    client = TestClient(app)

    response = client.patch("/tasks/999/done")

    assert response.status_code == 404
    assert response.json()["detail"] == "Task not found"

Use the same commands every time:

uv run pytest
uv run ruff check .
uv run ruff format .

When a command fails, do not paste a vague “fix this” request. Tell Claude Code which command failed, what behavior must stay the same, and which files it may edit. Example: “Fix only the failing uv run pytest result. Keep the API response shape unchanged. Prefer the smallest diff in src/task_api/main.py or tests/test_main.py.”

Three practical use cases

The first use case is a learning API. A beginner can see routing, validation, response models, OpenAPI docs, tests, and formatting in one small project. It is better than starting with a full SaaS backend because every concept is visible.

The second use case is a business automation CLI. Cleaning CSV files, renaming exports, checking folders, or generating a weekly report often does not need a web UI. Claude Code can help you turn a fragile script into a command with arguments, clear errors, and tests.

The third use case is adding tests to legacy Python. If a script is already used by the team but nobody wants to touch it, ask Claude Code to write characterization tests first. A characterization test captures current behavior before refactoring. That is safer than asking for a rewrite on day one.

The fourth use case is training material. A shared starter with pyproject.toml, src layout, pytest, ruff, and proof commands gives every learner the same baseline. For teams, ClaudeCodeLab can adapt this into Claude Code training and consultation so examples match internal tools instead of generic demos.

Failure cases to avoid

The most common mistake is asking “make a Python app” without naming the package manager. Claude Code may mix pip, poetry, uv, and requirements.txt. Choose one and write it into the brief.

The second mistake is a broken import path. With src layout, tests may fail to import the package unless pythonpath = ["src"] is configured or the package is installed correctly. Import errors waste beginner time because the application code can be perfectly fine while the test runner cannot find it.

The third mistake is adding a database and authentication too early. JWT, migrations, async sessions, and environment variables are useful, but they multiply the failure modes. Start in memory, then add SQLite, then move to the production database.

The fourth mistake is accepting automatic lint changes without review. Ruff is excellent, but a lint fix is still a code change. Ask Claude Code to explain the Ruff diff before applying it when the code is part of a real project.

The fifth mistake is pasting secrets into the prompt. Use .env.example names and fake values. Do not include customer data, API keys, production database URLs, or private tokens.

Monetization path

Python tutorials can bring search traffic, but a thin command list is hard to monetize and weak for AdSense quality. A better article gives the reader code, failure cases, verification, and a natural next step.

Solo developers can start with free checklists and reusable prompts, then move to Claude Code products when the same brief is used repeatedly. Teams should consider Claude Code training and consultation when the risk moves from personal learning to shared repositories, onboarding, code review, or production systems.

What I verified

For this update, I used the same order Masa uses for beginner Python materials: fix the environment first, put project rules in pyproject.toml, keep FastAPI small, add tests before features, and make Ruff part of the everyday command loop. The practical result is not that Claude Code writes perfect Python. The result is that each failure becomes local: dependency setup, import path, test behavior, or formatting. That makes the work teachable, reviewable, and reusable for training.

The takeaway is simple: use Claude Code after you define the scaffold. A small project with clear commands beats a large generated backend that nobody can debug.

#Claude Code #Python #uv #pytest #FastAPI
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.