Alembic使用 (SqlAlchemy)

Fastapi 使用ORM 使用的是SqlAlchemy,这里使用alembic进行数据库文件迁移与数据库迁移。

目录

介绍

Alembic是一种用于数据迁移和版本控制的工具,由 SQLAlchemy 项目维护,特别适用于Python编写的数据库应用程序。它允许你按照预定的数据库模型定义,在不丢失现有数据的情况下进行数据库模式的更改。

主要功能

  1. 数据库迁移:通过 Alembic 可以自动生成或手动编写数据库迁移脚本,并应用到数据库中,使得开发者能够安全地进行数据库变更。

  2. 版本控制:Alembic 允许开发者跟踪每一个数据库架构的更改,并在不同的版本之间轻松切换。

  3. 自动生成迁移文件:Alembic 能够通过 SQLAlchemy 模型的更改自动生成迁移脚本,减少手动编写 SQL 语句的需求。

  4. 支持回滚:如果在应用迁移时出现问题,Alembic 可以轻松回滚数据库到之前的版本。

  5. 与 SQLAlchemy 集成:Alembic 是 SQLAlchemy 生态的一部分,专为使用 SQLAlchemy ORM 的应用程序设计,但它也可以用于非 ORM 项目。

工作原理

Alembic 的核心概念是迁移脚本 (Migration Scripts)。每个迁移脚本代表数据库的一次结构变更,例如创建新表、删除表、修改列等。Alembic 会将这些迁移脚本存储在一个版本控制的目录中,并通过维护数据库中的一张特殊表(称为 alembic_version)来记录应用了哪些迁移脚本。

每个迁移脚本包含两个关键函数:

• upgrade():用于应用数据库结构的升级操作。

• downgrade():用于回退数据库到前一个版本,撤销 upgrade() 中的更改。

alembic.ini配置信息

安装方法:

bash 复制代码
pip install alembic
在你的项目目录中,使用以下命令来初始化 Alembic:
alembic init alembic
这将创建一个 alembic 目录,其中包含 Alembic 的配置文件 alembic.ini 和迁移脚本存储目录。
sql 复制代码
生成的目录信息:
├── alembic                # 迁移脚本的根目录
│   ├── README             # 说明文件
│   ├── env.py             # 一个python文件,在调用Alembic命令时运行该脚本
│   ├── script.py.mako     # mako模板文件,用于生成新的迁移脚本文件
│   └── versions           # 存放各个版本的迁移脚本,通过revision命令生成新的迁移脚本
|------ alembic.init           # Alembic的配置文件,需要配置 sqlalchemy.url 指向自己的数据库地址

配置信息: alembic.ini

这个信息最重要的就是,指定脚本的位置信息

sql 复制代码
[alembic]
# path to migration scripts
script_location = api/alembic

alembic/env.py 文件是由 Alembic 自动生成的,当你运行 alembic init 命令时,它会自动创建这个文件。然而,env.py 需要进行一定的手动配置,以便能够与项目中的 SQLAlchemy 模型和数据库正确连接。

env.py配置信息:

  1. 数据库连接配置env.py 文件会从 alembic.ini 中读取数据库连接的配置,你也可以在代码中手动定义连接。

  2. 上下文配置:用于配置 Alembic 的上下文,包括是否使用 SQLAlchemy 的 MetaData 对象来自动检测数据库模型的更改。

  3. 运行模式env.py 文件支持两种运行模式:

线上模式:适用于迁移脚本直接运行在生产数据库中。

离线模式:用于生成 SQL 语句而不实际对数据库进行更改

下面是一个env.py的代码示例:

python 复制代码
import sys
from logging.config import fileConfig
from pathlib import Path

from alembic import context
from sqlalchemy import pool
from sqlalchemy.ext.asyncio import create_async_engine

# 将项目根目录添加到 sys.path
sys.path.append(str(Path(__file__).resolve().parents[1]))

# 从环境变量加载设置
from api.config import settings

# 读取 alembic 配置文件中的配置
config = context.config
fileConfig(config.config_file_name)

# 加载所有模型
from api.models import load_all_models

load_all_models()

from api.extensions.database import Base  # 在加载所有模型后导入 Base

# 配置目标元数据
target_metadata = Base.metadata
print(f"Loaded models: {Base.metadata.tables.keys()}")  # test models

# 不考虑以下表的迁移,因为fastapi 和django公用数据库的话

def include_object(object, name, type_, reflected, compare_to):
    if name in ['django_session', 'auth_user', 'django_admin_log', 'auth_user_user_permissions',
                'auth_user_groups', 'auth_group_permissions', 'auth_group', 'auth_permission',
                'django_content_type', 'django_migrations']:
        return False
    return True

def run_migrations_offline():
    """Run migrations in 'offline' mode."""
    url = settings.DATABASE_URL
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
        include_object=include_object
    )

    with context.begin_transaction():
        context.run_migrations()

def do_run_migrations(connection):
    context.configure(connection=connection, target_metadata=target_metadata,
                      include_object=include_object)

    with context.begin_transaction():
        context.run_migrations()

def run_migrations_online():
    """Run migrations in 'online' mode."""
    connectable = create_async_engine(settings.DATABASE_URL, poolclass=pool.NullPool)

    async def async_run_migrations():
        async with connectable.connect() as connection:
            await connection.run_sync(do_run_migrations)

    import asyncio
    asyncio.run(async_run_migrations())

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

数据池连接

需要配置数据池的相关代码:

使用 SQLAlchemy 的异步扩展 sqlalchemy.ext.asyncio 来处理与数据库的连接和会话管理。它主要负责创建数据库引擎、会话(Session)以及提供上下文管理器来**【待完善知识】**简化数据库事务操作。

关于DATABASE_URL的设置:

python 复制代码
class Settings:
    def __init__(self):
    
        @property
    def DATABASE_URL(self) -> str:
        db_url = f"postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
        return db_url
python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

from api.config import settings

Base = declarative_base()
# Base 是一个由 declarative_base() 生成的类。你可以通过继承 Base 来定义所有的数据库模型。所有的模型都会继承这个 Base,从而将它们与数据库的表结构关联起来。
# declarative_base() 是 SQLAlchemy 中的一个函数,用于创建一个基础类,所有的模型类都继承自这个基础类。 
# declarative_base() 的作用是提供了一种面向对象的方式来声明数据库表结构,使得每个继承它的类都能够映射到数据库的一个表。
# 创建异步引擎时传递连接池的配置
engine = create_async_engine(
    settings.DATABASE_URL,
    echo=False,  # 不输出日志
    pool_size=50,  # 连接池大小
    max_overflow=20,  # 最大溢出连接数
    pool_timeout=60,  # 等待连接的超时时间
    pool_recycle=3600,  # 连接重置时间,防止闲置连接超时
)

AsyncSessionLocal = async_sessionmaker(
    bind=engine,  # 这里依然使用 AsyncEngine
    expire_on_commit=False
)

@asynccontextmanager
async def get_session_async_with():
    async with AsyncSessionLocal() as session:
        yield session

models建立

这里有几个注意点:

首先是load_all_models 的用法:

python 复制代码
#api/models/__init__.py:
import pkgutil
from pathlib import Path

def load_all_models() -> None:
    """Load all models from this folder."""
    package_dir = Path(__file__).resolve().parent #将这个路径解析为绝对路径,parent 则表示当前文件的父目录,也就是 models 目录所在的位置。
    modules = pkgutil.walk_packages(
        path=[str(package_dir)],
        prefix="models.",
    )
    # pkgutil.walk_packages() 是一个 Python 内置的工具,用于遍历指定路径下的所有模块。
    for module in modules:
        print(module)
        __import__(module.name)  # noqa: WPS421

# 功能:这段代码的功能是自动遍历并动态导入 models 目录下的所有 Python 模块。
# 通过动态导入,所有的模块可以在运行时被加载并可用,而不需要手动在代码中逐个导入。

命令迁移

先在根目录(说白了就是你的.ini 所在路径)

python 复制代码
export PYTHONPATH=$(pwd) 

【初始化Alembic(未动数据库就不要使用这个命令,注意这个命令是在你数据库为空的情况下执行)
alembic init alembic   】

生成初始化迁移脚本
alembic revision --autogenerate -m "Initial migration"
【-m指定此次迁移的注释, alembic会根据这个注释在versions目录生成一个py文件, 用于记录操作】
应用迁移
alembic upgrade head

注意需要和数据库的版本对应

使用alembic进行数据库迁移必须当心数据库的alembic版本和自己的迁移文件版本是否对应,否则会出现问题
要在一个新的数据库上进行Alembic迁移并确保数据库模型与代码中的模型同步:
1 删除alembic/versions文件夹中的所有版本文件
2 执行上面的迁移脚本命令和应用迁移命令

查看版本历史:

python 复制代码
alembic history --verbose

# 向上切换
alembic upgrade <version>
 
# 向下切换
alembic downgrade <version>

切换到最新版本
alembic upgrade head
alembic downgrade base

迁移独立

因为使用alembic 会涉及到不同的环境对其的影响:

可通过配置 Alembic 的 alembic.ini 或者在运行 Alembic 命令时指定对应的环境配置来实现区分线上环境和测试环境的版本文件夹。这可以通过设置 Alembic 的 --config 参数来指定不同的配置文件,或者通过自定义 Alembic 环境脚本来实现。

1.使用不同的 Alembic 配置文件

你可以为不同的环境(比如线上和测试)创建不同的 Alembic 配置文件,如 alembic_online.ini 和 alembic_test.ini,在这些配置文件中可以指定不同的 versions 文件夹:

• alembic_online.ini

python 复制代码
[alembic]
script_location = alembic_online
versions = alembic_online/versions

• alembic_test.ini

python 复制代码
[alembic]
script_location = alembic_test
versions = alembic_test/versions

你运行 Alembic 的迁移命令时,使用 --config 参数来指定配置文件:

python 复制代码
alembic --config alembic_online.ini upgrade head
alembic --config alembic_test.ini upgrade head

2.env.py 中读取环境变量

修改 alembic/env.py 文件,动态读取环境变量来选择版本文件夹。这可以使 Alembic 在不同的环境中自动选择正确的路径。

在 alembic/env.py 中增加逻辑,如下:

python 复制代码
import os

# 根据环境变量选择不同的版本路径
env = os.getenv("ALEMBIC_ENV", "default")

if env == "production":
    version_location = "alembic_online/versions"
elif env == "testing":
    version_location = "alembic_test/versions"
else:
    version_location = "alembic/versions"

config.set_main_option("version_locations", version_location)
相关推荐
Hacker_LaoYi18 分钟前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀20 分钟前
Redis梳理
数据库·redis·缓存
独行soc21 分钟前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天1 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
远游客07131 小时前
centos stream 8下载安装遇到的坑
linux·服务器·centos
工业甲酰苯胺1 小时前
分布式系统架构:服务容错
数据库·架构
独行soc2 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain2 小时前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship2 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站2 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle