Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions agents-md/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Project Domain

This is a REST API for browsing and managing a collection of cars.
Each car is a complete record with a unique, server-assigned `id`
and the fields `make`, `model`, `year`, `horsepower`, `engine_cc`,
and `transmission`.

The `engine_cc` field can be `0` for fully electric cars. Every
car you return or store must include all fields.

## Project Setup and Management

- Python version: 3.14. Don't use newer syntax.
- Dependency management: `uv` and `pyproject.toml`. Never use `pip`
or a `requirements.txt` file.
- Add a dependency with `uv add <package>`. Never use `uv pip`
for dependencies.
- Run the app with `uv run fastapi dev main.py`.
- Branch from `main` as `feature/<name>` and use Conventional Commits.
- Stage changes for review. Don't commit to `main` or push without
being asked.

## Coding Conventions

- Type-hint public functions and methods, including their return types.
- Use `pathlib` for path management. Don't use `os.path`.
- Prefer f-strings over `str.format()` or `%` formatting.
- Follow EAFP: handle exceptions rather than checking conditions up front.
- Write Google-style docstrings for every public function and method.
- Validate request bodies with Pydantic models.
- Embrace idiomatic Python like comprehensions, generators, and decorators.

## Project Structure

- The project is flat: `main.py` holds the app and `cars.json` holds
the data.
- The `main.py` file is the only module to edit when adding features.
- Put tests in `tests/test_main.py`.
- Don't create new packages or new files without being asked.

## Quality Gates

A task is done only when all of these pass:

- `uv run ruff format` leaves the code unchanged.
- `uv run ruff check` reports no errors.
- `uv run mypy main.py` reports no errors.
- `uv run pytest` passes, with a test added for every new endpoint.

## Constraints

- Ask before adding any external dependency.
- Preserve the signature and response shape of existing endpoints.
- Don't use blocking I/O inside `async` functions.
- Keep existing tests intact, and fix the code to make them pass.
- Declare a task done only after the gates pass and docstrings are
updated.

## Ignore

Treat everything in `.gitignore` as off-limits to read or edit. On top of
that, never open:

- Secrets and `.env` files
- Large data files unrelated to the current task
- Vendored or generated code
3 changes: 3 additions & 0 deletions agents-md/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# How to Write an AGENTS.md File for a Python Project

This folder provides the code examples for the Real Python tutorial [How to Write an AGENTS.md File for a Python Project](https://realpython.com/agents-md/)
92 changes: 92 additions & 0 deletions agents-md/cars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
[
{
"id": 1,
"make": "Ford",
"model": "Mustang",
"year": 1969,
"horsepower": 290,
"engine_cc": 5752,
"transmission": "Manual"
},
{
"id": 2,
"make": "Chevrolet",
"model": "Corvette",
"year": 2020,
"horsepower": 490,
"engine_cc": 6162,
"transmission": "Automatic"
},
{
"id": 3,
"make": "Dodge",
"model": "Charger",
"year": 2023,
"horsepower": 370,
"engine_cc": 5654,
"transmission": "Automatic"
},
{
"id": 4,
"make": "Tesla",
"model": "Model S",
"year": 2022,
"horsepower": 670,
"engine_cc": 0,
"transmission": "Automatic"
},
{
"id": 5,
"make": "Jeep",
"model": "Wrangler",
"year": 2021,
"horsepower": 285,
"engine_cc": 3604,
"transmission": "Automatic"
},
{
"id": 6,
"make": "Ford",
"model": "F-150",
"year": 2024,
"horsepower": 400,
"engine_cc": 3496,
"transmission": "Automatic"
},
{
"id": 7,
"make": "Cadillac",
"model": "Escalade",
"year": 2023,
"horsepower": 420,
"engine_cc": 6162,
"transmission": "Automatic"
},
{
"id": 8,
"make": "Chevrolet",
"model": "Camaro",
"year": 2018,
"horsepower": 455,
"engine_cc": 6162,
"transmission": "Manual"
},
{
"id": 9,
"make": "GMC",
"model": "Sierra",
"year": 2022,
"horsepower": 355,
"engine_cc": 5328,
"transmission": "Automatic"
},
{
"id": 10,
"make": "Chrysler",
"model": "300",
"year": 2019,
"horsepower": 292,
"engine_cc": 3604,
"transmission": "Automatic"
}
]
22 changes: 22 additions & 0 deletions agents-md/original_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import json
from pathlib import Path

from fastapi import FastAPI, HTTPException

app = FastAPI()
cars: list[dict] = json.loads(Path("cars.json").read_text())


@app.get("/cars")
def list_cars() -> list[dict]:
"""Return a list of all cars."""
return cars


@app.get("/cars/{car_id}")
def get_car(car_id: int) -> dict:
"""Return a single car by its id, or raise 404 if it doesn't exist."""
for car in cars:
if car["id"] == car_id:
return car
raise HTTPException(status_code=404, detail="Car not found")
41 changes: 41 additions & 0 deletions agents-md/run1_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import json
import os

from fastapi import FastAPI, HTTPException

app = FastAPI()

data_file = os.path.join(os.path.dirname(__file__), "cars.json")
with open(data_file) as f:
cars = json.load(f)


@app.get("/cars")
def list_cars() -> list[dict]:
"""Return a list of all cars."""
return cars


@app.get("/cars/{car_id}")
def get_car(car_id: int) -> dict:
"""Return a single car by its id, or raise 404 if it doesn't exist."""
for car in cars:
if car["id"] == car_id:
return car
raise HTTPException(status_code=404, detail="Car not found")


@app.post("/cars")
def create_car(car: dict):
car["id"] = len(cars) + 1
cars.append(car)
return {"message": "Car created successfully", "car": car}


@app.delete("/cars/{car_id}")
def delete_car(car_id: int):
for i in range(len(cars)):
if cars[i]["id"] == car_id:
cars.pop(i)
return {"message": "Car deleted"}
return {"error": "Car not found"}
51 changes: 51 additions & 0 deletions agents-md/run2_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import json
from pathlib import Path

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()
cars: list[dict] = json.loads(Path("cars.json").read_text())


class NewCar(BaseModel):
make: str
model: str
year: int
horsepower: int
engine_cc: int
transmission: str


@app.get("/cars")
def list_cars() -> list[dict]:
"""Return a list of all cars."""
return cars


@app.get("/cars/{car_id}")
def get_car(car_id: int) -> dict:
"""Return a single car by its id, or raise 404 if it doesn't exist."""
for car in cars:
if car["id"] == car_id:
return car
raise HTTPException(status_code=404, detail="Car not found")


@app.post("/cars", status_code=201)
def create_car(new_car: NewCar) -> dict:
"""Add a new car and return it with a server-assigned id."""
car = new_car.model_dump()
car["id"] = max((existing["id"] for existing in cars), default=0) + 1
cars.append(car)
return car


@app.delete("/cars/{car_id}", status_code=204)
def delete_car(car_id: int) -> None:
"""Delete a car by its id, or raise 404 if it doesn't exist."""
for index, car in enumerate(cars):
if car["id"] == car_id:
del cars[index]
return
raise HTTPException(status_code=404, detail="Car not found")
26 changes: 26 additions & 0 deletions agents-md/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi.testclient import TestClient

from run2_main import app, cars

client = TestClient(app)


def test_create_car_assigns_unique_id():
new_car = {
"make": "Honda",
"model": "Civic",
"year": 2021,
"horsepower": 158,
"engine_cc": 1996,
"transmission": "Manual",
}
response = client.post("/cars", json=new_car)

assert response.status_code == 201
assert response.json()["id"] not in {car["id"] for car in cars[:-1]}


def test_delete_missing_car_returns_404():
response = client.delete("/cars/999")

assert response.status_code == 404
Loading