FastAPI+PostgreSQL+SQLAlchemy2.0+asyncpg异步+alembic数据迁移==项目开发

1. 项目依赖

bash 复制代码
# fastapi相关依赖
pip install "fastapi[standard]" uvicorn

# ORM  sqlalchemy依赖
pip install sqlalchemy

# 连接postgresql数据库中间键依赖
pip install asyncpg

# 数据迁移
pip install alembic

2. 项目结构

3. 数据库链接

python 复制代码
# core/db.py
from datetime import datetime as dt
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession,AsyncEngine, async_sessionmaker
from sqlalchemy.orm import mapped_column, Mapped, DeclarativeBase
from sqlalchemy import Integer, DateTime, func

DATABASE_URL = "postgresql+asyncpg://postgres:123456@127.0.0.1:5432/fastapi"

async_engine: AsyncEngine = create_async_engine(
        DATABASE_URL,
        echo=True,     # 生产环境关闭False,sql注入输出
        pool_size=10,  # 基础连接数
        max_overflow=20,  # 突发溢出连接数
        pool_recycle=3600,
)

AsyncSessionLocal = async_sessionmaker(
    async_engine,
    class_=AsyncSession,
    expire_on_commit=False,  # 提交后不过期对象,避免懒加载
)

async def get_db():
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise
        finally:
            await session.close()

class DataBase(DeclarativeBase):
        id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
        create_id: Mapped[int] = mapped_column(Integer, default=0)
        update_id: Mapped[int] = mapped_column(Integer, default=0)
        update_datetime: Mapped[dt] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
        create_datetime: Mapped[dt] = mapped_column(DateTime, server_default=func.now())
        active: Mapped[bool] = mapped_column(Integer, default=0)

4. 数据模型

python 复制代码
# apps/user/models.py

from sqlalchemy.orm import mapped_column, Mapped
from sqlalchemy import String, Text
from core.db import DataBase

class User(DataBase):
    __tablename__ = 'auth_user'
    username: Mapped[str] = mapped_column(Text, nullable=False, index=True)
    display_name: Mapped[str] = mapped_column(Text)
    password: Mapped[str] = mapped_column(String(255))

5. alemtic数据迁移

5.1 alemtic初始化

bash 复制代码
# alembic初始化,会创建alembic目录,和alembic.ini配置文件
alembic init alembic

5.2 alembic配置文件

bash 复制代码
[alembic]
script_location = %(here)s/alembic
prepend_sys_path = .
path_separator = os
sqlalchemy.url = postgresql+asyncpg://postgres:123456@127.0.0.1:5432/fastapi
[post_write_hooks]
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

5.3 alembic-python入口文件

python 复制代码
# alemtic/env.py
import asyncio
from logging.config import fileConfig
from sqlalchemy import pool
from alembic import context
from core.db import DataBase
from sqlalchemy.ext.asyncio import async_engine_from_config

config = context.config
if config.config_file_name is not None:
    fileConfig(config.config_file_name)
target_metadata = DataBase.metadata

from apps.user import models   # 每个模型文件都要在此导入,否则不能迁移无效

def run_migrations_offline() -> None:
    """离线模式:生成 SQL 脚本(不连接数据库)"""
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
        compare_type=True,        # 检测字段类型变化
        compare_server_default=True,  # 检测默认值变化
    )
    with context.begin_transaction():
        context.run_migrations()

def do_run_migrations(connection):
    context.configure(
        connection=connection,
        target_metadata=target_metadata,
        compare_type=True,
        compare_server_default=True,
        # 软删除场景:排除 is_deleted=True 的数据(迁移时不管数据)
    )
    with context.begin_transaction():
        context.run_migrations()

async def run_async_migrations() -> None:
    """在线异步模式:直接连接数据库执行迁移"""

    connectable = async_engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,  # 迁移用 NullPool,不需要连接池
    )
    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)
    await connectable.dispose()

def run_migrations_online() -> None:
    asyncio.run(run_async_migrations())

if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

5.4 alembic常用命令

bash 复制代码
# 初始化
alembic init alembic

# 生成新迁移脚本
alembic revision --autogenerate -m "init"

# 升级到最新版本
alembic upgrade head

# 回退一个版本
alembic downgrade -1

# 查看当前版本
alembic current

# 查看历史链
alembic history

6. Pydantic 数据验证模型

python 复制代码
# apps/user/schemas.py
from pydantic import BaseModel

class UserBase(BaseModel):
    username: str                         # 必填参数
    display_name: str
    password: str

class CreateUser(UserBase)
    email_address: str | None = None

class UpdateUser(UserBase)
    id: int
    email_address: str | None = None      # 可填参数,默认值为None
    username: str | None = None
    display_name: str | None = None
    password: str | None = None

7. CURD增删改查操作

python 复制代码
# apps/user/curd.py
from typing import Annotated
from fastapi import Form
from sqlalchemy.ext.asyncio import AsyncSession
from apps.user.models import User
from apps.user.schemas import CreateUser, UpdateUser

async def async_create_user(form_data: CreateUser, session: AsyncSession) -> User:
    user = User(
        username=form_data.username,
        email_address=form_data.email_address,
        display_name=form_data.display_name,
        password=form_data.password
    )
    async with session.begin():
        session.add_all([user])
    return user

async def async_update_user(form_data: UpdateUser, session: AsyncSession):
    stmt  = select(User).where(User.id == data.pk)
    result = await session.execute(stmt)
    user = result.scalars().first()
    if form_data.username:
        user.username = form_data.username
    if form_data.email_address:
        user.email_address = form_data.email_address
    if data.display_name:
        user.display_name = form_data.display_name
    if data.password:
        user.password = form_data.password
    return user

8. 路由及请求处理

python 复制代码
from fastapi import APIRouter
from sqlalchemy.ext.asyncio import AsyncSession

from core.db_handle import get_db
from apps.user.schemas import CreateUser, UpdateUser
from apps.user.curd import async_create_user, async_update_user

user_router = APIRouter(prefix="/user", tags=["users"])

@user_router.post("/create_user")
async def create_user(form: CreateUser, session: AsyncSession = Depends(get_db)):
    user = await async_create_user(form, session)
    return user

@user_router.put("/update_user")
async def create_user(form: Annotated[UpdateUser, Form()], session: AsyncSession = Depends(get_db)):
    user = await async_update_user(form, session)
    return user

9. 项目主入口

python 复制代码
import uvicorn
from fastapi import FastAPI

from apps.user.router import user_router

app = FastAPI()
app.include_router(user_router)

@app.get("/")
async def root():
    return {"message": "Hello World"}


if __name__ == "__main__":

    uvicorn.run(app, host="127.0.0.1", port=8000)
相关推荐
郭老二5 小时前
【Python】Web框架 FastAPI 详解
python·fastapi
Maiko Star1 天前
FastAPI 进阶三部曲:中间件、依赖注入与 ORM 实战
python·中间件·fastapi
一个王同学2 天前
从零到一 | CV转多模态大模型 | week19 | 基于 FastAPI 和 vLLM 的多模态大模型部署
人工智能·深度学习·计算机视觉·fastapi·改行学it·vllm
像风一样自由20202 天前
从本地到公网:Windows 下使用 Cloudflare Quick Tunnel 与 Natapp 联调 FastAPI
windows·fastapi
心如鉄补2 天前
FastAPI Agent 函数调用实战:我让 AI 学会了“自己动手查天气“
人工智能·fastapi
ye小杰榨 问鼎中原ZP2 天前
初探:用 FastAPI 搭建你的第一个 AI Agent 接口
人工智能·fastapi
雨辰AI3 天前
全集实战:企业级大模型服务化部署全栈指南|FastAPI 封装 + Nginx 负载均衡 + 高可用架构 从单机到生产一步到位
人工智能·ai·负载均衡·fastapi·ai编程
李昊哲小课3 天前
FastAPI 猫咖预约系统 API
人工智能·python·fastapi
IT小盘3 天前
08-FastAPI加MySQL实现AI对话记录持久化
android·mysql·fastapi