- 新增图像生成接口,支持试用、积分和自定义API Key模式 - 实现生成图片结果异步上传至MinIO存储,带重试机制 - 优化积分预扣除和异常退还逻辑,保障用户积分准确 - 添加获取生成历史记录接口,支持时间范围和分页 - 提供本地字典配置接口,支持模型、比例、提示模板和尺寸 - 实现图片批量上传接口,支持S3兼容对象存储 feat(admin): 增加管理员角色管理与权限分配接口 - 实现角色列表查询、角色创建、更新及删除功能 - 增加权限列表查询接口 - 实现用户角色分配接口,便于统一管理用户权限 - 增加系统字典增删查改接口,支持分类过滤和排序 - 权限控制全面覆盖管理接口,保证安全访问 feat(auth): 完善用户登录注册及权限相关接口与页面 - 实现手机号验证码发送及校验功能,保障注册安全 - 支持手机号注册、登录及退出接口,集成日志记录 - 增加修改密码功能,验证原密码后更新 - 提供动态导航菜单接口,基于权限展示不同菜单 - 实现管理界面路由及日志、角色、字典管理页面访问权限控制 - 添加系统日志查询接口,支持关键词和等级筛选 feat(app): 初始化Flask应用并配置蓝图与数据库 - 创建应用程序工厂,加载配置,初始化数据库和Redis客户端 - 注册认证、API及管理员蓝图,整合路由 - 根路由渲染主页模板 - 应用上下文中自动创建数据库表,保证运行环境准备完毕 feat(database): 提供数据库创建与迁移支持脚本 - 新增数据库创建脚本,支持自动检测是否已存在 - 添加数据库表初始化脚本,支持创建和删除所有表 - 实现RBAC权限初始化,包含基础权限和角色创建 - 新增字段手动修复脚本,添加用户API Key和积分字段 - 强制迁移脚本支持清理连接和修复表结构,初始化默认数据及角色分配 feat(config): 新增系统配置参数 - 配置数据库、Redis、Session和MinIO相关参数 - 添加AI接口地址及试用Key配置 - 集成阿里云短信服务配置及开发模式相关参数 feat(extensions): 初始化数据库、Redis和MinIO客户端 - 创建全局SQLAlchemy数据库实例和Redis客户端 - 配置基于boto3的MinIO兼容S3客户端 chore(logs): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
390 lines
12 KiB
Python
390 lines
12 KiB
Python
# testing/suite/test_ddl.py
|
|
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
|
# <see AUTHORS file>
|
|
#
|
|
# This module is part of SQLAlchemy and is released under
|
|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
# mypy: ignore-errors
|
|
|
|
import random
|
|
|
|
from . import testing
|
|
from .. import config
|
|
from .. import fixtures
|
|
from .. import util
|
|
from ..assertions import eq_
|
|
from ..assertions import is_false
|
|
from ..assertions import is_true
|
|
from ..config import requirements
|
|
from ..schema import Table
|
|
from ... import CheckConstraint
|
|
from ... import Column
|
|
from ... import ForeignKeyConstraint
|
|
from ... import Index
|
|
from ... import inspect
|
|
from ... import Integer
|
|
from ... import schema
|
|
from ... import String
|
|
from ... import UniqueConstraint
|
|
|
|
|
|
class TableDDLTest(fixtures.TestBase):
|
|
__sparse_driver_backend__ = True
|
|
|
|
def _simple_fixture(self, schema=None):
|
|
return Table(
|
|
"test_table",
|
|
self.metadata,
|
|
Column("id", Integer, primary_key=True, autoincrement=False),
|
|
Column("data", String(50)),
|
|
schema=schema,
|
|
)
|
|
|
|
def _underscore_fixture(self):
|
|
return Table(
|
|
"_test_table",
|
|
self.metadata,
|
|
Column("id", Integer, primary_key=True, autoincrement=False),
|
|
Column("_data", String(50)),
|
|
)
|
|
|
|
def _table_index_fixture(self, schema=None):
|
|
table = self._simple_fixture(schema=schema)
|
|
idx = Index("test_index", table.c.data)
|
|
return table, idx
|
|
|
|
def _simple_roundtrip(self, table):
|
|
with config.db.begin() as conn:
|
|
conn.execute(table.insert().values((1, "some data")))
|
|
result = conn.execute(table.select())
|
|
eq_(result.first(), (1, "some data"))
|
|
|
|
@requirements.create_table
|
|
@util.provide_metadata
|
|
def test_create_table(self):
|
|
table = self._simple_fixture()
|
|
table.create(config.db, checkfirst=False)
|
|
self._simple_roundtrip(table)
|
|
|
|
@requirements.create_table
|
|
@requirements.schemas
|
|
@util.provide_metadata
|
|
def test_create_table_schema(self):
|
|
table = self._simple_fixture(schema=config.test_schema)
|
|
table.create(config.db, checkfirst=False)
|
|
self._simple_roundtrip(table)
|
|
|
|
@requirements.drop_table
|
|
@util.provide_metadata
|
|
def test_drop_table(self):
|
|
table = self._simple_fixture()
|
|
table.create(config.db, checkfirst=False)
|
|
table.drop(config.db, checkfirst=False)
|
|
|
|
@requirements.create_table
|
|
@util.provide_metadata
|
|
def test_underscore_names(self):
|
|
table = self._underscore_fixture()
|
|
table.create(config.db, checkfirst=False)
|
|
self._simple_roundtrip(table)
|
|
|
|
@requirements.comment_reflection
|
|
@util.provide_metadata
|
|
def test_add_table_comment(self, connection):
|
|
table = self._simple_fixture()
|
|
table.create(connection, checkfirst=False)
|
|
table.comment = "a comment"
|
|
connection.execute(schema.SetTableComment(table))
|
|
eq_(
|
|
inspect(connection).get_table_comment("test_table"),
|
|
{"text": "a comment"},
|
|
)
|
|
|
|
@requirements.comment_reflection
|
|
@util.provide_metadata
|
|
def test_drop_table_comment(self, connection):
|
|
table = self._simple_fixture()
|
|
table.create(connection, checkfirst=False)
|
|
table.comment = "a comment"
|
|
connection.execute(schema.SetTableComment(table))
|
|
connection.execute(schema.DropTableComment(table))
|
|
eq_(
|
|
inspect(connection).get_table_comment("test_table"), {"text": None}
|
|
)
|
|
|
|
@requirements.table_ddl_if_exists
|
|
@util.provide_metadata
|
|
def test_create_table_if_not_exists(self, connection):
|
|
table = self._simple_fixture()
|
|
|
|
connection.execute(schema.CreateTable(table, if_not_exists=True))
|
|
|
|
is_true(inspect(connection).has_table("test_table"))
|
|
connection.execute(schema.CreateTable(table, if_not_exists=True))
|
|
|
|
@requirements.index_ddl_if_exists
|
|
@util.provide_metadata
|
|
def test_create_index_if_not_exists(self, connection):
|
|
table, idx = self._table_index_fixture()
|
|
|
|
connection.execute(schema.CreateTable(table, if_not_exists=True))
|
|
is_true(inspect(connection).has_table("test_table"))
|
|
is_false(
|
|
"test_index"
|
|
in [
|
|
ix["name"]
|
|
for ix in inspect(connection).get_indexes("test_table")
|
|
]
|
|
)
|
|
|
|
connection.execute(schema.CreateIndex(idx, if_not_exists=True))
|
|
|
|
is_true(
|
|
"test_index"
|
|
in [
|
|
ix["name"]
|
|
for ix in inspect(connection).get_indexes("test_table")
|
|
]
|
|
)
|
|
|
|
connection.execute(schema.CreateIndex(idx, if_not_exists=True))
|
|
|
|
@requirements.table_ddl_if_exists
|
|
@util.provide_metadata
|
|
def test_drop_table_if_exists(self, connection):
|
|
table = self._simple_fixture()
|
|
|
|
table.create(connection)
|
|
|
|
is_true(inspect(connection).has_table("test_table"))
|
|
|
|
connection.execute(schema.DropTable(table, if_exists=True))
|
|
|
|
is_false(inspect(connection).has_table("test_table"))
|
|
|
|
connection.execute(schema.DropTable(table, if_exists=True))
|
|
|
|
@requirements.index_ddl_if_exists
|
|
@util.provide_metadata
|
|
def test_drop_index_if_exists(self, connection):
|
|
table, idx = self._table_index_fixture()
|
|
|
|
table.create(connection)
|
|
|
|
is_true(
|
|
"test_index"
|
|
in [
|
|
ix["name"]
|
|
for ix in inspect(connection).get_indexes("test_table")
|
|
]
|
|
)
|
|
|
|
connection.execute(schema.DropIndex(idx, if_exists=True))
|
|
|
|
is_false(
|
|
"test_index"
|
|
in [
|
|
ix["name"]
|
|
for ix in inspect(connection).get_indexes("test_table")
|
|
]
|
|
)
|
|
|
|
connection.execute(schema.DropIndex(idx, if_exists=True))
|
|
|
|
|
|
class FutureTableDDLTest(fixtures.FutureEngineMixin, TableDDLTest):
|
|
pass
|
|
|
|
|
|
class LongNameBlowoutTest(fixtures.TestBase):
|
|
"""test the creation of a variety of DDL structures and ensure
|
|
label length limits pass on backends
|
|
|
|
"""
|
|
|
|
__sparse_driver_backend__ = True
|
|
|
|
def fk(self, metadata, connection):
|
|
convention = {
|
|
"fk": "foreign_key_%(table_name)s_"
|
|
"%(column_0_N_name)s_"
|
|
"%(referred_table_name)s_"
|
|
+ (
|
|
"_".join(
|
|
"".join(random.choice("abcdef") for j in range(20))
|
|
for i in range(10)
|
|
)
|
|
),
|
|
}
|
|
metadata.naming_convention = convention
|
|
|
|
Table(
|
|
"a_things_with_stuff",
|
|
metadata,
|
|
Column("id_long_column_name", Integer, primary_key=True),
|
|
test_needs_fk=True,
|
|
)
|
|
|
|
cons = ForeignKeyConstraint(
|
|
["aid"], ["a_things_with_stuff.id_long_column_name"]
|
|
)
|
|
Table(
|
|
"b_related_things_of_value",
|
|
metadata,
|
|
Column(
|
|
"aid",
|
|
),
|
|
cons,
|
|
test_needs_fk=True,
|
|
)
|
|
actual_name = cons.name
|
|
|
|
metadata.create_all(connection)
|
|
|
|
if testing.requires.foreign_key_constraint_name_reflection.enabled:
|
|
insp = inspect(connection)
|
|
fks = insp.get_foreign_keys("b_related_things_of_value")
|
|
reflected_name = fks[0]["name"]
|
|
|
|
return actual_name, reflected_name
|
|
else:
|
|
return actual_name, None
|
|
|
|
def pk(self, metadata, connection):
|
|
convention = {
|
|
"pk": "primary_key_%(table_name)s_"
|
|
"%(column_0_N_name)s"
|
|
+ (
|
|
"_".join(
|
|
"".join(random.choice("abcdef") for j in range(30))
|
|
for i in range(10)
|
|
)
|
|
),
|
|
}
|
|
metadata.naming_convention = convention
|
|
|
|
a = Table(
|
|
"a_things_with_stuff",
|
|
metadata,
|
|
Column("id_long_column_name", Integer, primary_key=True),
|
|
Column("id_another_long_name", Integer, primary_key=True),
|
|
)
|
|
cons = a.primary_key
|
|
actual_name = cons.name
|
|
|
|
metadata.create_all(connection)
|
|
insp = inspect(connection)
|
|
pk = insp.get_pk_constraint("a_things_with_stuff")
|
|
reflected_name = pk["name"]
|
|
return actual_name, reflected_name
|
|
|
|
def ix(self, metadata, connection):
|
|
convention = {
|
|
"ix": "index_%(table_name)s_"
|
|
"%(column_0_N_name)s"
|
|
+ (
|
|
"_".join(
|
|
"".join(random.choice("abcdef") for j in range(30))
|
|
for i in range(10)
|
|
)
|
|
),
|
|
}
|
|
metadata.naming_convention = convention
|
|
|
|
a = Table(
|
|
"a_things_with_stuff",
|
|
metadata,
|
|
Column("id_long_column_name", Integer, primary_key=True),
|
|
Column("id_another_long_name", Integer),
|
|
)
|
|
cons = Index(None, a.c.id_long_column_name, a.c.id_another_long_name)
|
|
actual_name = cons.name
|
|
|
|
metadata.create_all(connection)
|
|
insp = inspect(connection)
|
|
ix = insp.get_indexes("a_things_with_stuff")
|
|
reflected_name = ix[0]["name"]
|
|
return actual_name, reflected_name
|
|
|
|
def uq(self, metadata, connection):
|
|
convention = {
|
|
"uq": "unique_constraint_%(table_name)s_"
|
|
"%(column_0_N_name)s"
|
|
+ (
|
|
"_".join(
|
|
"".join(random.choice("abcdef") for j in range(30))
|
|
for i in range(10)
|
|
)
|
|
),
|
|
}
|
|
metadata.naming_convention = convention
|
|
|
|
cons = UniqueConstraint("id_long_column_name", "id_another_long_name")
|
|
Table(
|
|
"a_things_with_stuff",
|
|
metadata,
|
|
Column("id_long_column_name", Integer, primary_key=True),
|
|
Column("id_another_long_name", Integer),
|
|
cons,
|
|
)
|
|
actual_name = cons.name
|
|
|
|
metadata.create_all(connection)
|
|
insp = inspect(connection)
|
|
uq = insp.get_unique_constraints("a_things_with_stuff")
|
|
reflected_name = uq[0]["name"]
|
|
return actual_name, reflected_name
|
|
|
|
def ck(self, metadata, connection):
|
|
convention = {
|
|
"ck": "check_constraint_%(table_name)s"
|
|
+ (
|
|
"_".join(
|
|
"".join(random.choice("abcdef") for j in range(30))
|
|
for i in range(10)
|
|
)
|
|
),
|
|
}
|
|
metadata.naming_convention = convention
|
|
|
|
cons = CheckConstraint("some_long_column_name > 5")
|
|
Table(
|
|
"a_things_with_stuff",
|
|
metadata,
|
|
Column("id_long_column_name", Integer, primary_key=True),
|
|
Column("some_long_column_name", Integer),
|
|
cons,
|
|
)
|
|
actual_name = cons.name
|
|
|
|
metadata.create_all(connection)
|
|
insp = inspect(connection)
|
|
ck = insp.get_check_constraints("a_things_with_stuff")
|
|
reflected_name = ck[0]["name"]
|
|
return actual_name, reflected_name
|
|
|
|
@testing.combinations(
|
|
("fk",),
|
|
("pk",),
|
|
("ix",),
|
|
("ck", testing.requires.check_constraint_reflection.as_skips()),
|
|
("uq", testing.requires.unique_constraint_reflection.as_skips()),
|
|
argnames="type_",
|
|
)
|
|
def test_long_convention_name(self, type_, metadata, connection):
|
|
actual_name, reflected_name = getattr(self, type_)(
|
|
metadata, connection
|
|
)
|
|
|
|
assert len(actual_name) > 255
|
|
|
|
if reflected_name is not None:
|
|
overlap = actual_name[0 : len(reflected_name)]
|
|
if len(overlap) < len(actual_name):
|
|
eq_(overlap[0:-5], reflected_name[0 : len(overlap) - 5])
|
|
else:
|
|
eq_(overlap, reflected_name)
|
|
|
|
|
|
__all__ = ("TableDDLTest", "FutureTableDDLTest", "LongNameBlowoutTest")
|