├── src ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── ping.py │ │ ├── models.py │ │ ├── crud.py │ │ └── notes.py │ ├── main.py │ └── db.py ├── tests │ ├── __init__.py │ ├── test_ping.py │ ├── conftest.py │ └── test_notes.py ├── requirements.txt └── Dockerfile ├── .gitignore ├── README.md ├── docker-compose.yml └── LICENSE /src/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | env -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg2-binary==2.9.5 2 | SQLAlchemy==1.4.41 3 | fastapi==0.87.0 4 | uvicorn==0.20.0 5 | 6 | # dev 7 | pytest==7.2.0 8 | httpx==0.23.1 -------------------------------------------------------------------------------- /src/app/api/ping.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter() 4 | 5 | 6 | @router.get("/ping") 7 | def pong(): 8 | return {"ping": "pong!"} 9 | -------------------------------------------------------------------------------- /src/tests/test_ping.py: -------------------------------------------------------------------------------- 1 | def test_ping(test_app): 2 | response = test_app.get("/ping") 3 | assert response.status_code == 200 4 | assert response.json() == {"ping": "pong!"} 5 | -------------------------------------------------------------------------------- /src/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from starlette.testclient import TestClient 3 | 4 | from app.main import app 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def test_app(): 9 | client = TestClient(app) 10 | yield client # testing happens here 11 | -------------------------------------------------------------------------------- /src/app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from app.api import ping, notes 4 | from app.api.models import Base 5 | from app.db import engine 6 | 7 | 8 | Base.metadata.create_all(bind=engine) 9 | 10 | app = FastAPI() 11 | 12 | 13 | app.include_router(ping.router) 14 | app.include_router(notes.router, prefix="/notes", tags=["notes"]) 15 | -------------------------------------------------------------------------------- /src/app/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from sqlalchemy import create_engine 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | 8 | DATABASE_URL = os.getenv("DATABASE_URL") 9 | 10 | engine = create_engine(DATABASE_URL) 11 | 12 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 13 | 14 | Base = declarative_base() 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Developing and Testing an API with FastAPI and Pytest 2 | 3 | Synchronous Example 4 | 5 | ## Want to use this project? 6 | 7 | Build the images and run the containers: 8 | 9 | ```sh 10 | $ docker-compose up -d --build 11 | ``` 12 | 13 | Test out the following routes: 14 | 15 | 1. [http://localhost:8003/ping](http://localhost:8003/ping) 16 | 1. [http://localhost:8003/docs](http://localhost:8003/docs) 17 | 1. [http://localhost:8003/notes](http://localhost:8003/notes) 18 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM python:3.11.0-alpine 3 | 4 | # set work directory 5 | WORKDIR /usr/src/app 6 | 7 | # set environment variables 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | ENV PYTHONUNBUFFERED 1 10 | 11 | # copy requirements file 12 | COPY ./requirements.txt /usr/src/app/requirements.txt 13 | 14 | # install dependencies 15 | RUN set -eux \ 16 | && apk add --no-cache --virtual .build-deps build-base \ 17 | openssl-dev libffi-dev gcc musl-dev python3-dev \ 18 | postgresql-dev bash \ 19 | && pip install --upgrade pip setuptools wheel \ 20 | && pip install -r /usr/src/app/requirements.txt \ 21 | && rm -rf /root/.cache/pip 22 | 23 | # copy project 24 | COPY . /usr/src/app/ 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: ./src 6 | command: | 7 | bash -c 'while !