|
1 | | -## Small FastAPI template, all boring and tedious things covered |
| 1 | +## Minimal async FastAPI + postgresql template |
2 | 2 |
|
3 | 3 |  |
4 | 4 |
|
@@ -57,3 +57,159 @@ This project is heavily base on official template https://github.com/tiangolo/fu |
57 | 57 | ## Step by step example |
58 | 58 |
|
59 | 59 | I always enjoy to to have some kind of example in templates (even if I don't like it much, _some_ parts may be useful and save my time...), so let's create `POST` endpoint for creating dogs. |
| 60 | + |
| 61 | +### 1. Add `HappyDog` model |
| 62 | + |
| 63 | +```python |
| 64 | +# /app/models.py |
| 65 | +(...) |
| 66 | + |
| 67 | +class HappyDog(Base): |
| 68 | + __tablename__ = "happy_dog" |
| 69 | + id = Column(Integer, primary_key=True, index=True) |
| 70 | + puppy_name = Column(String(500)) |
| 71 | + puppy_age = Column(Integer) |
| 72 | +``` |
| 73 | + |
| 74 | +### 2. Create and apply alembic migrations |
| 75 | + |
| 76 | +```bash |
| 77 | +# Run |
| 78 | +alembic revision --autogenerate -m "add_happy_dog" |
| 79 | + |
| 80 | +# Somethig like `YYYY-MM-DD-....py` will appear in `/alembic/versions` folder |
| 81 | + |
| 82 | +alembic upgrade head |
| 83 | + |
| 84 | +# (...) |
| 85 | +# INFO [alembic.runtime.migration] Running upgrade cefce371682e -> 038f530b0e9b, add_happy_dog |
| 86 | +``` |
| 87 | + |
| 88 | +PS. Note, alembic is configured in a way that it work with async setup and also detects specific column changes. |
| 89 | + |
| 90 | +### 3. Create schemas |
| 91 | + |
| 92 | +```python |
| 93 | +# /app/schemas/happy_dog.py |
| 94 | + |
| 95 | +from typing import Optional |
| 96 | + |
| 97 | +from pydantic import BaseModel |
| 98 | + |
| 99 | + |
| 100 | +class BaseHappyDog(BaseModel): |
| 101 | + puppy_name: str |
| 102 | + puppy_age: Optional[int] |
| 103 | + |
| 104 | + |
| 105 | +class CreateHappyDog(BaseHappyDog): |
| 106 | + pass |
| 107 | + |
| 108 | + |
| 109 | +class HappyDog(BaseHappyDog): |
| 110 | + id: int |
| 111 | + |
| 112 | +``` |
| 113 | + |
| 114 | +Then add it to schemas `__init__.py` |
| 115 | + |
| 116 | +```python |
| 117 | +# /app/schemas/__init__.py |
| 118 | + |
| 119 | +from .token import Token, TokenPayload, TokenRefresh |
| 120 | +from .user import User, UserCreate, UserUpdate |
| 121 | +from .happy_dog import HappyDog, CreateHappyDog |
| 122 | +``` |
| 123 | + |
| 124 | +### 4. Create endpoint |
| 125 | + |
| 126 | +```python |
| 127 | +# /app/api/endpoints/dogs.py |
| 128 | + |
| 129 | +from typing import Any |
| 130 | +from fastapi import APIRouter, Depends |
| 131 | +from sqlalchemy.ext.asyncio import AsyncSession |
| 132 | + |
| 133 | +from app import models, schemas |
| 134 | +from app.api import deps |
| 135 | + |
| 136 | +router = APIRouter() |
| 137 | + |
| 138 | + |
| 139 | +@router.post("/", response_model=schemas.HappyDog, status_code=201) |
| 140 | +async def create_happy_dog( |
| 141 | + dog_create: schemas.CreateHappyDog, |
| 142 | + session: AsyncSession = Depends(deps.get_session), |
| 143 | + current_user: models.User = Depends(deps.get_current_active_user), |
| 144 | +) -> Any: |
| 145 | + """ |
| 146 | + Creates new happy dog. Only for logged users. |
| 147 | + """ |
| 148 | + new_dog = models.HappyDog( |
| 149 | + puppy_name=dog_create.puppy_name, puppy_age=dog_create.puppy_age |
| 150 | + ) |
| 151 | + |
| 152 | + session.add(new_dog) |
| 153 | + await session.commit() |
| 154 | + await session.refresh(new_dog) |
| 155 | + |
| 156 | + return new_dog |
| 157 | + |
| 158 | +``` |
| 159 | + |
| 160 | +Also, add it to router |
| 161 | + |
| 162 | +```python |
| 163 | +# /app/api/api.py |
| 164 | + |
| 165 | +from fastapi import APIRouter |
| 166 | + |
| 167 | +from app.api.endpoints import auth, users, dogs |
| 168 | + |
| 169 | +api_router = APIRouter() |
| 170 | +api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) |
| 171 | +api_router.include_router(users.router, prefix="/users", tags=["users"]) |
| 172 | +# new content below |
| 173 | +api_router.include_router(dogs.router, prefix="/dogs", tags=["dogs"]) |
| 174 | + |
| 175 | +``` |
| 176 | + |
| 177 | +### 5. Test it simply |
| 178 | + |
| 179 | +```python |
| 180 | +# /app/tests/test_dogs.py |
| 181 | + |
| 182 | +import pytest |
| 183 | +from httpx import AsyncClient |
| 184 | +from app.models import User |
| 185 | + |
| 186 | +pytestmark = pytest.mark.asyncio |
| 187 | + |
| 188 | + |
| 189 | +async def test_dog_endpoint(client: AsyncClient, default_user: User): |
| 190 | + # better to create fixture auth_client or similar than repeat code with access_token |
| 191 | + access_token = await client.post( |
| 192 | + "/auth/access-token", |
| 193 | + data={ |
| 194 | + "username": "user@email.com", |
| 195 | + "password": "password", |
| 196 | + }, |
| 197 | + headers={"Content-Type": "application/x-www-form-urlencoded"}, |
| 198 | + ) |
| 199 | + assert access_token.status_code == 200 |
| 200 | + access_token = access_token.json()["access_token"] |
| 201 | + |
| 202 | + puppy_name = "Sonia" |
| 203 | + puppy_age = 6 |
| 204 | + |
| 205 | + create_dog = await client.post( |
| 206 | + "/dogs/", |
| 207 | + json={"puppy_name": puppy_name, "puppy_age": puppy_age}, |
| 208 | + headers={"Authorization": f"Bearer {access_token}"}, |
| 209 | + ) |
| 210 | + assert create_dog.status_code == 201 |
| 211 | + create_dog_json = create_dog.json() |
| 212 | + assert create_dog_json["puppy_name"] == puppy_name |
| 213 | + assert create_dog_json["puppy_age"] == puppy_age |
| 214 | + |
| 215 | +``` |
0 commit comments