# Petstore reference (Python)

Reference consumer project for Smithplates **SQL** and **HTTP** codegen with thin hand-written adapters that connect generated FastAPI routes to generated repository services. HTTP clients are generated by Smithplates from the same Smithy `@httpService` model as the server.

## Layout

```
example/
  petstore-smithy-spec/    Shared Smithy model (`petstore/api/`, `petstore/db/`)
  python/                  Python reference implementation (codegen + server)
    smithy-build.json.template  Smithplates plugin config (render to smithy-build.json; CI commits version bumps)
    smithy-build.json           Rendered plugin config (do not edit; run scripts/render-smithy-build.sh)
    build-generated.sh       Wrapper for scripts/run-example-build.sh python
    db/migrations/           Versioned schema DDL (generated)
    src/
      generated/
        petstore/
          api/                 Generated FastAPI routes, httpx clients, HTTP models
          db/                  Generated repository services
      server/                Protocol adapters + app wiring
    tests/
      petstore/db/           Generated repository integration tests
      test_api.py            Hand-written API smoke tests
  tests/                   Cross-language HTTP scenario suite
```

Smithy models live under [`../petstore-smithy-spec/`](../petstore-smithy-spec/) (not inside `python/`). Regeneration still writes into this tree via `smithy-build.json` `sourceOutputDir` / `testOutputDir` settings.

## Features demonstrated

| Smithy feature | Where |
|----------------|--------|
| Separate API/DB Smithy namespaces | `petstore.api` (HTTP) and `petstore.db` (SQL); mapped in `src/server/repository_service.py` |
| `@sqlTable`, CRUD derive traits | `PetRepository`, `CategoryRepository`, `OrderRepository` in `petstore.db` |
| `@httpService`, `@http`, `@tags` | `Petstore` service in [`petstore-smithy-spec/petstore/api/http-service.smithy`](../petstore-smithy-spec/petstore/api/http-service.smithy) (`petstore.api`) |
| `@websocket` | Chat-style bidirectional endpoint in `petstore.api` (generated `websocket_routes.py` + client) |
| `@httpProblem` / shared `HttpProblem` | API error structures; base under `src/generated/smithplates/codegen/http/` |
| Smithplates httpx HTTP client | `smithplates.python.http.client` → `src/generated/petstore/api/petstore/client/` |
| String + int enums | `PetStatus`, `OrderStatus`, `PetSpecies`, `OrderPriority` |
| Timestamps | `created_at`, `updated_at`, `adopted_at` |
| `@sqlAutoUuid`, `@sqlVarchar`, indexes | `Pet`, `Store`, … |
| `@sqlAutoIncrement` | `OrderLine.id` (`Integer` serial PK) |
| `@nestedProperties` body binding | `UpdatePetInput.body` flattens `UpdatePetBody` as the PUT wire body |
| `@sqlJson` lists/structs/unions | `tags`, `attributes`, `fulfillment` |
| Blob + Document columns | `photo`, `metadata` |
| FK joins (many-to-one, optional, transitive) | `GetPetRecord` joins category → store, optional owner/profile |
| One-to-many join | `GetOrderRecord` → `order_lines` |
| HTTP + SQL wiring | `src/server/api_adapters.py` and `repository_service.py` bridge `petstore.api` to `petstore.db` |

Generated HTTP code lives under `src/generated/petstore/api/` and imports as `generated.petstore.api.*`. SQL repository code lives under `src/generated/petstore/db/` and imports as `generated.petstore.db.*`. Put `src` on `PYTHONPATH` so the `generated` package resolves from `src/generated/`.

For an OpenAPI Generator reference client (comparison / regression checks), see [`../openapi-reference-python/`](../openapi-reference-python/).

See [Integration — HTTP and SQL model separation](../../docs/usage/integration.md#http-and-sql-model-separation) for the convention this example follows.

## Regenerate codegen

This project is a **consumer** reference: the Smithy CLI resolves `smithplates-plugin` and its dependencies from Maven (`publishM2` locally, or Central for releases). Golden template fixtures under `templates/python/tests/` omit `maven` because they run the plugin from the sbt test classpath instead.

From the Smithplates repository root (after plugin source changes):

```bash
./scripts/run-example-build.sh python
```

Or from this directory:

```bash
./build-generated.sh
```

That runs `publishM2`, renders `smithy-build.json`, runs `smithy build`, syncs generated output, and formats Python with `uv run ruff format` from this project's `pyproject.toml`. Edit the `.template` files for config changes. CI and `./validate` regenerate before example lint/test.

To render configs only (for example before a manual `smithy build`):

```bash
sbtn publishM2
./scripts/render-smithy-build.sh example/python
cd example/python && smithy build
```

## Run the server

```bash
cd example/python
uv sync
uv run petstore-server
```

The server applies SQLite migrations on startup, seeds a demo store/category, and listens on `http://127.0.0.1:8080`.

## Tests

```bash
cd example/python
uv run pytest tests/test_api.py
uv run pytest tests/petstore/db/sqlite -m "integration and sqlite"
```

Cross-language HTTP scenarios live under [`../tests/`](../tests/). Run the Python server and client together:

```bash
../tests/run-tests.sh python python
```

The shared runner sets `PETSTORE_DATABASE_PATH` for an isolated SQLite file during each run.

Postgres generated tests require Docker (`tests/petstore/db/postgres`).

## Client example

```python
import asyncio

import httpx
from generated.petstore.api.client.client_registry import create_api_clients


async def main() -> None:
    async with httpx.AsyncClient() as http_client:
        clients = create_api_clients(http_client, base_url="http://127.0.0.1:8080")
        health = await clients.health_client.health_check()
        print(health.model_dump(mode="json"))

asyncio.run(main())
```

Run from this directory with `PYTHONPATH=src` (see `pyproject.toml` `pythonpath` for pytest).
