{
  "id": "fastapi-async-sqlalchemy",
  "type": "loadout",
  "title": "FastAPI with async SQLAlchemy 2.1 and Alembic",
  "summary": "FastAPI on async SQLAlchemy 2.1: per-request AsyncSession, lazy=\"raise\" with eager loads, async Alembic with alembic check, and tests that count queries.",
  "description": "Agents writing async SQLAlchemy reach for habits from sync code. They lazy-load a relationship inside an async route and hit MissingGreenlet, or share one AsyncSession across asyncio.gather. SQLAlchemy 2.1 adds a new trap: it no longer installs greenlet, so a project that declares plain sqlalchemy cannot start an async engine. This loadout sets the rules: a per-request session dependency, expire_on_commit=False, lazy=\"raise\" on every relationship with selectinload for what the response needs, explicit commits with IntegrityError mapped to 409, and database URLs from the environment. The skill adds a resource end to end, with model, reviewed Alembic migration, router and async tests that assert the query count. The verifier runs the tests on SQLAlchemy 2.1.5 and FastAPI 0.143.3. It upgrades and downgrades the migration, confirms alembic check catches model drift, and shows the greenlet failure.\n",
  "highlights": [
    "SQLAlchemy 2.1 needs sqlalchemy[asyncio]; the verifier shows the greenlet error",
    "lazy=\"raise\" plus selectinload: the list test asserts exactly two SELECTs",
    "One AsyncSession per request, never shared across tasks",
    "Async Alembic env from DATABASE_URL; alembic check catches model drift",
    "httpx AsyncClient over ASGITransport with a fresh database per test"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "credits": [
    {
      "title": "SQLAlchemy 2.1 docs: asynchronous I/O (asyncio)",
      "url": "https://docs.sqlalchemy.org/en/21/orm/extensions/asyncio.html"
    },
    {
      "title": "SQLAlchemy 2.1 docs: what's new (greenlet no longer installed by default)",
      "url": "https://docs.sqlalchemy.org/en/21/changelog/migration_21.html"
    },
    {
      "title": "SQLAlchemy docs: relationship loading techniques (raise loading)",
      "url": "https://docs.sqlalchemy.org/en/21/orm/queryguide/relationships.html"
    },
    {
      "title": "Alembic docs: autogenerate and alembic check",
      "url": "https://alembic.sqlalchemy.org/en/latest/autogenerate.html"
    },
    {
      "title": "FastAPI docs: async tests",
      "url": "https://fastapi.tiangolo.com/advanced/async-tests/"
    }
  ],
  "categories": [
    "web-app",
    "data"
  ],
  "stacks": [
    "python",
    "fastapi",
    "sqlalchemy",
    "uv",
    "pytest"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Rules for a FastAPI service on async SQLAlchemy 2.1 with Alembic and uv.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "antigravity-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/AGENTS.md",
      "bytes": 1918,
      "lang": "markdown",
      "content": "# AGENTS.md: FastAPI with async SQLAlchemy\n\nFastAPI service on SQLAlchemy 2.1's asyncio extension, with Alembic for migrations and uv for packages. SQLAlchemy 2.1\nneeds Python 3.11 or newer and no longer installs greenlet by default: declare `sqlalchemy[asyncio]`, or async\nengines fail with an ImportError about greenlet.\n\n## Commands\n- `uv sync`; add packages with `uv add` (exact pins: `\"sqlalchemy[asyncio]==2.1.5\"`, `aiosqlite==0.22.1` or\n  `asyncpg==0.32.0`, `alembic==1.20.0`).\n- Run: `uv run fastapi dev app/main.py`. Tests: `uv run pytest -q`. Lint: `uv run ruff check . && uv run ruff format --check .`.\n- Migrations: `uv run alembic revision --autogenerate -m \"...\"`, review the file, then `uv run alembic upgrade head`\n  and `uv run alembic check`.\n\n## Rules\n- Async all the way: `create_async_engine`, `async_sessionmaker(engine, expire_on_commit=False)`, `AsyncSession`\n  via a `SessionDep` dependency. No sync `Session`, `requests` or blocking file I/O inside `async def` routes.\n- One session per request. Never share an `AsyncSession` across tasks (`asyncio.gather` over one session is a bug).\n- Relationships use `lazy=\"raise\"`. Load what you return with `selectinload` or `joinedload`. A `MissingGreenlet`\n  error means a lazy load slipped through; fix the query, do not wrap it in `run_sync`.\n- Commit explicitly in the route or service; roll back and map `IntegrityError` to 409.\n- Return Pydantic response models, never ORM objects. Pydantic v2 only.\n- Database URLs and passwords come from the environment (`DATABASE_URL`), never `alembic.ini` or code.\n- Dispose of the engine in the app's `lifespan` shutdown.\n- Never edit a migration that has run outside your machine. Add a new one.\n\n## Tests\n- Async tests through `httpx.AsyncClient` and `ASGITransport` with a throwaway database per test.\n- List endpoints assert their query count. New resources: use the `async-sqlalchemy-resource` skill.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/CLAUDE.md",
      "bytes": 416,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n\n- Skills from this loadout live in `.claude/skills/`: `async-sqlalchemy-resource`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"FastAPI with async SQLAlchemy 2.1 and Alembic\" v1.0.0 — https://loadout.jnw-it.com/loadouts/fastapi-async-sqlalchemy/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/GEMINI.md",
      "bytes": 417,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n\n- Skills from this loadout live in `.agents/skills/`: `async-sqlalchemy-resource`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"FastAPI with async SQLAlchemy 2.1 and Alembic\" v1.0.0 — https://loadout.jnw-it.com/loadouts/fastapi-async-sqlalchemy/ -->\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/SKILL.md",
      "role": "skill",
      "description": "Model, migration, router and tests for a new resource.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/SKILL.md",
        "codex": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
        "cursor": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
        "copilot": ".agents/skills/async-sqlalchemy-resource/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/SKILL.md",
      "bytes": 2983,
      "lang": "markdown",
      "content": "---\nname: async-sqlalchemy-resource\ndescription: Add a database-backed resource to a FastAPI app on async SQLAlchemy 2.1 (model, Alembic migration, router and async tests) without lazy-load errors or N+1 queries. Use when adding or changing tables, relationships or endpoints that read or write the database.\n---\n\n# Add an async SQLAlchemy resource\n\nThe references are a complete working set: `db.py` (engine, session factory, `SessionDep`), `models.py`,\n`customers.py` (router), `0001_customers_orders.py` (migration), `alembic_env.py` (async Alembic env) and\n`test_customers.py`.\n\n## 1. Model\n\n- Typed mappings: `Mapped[int]`, `mapped_column(...)`. `Mapped[str | None]` means nullable; plain `Mapped[str]` is\n  `NOT NULL`.\n- Every relationship gets `lazy=\"raise\"`. Under asyncio an implicit lazy load fails with `MissingGreenlet`, and in\n  sync code it is a hidden extra query. `raise` turns both into an immediate, obvious error.\n- Index foreign keys you filter on (`index=True`). Use `ondelete=` on the foreign key, not ORM cascade alone.\n\n## 2. Migration\n\n```bash\nuv run alembic revision --autogenerate -m \"add orders\"\n```\n\nRead the generated file before keeping it. Autogenerate misses renames (it emits drop plus add, which loses data),\nsome server defaults and enum changes. Then:\n\n```bash\nuv run alembic upgrade head\nuv run alembic check          # fails if models and migrations disagree\nuv run alembic downgrade -1 && uv run alembic upgrade head   # downgrade must work too\n```\n\nThe env reads `DATABASE_URL` from the environment. Never write a URL with a password into `alembic.ini`.\n\n## 3. Router\n\n- Take the session as `session: SessionDep`. One session per request. Never store it globally or share it between\n  `asyncio` tasks: an `AsyncSession` is not safe for concurrent use.\n- Load what the response needs in the query: `selectinload(Customer.orders)` for collections, `joinedload` for\n  many-to-one. Use `await session.scalars(select(...))`, `await session.get(...)` and `await session.commit()`.\n- `expire_on_commit=False` on the session factory keeps attributes readable after commit.\n- Map `IntegrityError` to 409 after `await session.rollback()`. Return Pydantic models\n  (`ConfigDict(from_attributes=True)`), never ORM objects.\n- Need a relationship on an object you already have? `await session.refresh(obj, [\"orders\"])`, or\n  `await obj.awaitable_attrs.orders` on relationships that are not `lazy=\"raise\"`.\n\n## 4. Tests\n\n- `httpx.AsyncClient(transport=ASGITransport(app=app))` with `pytest.mark.anyio`, so tests run on the same event\n  loop as the app.\n- A fresh `sqlite+aiosqlite://` engine with `StaticPool` per test, and `app.dependency_overrides[get_session]`.\n- Count queries with an event listener on `engine.sync_engine` and assert the number of SELECTs for list endpoints.\n  That is the test that catches N+1 regressions.\n\n## Done means\n\npytest, `ruff check`, `ruff format --check` and `alembic check` all pass, and the migration's downgrade works.\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/references/db.py",
      "role": "reference",
      "description": "Async engine, session factory and SessionDep.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/references/db.py",
        "codex": ".agents/skills/async-sqlalchemy-resource/references/db.py",
        "cursor": ".agents/skills/async-sqlalchemy-resource/references/db.py",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/references/db.py",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/references/db.py",
        "copilot": ".agents/skills/async-sqlalchemy-resource/references/db.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/references/db.py",
      "bytes": 928,
      "lang": "markdown",
      "content": "\"\"\"Async engine, session factory and the per-request session dependency.\"\"\"\n\nimport os\nfrom collections.abc import AsyncIterator\nfrom typing import Annotated\n\nfrom fastapi import Depends\nfrom sqlalchemy.ext.asyncio import AsyncAttrs, AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = os.environ.get(\"DATABASE_URL\", \"sqlite+aiosqlite:///./app.db\")\n\nengine = create_async_engine(DATABASE_URL, pool_pre_ping=True)\n# expire_on_commit=False: attributes stay loaded after commit, so building the\n# response does not trigger a lazy load (which raises under asyncio).\nSessionFactory = async_sessionmaker(engine, expire_on_commit=False)\n\n\nclass Base(AsyncAttrs, DeclarativeBase):\n    pass\n\n\nasync def get_session() -> AsyncIterator[AsyncSession]:\n    async with SessionFactory() as session:\n        yield session\n\n\nSessionDep = Annotated[AsyncSession, Depends(get_session)]\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/references/models.py",
      "role": "reference",
      "description": "Typed models with lazy=\"raise\" relationships.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/references/models.py",
        "codex": ".agents/skills/async-sqlalchemy-resource/references/models.py",
        "cursor": ".agents/skills/async-sqlalchemy-resource/references/models.py",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/references/models.py",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/references/models.py",
        "copilot": ".agents/skills/async-sqlalchemy-resource/references/models.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/references/models.py",
      "bytes": 928,
      "lang": "markdown",
      "content": "\"\"\"Customer and Order. Relationships use lazy=\"raise\" so a missed eager load fails loudly.\"\"\"\n\nfrom datetime import datetime\n\nfrom sqlalchemy import ForeignKey, String, func\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\n\nfrom app.db import Base\n\n\nclass Customer(Base):\n    __tablename__ = \"customers\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    email: Mapped[str] = mapped_column(String(254), unique=True)\n    orders: Mapped[list[\"Order\"]] = relationship(back_populates=\"customer\", lazy=\"raise\")\n\n\nclass Order(Base):\n    __tablename__ = \"orders\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    customer_id: Mapped[int] = mapped_column(ForeignKey(\"customers.id\", ondelete=\"CASCADE\"), index=True)\n    total_pence: Mapped[int]\n    created_at: Mapped[datetime] = mapped_column(server_default=func.now())\n    customer: Mapped[Customer] = relationship(back_populates=\"orders\", lazy=\"raise\")\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/references/customers.py",
      "role": "reference",
      "description": "Router with eager loads, commits and 409/404 handling.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/references/customers.py",
        "codex": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
        "cursor": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
        "copilot": ".agents/skills/async-sqlalchemy-resource/references/customers.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/references/customers.py",
      "bytes": 2147,
      "lang": "markdown",
      "content": "\"\"\"Customers router: async session per request, eager loads, explicit commits.\"\"\"\n\nfrom fastapi import APIRouter, HTTPException, status\nfrom pydantic import BaseModel, ConfigDict, EmailStr, Field\nfrom sqlalchemy import select\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm import selectinload\n\nfrom app.db import SessionDep\nfrom app.models import Customer, Order\n\nrouter = APIRouter(prefix=\"/customers\", tags=[\"customers\"])\n\n\nclass OrderOut(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    total_pence: int\n\n\nclass CustomerIn(BaseModel):\n    email: EmailStr\n\n\nclass CustomerOut(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n    email: str\n    orders: list[OrderOut]\n\n\nclass OrderIn(BaseModel):\n    total_pence: int = Field(gt=0, le=10_000_000)\n\n\n@router.post(\"\", status_code=status.HTTP_201_CREATED)\nasync def create_customer(body: CustomerIn, session: SessionDep) -> CustomerOut:\n    customer = Customer(email=body.email, orders=[])\n    session.add(customer)\n    try:\n        await session.commit()\n    except IntegrityError:\n        await session.rollback()\n        raise HTTPException(status.HTTP_409_CONFLICT, \"email already registered\") from None\n    return CustomerOut.model_validate(customer)\n\n\n@router.get(\"\")\nasync def list_customers(session: SessionDep, limit: int = 50) -> list[CustomerOut]:\n    # One query for customers and one for all their orders, however many rows.\n    stmt = select(Customer).options(selectinload(Customer.orders)).order_by(Customer.id).limit(min(limit, 200))\n    customers = (await session.scalars(stmt)).all()\n    return [CustomerOut.model_validate(c) for c in customers]\n\n\n@router.post(\"/{customer_id}/orders\", status_code=status.HTTP_201_CREATED)\nasync def add_order(customer_id: int, body: OrderIn, session: SessionDep) -> OrderOut:\n    if await session.get(Customer, customer_id) is None:\n        raise HTTPException(status.HTTP_404_NOT_FOUND, \"customer not found\")\n    order = Order(customer_id=customer_id, total_pence=body.total_pence)\n    session.add(order)\n    await session.commit()\n    return OrderOut.model_validate(order)\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
      "role": "reference",
      "description": "Reviewed Alembic migration with a working downgrade.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
        "codex": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
        "cursor": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
        "copilot": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
      "bytes": 1167,
      "lang": "markdown",
      "content": "\"\"\"customers and orders\n\nRevision ID: 0001\nRevises:\nCreate Date: 2026-10-17\n\"\"\"\n\nimport sqlalchemy as sa\nfrom alembic import op\n\nrevision = \"0001\"\ndown_revision = None\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade() -> None:\n    op.create_table(\n        \"customers\",\n        sa.Column(\"id\", sa.Integer(), nullable=False),\n        sa.Column(\"email\", sa.String(length=254), nullable=False),\n        sa.PrimaryKeyConstraint(\"id\"),\n        sa.UniqueConstraint(\"email\"),\n    )\n    op.create_table(\n        \"orders\",\n        sa.Column(\"id\", sa.Integer(), nullable=False),\n        sa.Column(\"customer_id\", sa.Integer(), nullable=False),\n        sa.Column(\"total_pence\", sa.Integer(), nullable=False),\n        sa.Column(\"created_at\", sa.DateTime(), server_default=sa.text(\"(CURRENT_TIMESTAMP)\"), nullable=False),\n        sa.ForeignKeyConstraint([\"customer_id\"], [\"customers.id\"], ondelete=\"CASCADE\"),\n        sa.PrimaryKeyConstraint(\"id\"),\n    )\n    op.create_index(\"ix_orders_customer_id\", \"orders\", [\"customer_id\"])\n\n\ndef downgrade() -> None:\n    op.drop_index(\"ix_orders_customer_id\", table_name=\"orders\")\n    op.drop_table(\"orders\")\n    op.drop_table(\"customers\")\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
      "role": "reference",
      "description": "Async Alembic env.py reading DATABASE_URL.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/references/alembic_env.py",
        "codex": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
        "cursor": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
        "copilot": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/references/alembic_env.py",
      "bytes": 1443,
      "lang": "markdown",
      "content": "\"\"\"Alembic env.py for an async engine (from `alembic init -t async`), wired to the app's models.\n\nThe URL comes from DATABASE_URL, never from alembic.ini, so no credentials are committed.\n\"\"\"\n\nimport asyncio\nimport os\n\nfrom alembic import context\nfrom sqlalchemy import pool\nfrom sqlalchemy.engine import Connection\nfrom sqlalchemy.ext.asyncio import create_async_engine\n\nimport app.models  # noqa: F401  (registers every model on Base.metadata)\nfrom app.db import Base\n\ntarget_metadata = Base.metadata\nURL = os.environ.get(\"DATABASE_URL\", \"sqlite+aiosqlite:///./app.db\")\n\n\ndef run_migrations_offline() -> None:\n    context.configure(url=URL, target_metadata=target_metadata, literal_binds=True, render_as_batch=True)\n    with context.begin_transaction():\n        context.run_migrations()\n\n\ndef do_run_migrations(connection: Connection) -> None:\n    # render_as_batch lets ALTER-style changes work on SQLite; harmless elsewhere.\n    context.configure(connection=connection, target_metadata=target_metadata, render_as_batch=True)\n    with context.begin_transaction():\n        context.run_migrations()\n\n\nasync def run_migrations_online() -> None:\n    engine = create_async_engine(URL, poolclass=pool.NullPool)\n    async with engine.connect() as connection:\n        await connection.run_sync(do_run_migrations)\n    await engine.dispose()\n\n\nif context.is_offline_mode():\n    run_migrations_offline()\nelse:\n    asyncio.run(run_migrations_online())\n"
    },
    {
      "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
      "role": "reference",
      "description": "Async tests with a query-count N+1 guard.",
      "skill": "async-sqlalchemy-resource",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/async-sqlalchemy-resource/references/test_customers.py",
        "codex": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
        "cursor": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
        "gemini-cli": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
        "antigravity-cli": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
        "copilot": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/latest/skills/async-sqlalchemy-resource/references/test_customers.py",
      "bytes": 3309,
      "lang": "markdown",
      "content": "\"\"\"Async API tests: httpx.AsyncClient over ASGITransport, a fresh in-memory database\nper test, and a query counter that catches N+1 loads.\"\"\"\n\nfrom collections.abc import AsyncIterator\n\nimport pytest\nfrom httpx import ASGITransport, AsyncClient\nfrom sqlalchemy import event, select\nfrom sqlalchemy.exc import InvalidRequestError\nfrom sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine\nfrom sqlalchemy.pool import StaticPool\n\nfrom app.db import Base, get_session\nfrom app.main import app\nfrom app.models import Customer\n\npytestmark = pytest.mark.anyio\n\n\n@pytest.fixture\ndef anyio_backend() -> str:\n    return \"asyncio\"\n\n\n@pytest.fixture\nasync def db() -> AsyncIterator[dict]:\n    engine = create_async_engine(\"sqlite+aiosqlite://\", poolclass=StaticPool)\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    factory = async_sessionmaker(engine, expire_on_commit=False)\n    queries: list[str] = []\n    event.listen(engine.sync_engine, \"before_cursor_execute\", lambda *a: queries.append(a[2]))\n\n    async def override() -> AsyncIterator:\n        async with factory() as session:\n            yield session\n\n    app.dependency_overrides[get_session] = override\n    yield {\"factory\": factory, \"queries\": queries}\n    app.dependency_overrides.clear()\n    await engine.dispose()\n\n\n@pytest.fixture\nasync def client(db: dict) -> AsyncIterator[AsyncClient]:\n    async with AsyncClient(transport=ASGITransport(app=app), base_url=\"http://test\") as c:\n        yield c\n\n\nasync def test_create_and_list_without_n_plus_one(client: AsyncClient, db: dict) -> None:\n    for i in range(5):\n        r = await client.post(\"/customers\", json={\"email\": f\"c{i}@example.com\"})\n        assert r.status_code == 201\n        await client.post(f\"/customers/{r.json()['id']}/orders\", json={\"total_pence\": 1000 + i})\n    db[\"queries\"].clear()\n    r = await client.get(\"/customers\")\n    assert r.status_code == 200\n    assert [len(c[\"orders\"]) for c in r.json()] == [1, 1, 1, 1, 1]\n    selects = [q for q in db[\"queries\"] if q.lstrip().upper().startswith(\"SELECT\")]\n    assert len(selects) == 2, selects\n\n\nasync def test_duplicate_email_is_409(client: AsyncClient) -> None:\n    assert (await client.post(\"/customers\", json={\"email\": \"a@example.com\"})).status_code == 201\n    r = await client.post(\"/customers\", json={\"email\": \"a@example.com\"})\n    assert r.status_code == 409\n\n\nasync def test_order_for_missing_customer_is_404(client: AsyncClient) -> None:\n    r = await client.post(\"/customers/999/orders\", json={\"total_pence\": 500})\n    assert r.status_code == 404\n\n\nasync def test_bad_total_is_422(client: AsyncClient) -> None:\n    r = await client.post(\"/customers\", json={\"email\": \"b@example.com\"})\n    bad = await client.post(f\"/customers/{r.json()['id']}/orders\", json={\"total_pence\": 0})\n    assert bad.status_code == 422\n\n\nasync def test_lazy_load_raises_instead_of_hiding_a_query(client: AsyncClient, db: dict) -> None:\n    await client.post(\"/customers\", json={\"email\": \"lazy@example.com\"})\n    async with db[\"factory\"]() as session:\n        customer = (await session.scalars(select(Customer))).one()\n        with pytest.raises(InvalidRequestError):\n            _ = customer.orders\n        await session.refresh(customer, [\"orders\"])\n        assert customer.orders == []\n"
    }
  ],
  "requires": {
    "tools": [
      "uv",
      "python3"
    ],
    "env": [],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "antigravity-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/1",
    "verdict": "clean",
    "network": true,
    "scripts": [],
    "notes": "uv downloads FastAPI, SQLAlchemy, Alembic and test packages from PyPI. Database URLs come from DATABASE_URL; the examples use local SQLite files."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-17",
  "published": "2026-10-17",
  "updated": "2026-10-17",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "antigravity-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 1,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://loadout.jnw-it.com/loadouts/fastapi-async-sqlalchemy/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/fastapi-async-sqlalchemy.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-17T20:59:33Z",
    "commit": "08c3710727a4d6fc320a98ecf32bdc0a780eaa6c",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 10 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 10 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 6 agents; layouts re-discovered",
        "details": [
          "Claude Code: 9 files, 1 skill in .claude/skills/",
          "Codex CLI: 8 files, 1 skill in .agents/skills/",
          "Cursor: 8 files, 1 skill in .agents/skills/",
          "Gemini CLI: 9 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 8 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 8 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell or hidden text",
        "details": [
          "Scanner intake-precheck/1: verdict clean; network no (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "1 reference between files resolves for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "10 files, 15.3 KB total; largest skills/async-sqlalchemy-resource/references/test_customers.py 3.2 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by Agent Loadout; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "The async-sqlalchemy-resource skill's model, router, migration and Alembic env drop into a FastAPI app on SQLAlchemy 2.1.5: five async tests pass (including a two-SELECT N+1 guard and lazy=\"raise\"), ruff is clean, the migration upgrades, downgrades and matches the models, alembic check catches a dri",
        "details": [
          "Applied 17 loadout files at every agent's paths",
          "✓ place the skill's files: bash -c R=.agents/skills/async-sqlalchemy-resource/references; mkdir -p tests migrations/versions && cp $R/db.py $R/models.py app/ && cp $R/customers.py app/routers/ && cp $R/test_customers.py tests/ && touch tests/__init__.py && cp $R/alembic_env.py migrations/env.py && cp $R/0001_customers_orders.py migrations/versions/ && echo placed",
          "✓ pytest (async client, N+1 guard): uv run --no-project --python 3.12 --with fastapi==0.143.3 --with sqlalchemy[asyncio]==2.1.5 --with aiosqlite==0.22.1 --with httpx==0.28.1 --with pydantic[email]==2.14.1 --with anyio==4.15.1 --with pytest==9.1.1 python -m pytest -q -p no:cacheprovider tests",
          "✓ ruff check: uvx ruff@0.17.1 check .",
          "✓ ruff format --check: uvx ruff@0.17.1 format --check .",
          "✓ migration upgrades, downgrades and matches the models: bash -c A='uv run -q --no-project --python 3.12 --with sqlalchemy[asyncio]==2.1.5 --with aiosqlite==0.22.1 --with alembic==1.20.0 --with fastapi==0.143.3 --with pydantic[email]==2.14.1 alembic'; $A upgrade head && $A downgrade base && $A upgrade head && $A current && $A check",
          "✓ alembic check catches a model change with no migration: bash -c cp app/models.py /tmp/models.$$ && sed -i 's/    total_pence: Mapped\\[int\\]/&\\n    note: Mapped[str | None]/' app/models.py && uv run -q --no-project --python 3.12 --with sqlalchemy[asyncio]==2.1.5 --with aiosqlite==0.22.1 --with alembic==1.20.0 --with fastapi==0.143.3 --with pydantic[email]==2.14.1 alembic check; echo \"exit=$?\"; cp /tmp/models.$$ app/models.py",
          "✓ plain sqlalchemy 2.1 without [asyncio] fails as AGENTS.md warns: bash -c uv run -q --no-project --python 3.12 --with sqlalchemy==2.1.5 --with aiosqlite==0.22.1 python -c 'import asyncio; from sqlalchemy.ext.asyncio import create_async_engine; from sqlalchemy import text\nasync def m():\n    async with create_async_engine(\"sqlite+aiosqlite://\").connect() as c: await c.execute(text(\"select 1\"))\nasyncio.run(m())' 2>&1 | tail -1",
          "Covers: async_sessionmaker with expire_on_commit=False and a per-request SessionDep",
          "Covers: lazy=\"raise\" relationships with selectinload: listing five customers with orders takes two SELECTs",
          "Covers: 409 on a duplicate email after rollback, 404 for a missing parent, 422 from Pydantic",
          "Covers: Async Alembic env reading DATABASE_URL; upgrade, downgrade and alembic check",
          "Covers: SQLAlchemy 2.1 needs the [asyncio] extra for greenlet"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/async-sqlalchemy-resource/SKILL.md",
          "target": ".claude/skills/async-sqlalchemy-resource/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/db.py",
          "target": ".claude/skills/async-sqlalchemy-resource/references/db.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/models.py",
          "target": ".claude/skills/async-sqlalchemy-resource/references/models.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/customers.py",
          "target": ".claude/skills/async-sqlalchemy-resource/references/customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "target": ".claude/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
          "target": ".claude/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
          "target": ".claude/skills/async-sqlalchemy-resource/references/test_customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/async-sqlalchemy-resource/SKILL.md",
          "target": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/db.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/db.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/models.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/models.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/async-sqlalchemy-resource/SKILL.md",
          "target": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/db.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/db.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/models.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/models.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/async-sqlalchemy-resource/SKILL.md",
          "target": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/db.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/db.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/models.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/models.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/async-sqlalchemy-resource/SKILL.md",
          "target": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/db.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/db.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/models.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/models.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/async-sqlalchemy-resource/SKILL.md",
          "target": ".agents/skills/async-sqlalchemy-resource/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/db.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/db.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/db.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/models.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/models.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/models.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/customers.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/0001_customers_orders.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/alembic_env.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/alembic_env.py",
          "role": "reference"
        },
        {
          "path": "skills/async-sqlalchemy-resource/references/test_customers.py",
          "target": ".agents/skills/async-sqlalchemy-resource/references/test_customers.py",
          "raw_url": "https://loadout.jnw-it.com/raw/fastapi-async-sqlalchemy/1.0.0/skills/async-sqlalchemy-resource/references/test_customers.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/fastapi-async-sqlalchemy/source.zip"
}