Skip to content

Latest commit

Β 

History

21 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FastAPI Project Setup Guide using uv πŸš€βœ¨πŸ’‘

I've been tinkering with FastAPI and have found a couple of useful repos. However there are so many different tools and styles in use, I found it hard to bootstrap and start my projects. This guide provides a step-by-step process for scaffolding a FastAPI project using fastapi-users for authentication and authorization, PostgreSQL as the database, Alembic for migrations, and uv for package management. I will try to build this out over time as I incorporate additional tools/techniques. I'm learning a lot from the zhanymkanov/fastapi-best-practices repository. πŸŒŸπŸ”§πŸ“„


1. Project Initialization πŸ› οΈπŸ“‚πŸ”§

1.1 Install uv πŸ–₯οΈπŸ’»βœ¨

Ensure that Python 3.7+ is installed, then globally install uv:

pip install uv

Verify the installation:

uv --version

1.2 Initialize a New Project πŸš€πŸ“‚βš‘

Create a new directory for the project and initialize it using uv:

mkdir fastapi-bootstrap
cd fastapi-bootstrap
uv init

This command creates a pyproject.toml file for managing dependencies. πŸ“πŸ“¦πŸ”§

1.3 Add Required Dependencies πŸ§°πŸ“¦βš™οΈ

Add the following dependencies using uv:

uv add fastapi uvicorn psycopg2-binary python-dotenv alembic passlib bcrypt
uv add fastapi-users --extra sqlalchemy

uv will install and pin the specified versions of the packages while updating pyproject.toml automatically. πŸ“‹βœ¨πŸ”’


2. Directory Structure πŸ“‚πŸ—οΈβœ¨

Here’s the recommended directory structure based on best practices:

fastapi-project/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ main.py
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ config.py
β”‚   β”‚   └── constants.py
β”‚   β”‚   └── database.py
β”‚   β”‚   └── exceptions.py
β”‚   β”‚   └── security.py
β”‚   β”œβ”€β”€ users/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ auth.py
β”‚   β”‚   β”œβ”€β”€ manager.py
β”‚   β”‚   β”œβ”€β”€ models.py
β”‚   β”‚   └── schemas.py
β”‚   └── api/
β”‚       β”œβ”€β”€ __init__.py
β”‚       └── routes.py
β”œβ”€β”€ alembic/
β”‚   └── versions/
β”œβ”€β”€ alembic.ini
└── .env

3. Environment Variables (.env) πŸŒπŸ”πŸ“„

Create a .env file to store environment variables:

DATABASE_URL=postgresql://username:password@localhost:5432/mydatabase
SECRET_KEY=your_secret_key_here

4. Database Configuration (app/core/database.py) πŸ—„οΈβš™οΈπŸ”—

from typing import Any
from collections.abc import AsyncGenerator
from sqlalchemy import (
    CursorResult,
    Insert,
    MetaData,
    Select,
    Update
)
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncSession, async_sessionmaker, create_async_engine
from .config import settings
from .constants import DB_NAMING_CONVENTION

Base: DeclarativeMeta = declarative_base()

DATABASE_URL = str(settings.DATABASE_ASYNC_URL)

engine = create_async_engine(
    DATABASE_URL,
    pool_size = settings.DATABASE_POOL_SIZE,
    pool_recycle = settings.DATABASE_POOL_TTL,
    pool_pre_ping = settings.DATABASE_POOL_PRE_PING
)

async_session_maker = async_sessionmaker(engine, expire_on_commit=False)

metadata = MetaData(naming_convention=DB_NAMING_CONVENTION)

async def create_db_and_tables():
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
    async with async_s

5. User Model and Schemas πŸ‘€πŸ“„πŸ”§

5.1 User Schemas (app/users/schemas.py) πŸ§ΎπŸ“šβœ¨

from fastapi_users import schemas

class UserRead(schemas.BaseUser[int]):
    pass

class UserCreate(schemas.BaseUserCreate):
    pass

class UserUpdate(schemas.BaseUserUpdate):
    pass

5.2 User Table (app/users/models.py) πŸ› οΈπŸ“ŠπŸ—„οΈ

from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession 
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase

from ..core.database import Base, get_async_session

class User(SQLAlchemyBaseUserTableUUID, Base):
    pass

async def get_user_db(session: AsyncSession = Depends(get_async_session)):
    yield SQLAlchemyUserDatabase(session, User)

6. User Manager (app/users/manager.py) πŸ‘¨β€πŸ’»πŸ”‘πŸ“œ

from fastapi_users import BaseUserManager, IntegerIDMixin
from .schemas import UserCreate
from ..db.models import User
from ..db.base import SessionLocal
import os

SECRET_KEY = os.getenv("SECRET_KEY")

class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
    reset_password_token_secret = SECRET_KEY
    verification_token_secret = SECRET_KEY

    async def on_after_register(self, user: User, request=None):
        print(f"User {user.email} has registered.")

def get_user_manager():
    db = SessionLocal()
    yield UserManager(db)

7. Auth Setup (app/users/auth.py) πŸ”’πŸ”‘πŸ“‹

from fastapi import Depends
from fastapi_users import FastAPIUsers
from fastapi_users.authentication import BearerTransport, JWTStrategy, AuthenticationBackend
from .schemas import UserRead, UserCreate, UserUpdate
from .manager import get_user_manager
from ..db.models import User

SECRET_KEY = os.getenv("SECRET_KEY")

def get_jwt_strategy() -> JWTStrategy:
    return JWTStrategy(secret=SECRET_KEY, lifetime_seconds=3600)

auth_backend = AuthenticationBackend(
    name="jwt",
    transport=BearerTransport(tokenUrl="auth/jwt/login"),
    get_strategy=get_jwt_strategy,
)

fastapi_users = FastAPIUsers[User, int](
    get_user_manager,
    [auth_backend],
)

current_active_user = fastapi_users.current_user(active=True)

8. API Routes (app/api/routes.py) πŸš¦πŸ“‘πŸ”§

from fastapi import APIRouter
from ..users.auth import fastapi_users, auth_backend
from ..users.schemas import UserRead, UserCreate, UserUpdate

router = APIRouter()

# User routes
router.include_router(
    fastapi_users.get_auth_router(auth_backend),
    prefix="/auth/jwt",
    tags=["auth"],
)
router.include_router(
    fastapi_users.get_register_router(UserRead, UserCreate),
    prefix="/auth",
    tags=["auth"],
)
router.include_router(
    fastapi_users.get_users_router(UserRead, UserUpdate),
    prefix="/users",
    tags=["users"],
)

9. Main Application (app/main.py) πŸ–₯οΈπŸš€πŸ”§

from fastapi import FastAPI
from .api.routes import router as api_router
from .db.base import Base, engine

app = FastAPI()

# Create database tables
Base.metadata.create_all(bind=engine)

# Include API router
app.include_router(api_router)

10. Alembic Setup βš™οΈπŸ—‚οΈπŸ“‹

10.1 Initialize Alembic πŸ—οΈπŸ”§πŸ“

alembic init alembic

10.2 Configure Alembic πŸ› οΈπŸ—„οΈβœ¨

Edit alembic.ini and set the sqlalchemy.url to the database URL from .env. Additionally, update env.py to include the target_metadata from your models by importing the Base class and setting target_metadata = Base.metadata. This ensures Alembic can detect schema changes correctly during migrations:

sqlalchemy.url = postgresql://username:password@localhost:5432/mydatabase
from app.db.base import Base

target_metadata = Base.metadata

10.3 Create and Apply Migrations πŸš€πŸ“‹βœ¨

alembic revision --autogenerate -m "Initial migration"
alembic upgrade head

11. Run the Application πŸš€πŸ–₯️🌐

Start the FastAPI application using uv:

uv run app.main:app --reload

Visit http://127.0.0.1:8000/docs to test the API using the automatically generated interactive documentation. πŸŒπŸ“‹βœ¨


12. Managing Dependencies with uv πŸ“¦πŸ› οΈπŸ”„

Add a New Dependency βž•πŸ“¦βœ¨

uv add <package-name>

Update Dependencies πŸ”„βš™οΈπŸ“¦

uv update

Remove a Dependency βž–πŸ—‘οΈπŸ“¦

uv remove <package-name>

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages