checkpoint: SQLite 清理——业务/测试层统一 PostgreSQL(删 aiosqlite/fakeredis)

- setting: DATABASE_TYPE 收窄为 postgres,DB_URI/ASYNC_DB_URI 去 sqlite 分支
- database: create_async_engine_and_session 去 sqlite 分支(同步 psycopg 引擎保留给 jobstore)
- number_gen: 去 DATABASE_TYPE 非 postgres 早退(advisory lock 恒定走 PG)
- chat/crud: 删 SqliteDb 分支(agno 无硬依赖)
- pyproject/requirements/uv.lock: 删 aiosqlite、fakeredis;conftest 口径注释同步
- test_stat_analysis_tc: docstring 口径 SQLite→PG
This commit is contained in:
34047007@qq.com
2026-08-07 21:47:42 +08:00
parent 73e25050df
commit 0b50e0adfa
8 changed files with 25 additions and 87 deletions
@@ -3,7 +3,6 @@ from typing import Any
from agno.db.base import SessionType from agno.db.base import SessionType
from agno.db.mysql import MySQLDb from agno.db.mysql import MySQLDb
from agno.db.postgres import PostgresDb from agno.db.postgres import PostgresDb
from agno.db.sqlite import SqliteDb
from agno.session.team import TeamSession from agno.session.team import TeamSession
from app.config.setting import settings from app.config.setting import settings
@@ -34,7 +33,6 @@ class ChatSessionCRUD:
db_mapping = { db_mapping = {
"mysql": lambda: MySQLDb(db_url=db_uri, db_schema=settings.DATABASE_NAME, create_schema=False), "mysql": lambda: MySQLDb(db_url=db_uri, db_schema=settings.DATABASE_NAME, create_schema=False),
"postgres": lambda: PostgresDb(db_url=db_uri, db_schema="public", create_schema=False), "postgres": lambda: PostgresDb(db_url=db_uri, db_schema="public", create_schema=False),
"sqlite": lambda: SqliteDb(db_file=db_uri.replace("sqlite:///", "")),
} }
if db_type not in db_mapping: if db_type not in db_mapping:
+4 -13
View File
@@ -10,7 +10,6 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
from app.common.enums import EnvironmentEnum from app.common.enums import EnvironmentEnum
from app.config.path_conf import ENV_DIR, STATIC_DIR from app.config.path_conf import ENV_DIR, STATIC_DIR
# JWT 开发默认密钥:仅允许 DEV 环境使用,生产命中即拒绝启动 # JWT 开发默认密钥:仅允许 DEV 环境使用,生产命中即拒绝启动
_DEV_DEFAULT_SECRET_KEY = "dpb-dev-secret-key-do-not-use-in-production" _DEV_DEFAULT_SECRET_KEY = "dpb-dev-secret-key-do-not-use-in-production"
@@ -110,8 +109,8 @@ class Settings(BaseSettings):
AUTOFETCH: bool | None = None # AUTOFLUSH 别名(优先级高于 AUTOFLUSH,兼容旧环境变量名) AUTOFETCH: bool | None = None # AUTOFLUSH 别名(优先级高于 AUTOFLUSH,兼容旧环境变量名)
EXPIRE_ON_COMMIT: bool = False # 是否在提交时过期 EXPIRE_ON_COMMIT: bool = False # 是否在提交时过期
# 数据库连接(生产 PostgreSQL;测试套件用 SQLite # 数据库连接(开发与生产统一 PostgreSQL
DATABASE_TYPE: Literal["postgres", "sqlite"] = "postgres" DATABASE_TYPE: Literal["postgres"] = "postgres"
DATABASE_HOST: str = "localhost" DATABASE_HOST: str = "localhost"
DATABASE_PORT: int = 5432 DATABASE_PORT: int = 5432
DATABASE_USER: str = "dpb" DATABASE_USER: str = "dpb"
@@ -305,19 +304,11 @@ class Settings(BaseSettings):
@property @property
def ASYNC_DB_URI(self) -> str: def ASYNC_DB_URI(self) -> str:
if self.DATABASE_TYPE == "postgres": return f"postgresql+asyncpg://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
db_connect = f"postgresql+asyncpg://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
else:
db_connect = f"sqlite+aiosqlite:///{self.DATABASE_NAME}.db"
return db_connect
@property @property
def DB_URI(self) -> str: def DB_URI(self) -> str:
if self.DATABASE_TYPE == "postgres": return f"postgresql+psycopg://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
db_connect = f"postgresql+psycopg://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
else:
db_connect = f"sqlite:///{self.DATABASE_NAME}.db"
return db_connect
@property @property
def FASTAPI_CONFIG(self) -> dict[str, Any]: def FASTAPI_CONFIG(self) -> dict[str, Any]:
+1 -11
View File
@@ -49,17 +49,7 @@ def create_async_engine_and_session(db_url: str = settings.ASYNC_DB_URI) -> tupl
- tuple[AsyncEngine, async_sessionmaker[AsyncSession]]: 异步数据库引擎和会话工厂。 - tuple[AsyncEngine, async_sessionmaker[AsyncSession]]: 异步数据库引擎和会话工厂。
""" """
try: try:
# 异步数据库引擎 # 异步数据库引擎(统一 PostgreSQL 连接池参数)
if settings.DATABASE_TYPE == "sqlite":
async_engine = create_async_engine(
url=db_url,
echo=settings.DATABASE_ECHO,
echo_pool=settings.ECHO_POOL,
pool_pre_ping=settings.POOL_PRE_PING,
future=settings.FUTURE,
pool_recycle=settings.POOL_RECYCLE,
)
else:
async_engine = create_async_engine( async_engine = create_async_engine(
url=db_url, url=db_url,
echo=settings.DATABASE_ECHO, echo=settings.DATABASE_ECHO,
+1 -5
View File
@@ -7,7 +7,7 @@
存量数据迁移:首次使用某序列名时由 seed_fn(服务端传入)从既有记录推算 存量数据迁移:首次使用某序列名时由 seed_fn(服务端传入)从既有记录推算
起始号(原扫描逻辑),此后全部走原子递增,不再扫描。 起始号(原扫描逻辑),此后全部走原子递增,不再扫描。
""" """
from typing import Any, Awaitable, Callable from collections.abc import Awaitable, Callable
from sqlalchemy import select, text from sqlalchemy import select, text
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -23,10 +23,6 @@ class NumberGenService:
@staticmethod @staticmethod
async def _acquire_lock(db: AsyncSession, name: str) -> None: async def _acquire_lock(db: AsyncSession, name: str) -> None:
from app.config.setting import settings
if settings.DATABASE_TYPE != "postgres":
return # sqlite 测试库无 advisory lock,退化为普通自增(单进程无并发竞争)
# hashtextextended 对文本做稳定 64 位哈希,同名序列恒映射同一把锁 # hashtextextended 对文本做稳定 64 位哈希,同名序列恒映射同一把锁
await db.execute(text("SELECT pg_advisory_xact_lock(hashtextextended(:n, 0))"), {"n": name}) await db.execute(text("SELECT pg_advisory_xact_lock(hashtextextended(:n, 0))"), {"n": name})
+2 -4
View File
@@ -43,15 +43,13 @@ postgres = ["asyncpg==0.31.0"]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"aiosqlite==0.22.1", # 测试:SQLite 内存库(conftest 固定 DATABASE_TYPE=sqlite,勿移除)
"fakeredis>=2.21.0,<3", # 测试:内存 Redis,配合 TESTING
"pytest==9.0.2", "pytest==9.0.2",
"pytest-asyncio>=0.24.0", # 异步测试支持 "pytest-asyncio>=0.24.0", # 异步测试支持
"ruff==0.14.13", # 代码检查/格式化 "ruff==0.14.13", # 代码检查/格式化
] ]
[tool.uv] [tool.uv]
default-groups = ["dev"] # 本地 uv sync 含 dev;生产 pip -r requirements.txt 不含 pytest/ruff/fakeredis default-groups = ["dev"] # 本地 uv sync 含 dev;生产 pip -r requirements.txt 不含 pytest/ruff
[[tool.uv.index]] [[tool.uv.index]]
name = "tuna" name = "tuna"
@@ -118,7 +116,7 @@ ignore = [
] ]
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"tests/conftest.py" = ["E402"] # 先设置 TESTING/SQLite 环境再 import 应用 "tests/conftest.py" = ["E402"] # 先设置 TESTING/PostgreSQL 环境再 import 应用
[tool.ruff.format] [tool.ruff.format]
docstring-code-format = true docstring-code-format = true
+1 -1
View File
@@ -1,4 +1,4 @@
# 与 pyproject.toml [project] dependencies 一致(生产 pip;不含 dev 组:pytest / ruff / fakeredis # 与 pyproject.toml [project] dependencies 一致(生产 pip;不含 dev 组:pytest / ruff
agno==2.5.8 # AI agent框架 agno==2.5.8 # AI agent框架
aiofiles==24.1.0 # 文件操作 aiofiles==24.1.0 # 文件操作
alembic==1.18.4 # 数据库迁移 alembic==1.18.4 # 数据库迁移
+4 -4
View File
@@ -6,9 +6,9 @@
本包装层以 subprocess 顺序调用这些脚本,断言 rc==0 —— 与 run_regression.py 的执行契约一致。 本包装层以 subprocess 顺序调用这些脚本,断言 rc==0 —— 与 run_regression.py 的执行契约一致。
关键点: 关键点:
- 全部基于真实 PG16 + Redis(本工程无 SQLite,仅当探测到 PG/Redis 可达才跑,否则 skipif 跳过。 - 全部基于真实 PG16 + Redis,仅当探测到 PG/Redis 可达才跑,否则 skipif 跳过。
- 不 import 任何 app 模块(避免被 conftest 的 sqlite 环境污染);subprocess env 显式构建: - 不 import 任何 app 模块(测试库生命周期由 conftest 管理,与统计引擎 tc 独立);subprocess env 显式构建:
剥掉 conftest 注入的 DATABASE_TYPE/DATABASE_NAME/POOL_SIZE/MAX_OVERFLOW/CAPTCHA_ENABLE 清空可能残留的 DATABASE_*/REDIS_*/POOL_*/CAPTCHA_ENABLE 等连接变量
置 ENVIRONMENT=dev / DATABASE_TYPE=postgres / PYTHONPATH=backend / PYTHONUTF8=1 置 ENVIRONMENT=dev / DATABASE_TYPE=postgres / PYTHONPATH=backend / PYTHONUTF8=1
再并入从 backend/env/.env.dev 解析的真实连接值(可被 TC_CI_* 覆盖,用于验证 skipif 路径)。 再并入从 backend/env/.env.dev 解析的真实连接值(可被 TC_CI_* 覆盖,用于验证 skipif 路径)。
- 标记 pgpyproject 已注册),`pytest -m pg` 只跑本组;run_ci.py 的验收门即此命令。 - 标记 pgpyproject 已注册),`pytest -m pg` 只跑本组;run_ci.py 的验收门即此命令。
@@ -118,7 +118,7 @@ def pg_ready() -> tuple[bool, bool, str]:
def _sub_env() -> dict: def _sub_env() -> dict:
"""显式构建子进程环境:剥掉 conftest 注入的 sqlite 变量,置真实 PG/Redis 连接。""" """显式构建子进程环境:清空可能残留的连接变量,置真实 PG/Redis 连接。"""
env = dict(os.environ) env = dict(os.environ)
for k in ("DATABASE_TYPE", "DATABASE_NAME", "POOL_SIZE", "MAX_OVERFLOW", for k in ("DATABASE_TYPE", "DATABASE_NAME", "POOL_SIZE", "MAX_OVERFLOW",
"CAPTCHA_ENABLE", "ENVIRONMENT", "PYTHONPATH", "PYTHONIOENCODING"): "CAPTCHA_ENABLE", "ENVIRONMENT", "PYTHONPATH", "PYTHONIOENCODING"):
-35
View File
@@ -46,15 +46,6 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" },
] ]
[[package]]
name = "aiosqlite"
version = "0.22.1"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" },
]
[[package]] [[package]]
name = "alembic" name = "alembic"
version = "1.18.4" version = "1.18.4"
@@ -197,8 +188,6 @@ postgres = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "aiosqlite" },
{ name = "fakeredis" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-asyncio" }, { name = "pytest-asyncio" },
{ name = "ruff" }, { name = "ruff" },
@@ -243,8 +232,6 @@ provides-extras = ["postgres"]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "aiosqlite", specifier = "==0.22.1" },
{ name = "fakeredis", specifier = ">=2.21.0,<3" },
{ name = "pytest", specifier = "==9.0.2" }, { name = "pytest", specifier = "==9.0.2" },
{ name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-asyncio", specifier = ">=0.24.0" },
{ name = "ruff", specifier = "==0.14.13" }, { name = "ruff", specifier = "==0.14.13" },
@@ -353,19 +340,6 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
] ]
[[package]]
name = "fakeredis"
version = "2.34.1"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "redis" },
{ name = "sortedcontainers" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/40/fd09efa66205eb32253d2b2ebc63537281384d2040f0a88bcd2289e120e4/fakeredis-2.34.1.tar.gz", hash = "sha256:4ff55606982972eecce3ab410e03d746c11fe5deda6381d913641fbd8865ea9b", size = 177315, upload-time = "2026-02-25T13:17:51.315Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" },
]
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.138.2" version = "0.138.2"
@@ -1251,15 +1225,6 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
] ]
[[package]]
name = "sortedcontainers"
version = "2.4.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
]
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "2.0.51" version = "2.0.51"