Files
AgentLab/backend/app/db/urls.py
T
ThearchyHelios b2f6d8d7d2 README、部署说明补上用 PostgreSQL:AGENTLAB_DATABASE_URL、断点、连接池(含 PgBouncer)、和 SQLite 一致的那几条、从 SQLite 导入的步骤(copy-to-postgres,先停服务、先 --dry-run)、备份(pg_dump 加数据目录)、多台机器要把数据目录挂在共享存储的同一个路径上,以及实话:数据目录里的 SQLite 文件(记录表原地追加写、上传表格版本库的 flock)在网络文件系统上并发写不安全,眼下仍按单进程部署
- 依赖与锁文件:[postgres] 里装的是什么、为什么 langgraph-checkpoint 单独卡小版本、checkpoint-postgres 锁在 3.1.2;数据位置:PostgreSQL 时不再用 agentlab.db / checkpoints.db;开发与验证:scripts/test-postgres.sh
- environment.yml 带上 [postgres];.env.example 补应用库一节
- deploy.sh:数据目录里没有 agentlab.db(库在 PostgreSQL)时,认 .secret_key、uploads/ 也算 AgentLab 的数据目录,不再拒绝改属主
- 断点库的连接串不带只给 asyncpg 看的参数(prepared_statement_cache_size 之类,psycopg 不认);地址规范化、启动日志不带密码的测试
2026-10-19 10:13:58 +08:00

60 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""应用库、断点库的地址:规范写法、换算成 psycopg 用的连接串、写进日志的样子(不带密码)。
不依赖 app.core.config:配置的校验器要调这里,这里再导 config 就成了循环导入。
"""
from __future__ import annotations
from sqlalchemy.engine import URL, make_url
from sqlalchemy.exc import ArgumentError
#: 应用库认的写法。asyncpg 是 SQLAlchemy 这边的驱动;postgresql:// / postgres:// 是常见的通用写法,一律换成 asyncpg
_APP_SCHEMES = {"postgresql+asyncpg", "postgresql", "postgres"}
#: 断点库认的写法:psycopg 只认 libpq 的 postgresql:// / postgres://;带驱动名的 SQLAlchemy 写法也收,去掉驱动名
_CHECKPOINT_SCHEMES = {"postgresql", "postgres", "postgresql+psycopg", "postgresql+asyncpg"}
#: asyncpg 和 libpq 对 SSL 的参数名不一样:asyncpg 是 ssl=require,libpq 是 sslmode=require(取值同一套)
_ASYNCPG_TO_LIBPQ = {"ssl": "sslmode"}
#: 只给 SQLAlchemy / asyncpg 看的参数,libpq 不认(psycopg 连的时候会报「invalid connection option」)
_ASYNCPG_ONLY = {"prepared_statement_cache_size", "prepared_statement_name_func", "command_timeout", "async_fallback"}
def _parse(raw: str, schemes: set[str], example: str) -> URL:
try:
url = make_url(raw)
except ArgumentError:
raise ValueError(f"认不出这个地址,应当写成 {example}") from None
if url.drivername not in schemes:
raise ValueError(f"只认 PostgreSQL({example}),不认 {url.drivername}://;用 SQLite 就不要设这一项")
if not url.database:
raise ValueError(f"地址里要写库名,例如 {example}")
return url
def normalize_database_url(raw: str) -> str:
"""AGENTLAB_DATABASE_URL → postgresql+asyncpg://…(密码原样保留)。认不出的写法抛 ValueError。"""
url = _parse(raw, _APP_SCHEMES, "postgresql+asyncpg://用户:密码@主机:5432/库名")
return url.set(drivername="postgresql+asyncpg").render_as_string(hide_password=False)
def normalize_checkpoint_url(raw: str) -> str:
"""AGENTLAB_CHECKPOINT_URL → libpq 认的 postgresql://…。认不出的写法抛 ValueError。"""
url = _parse(raw, _CHECKPOINT_SCHEMES, "postgresql://用户:密码@主机:5432/库名")
return libpq_conninfo(url.render_as_string(hide_password=False))
def libpq_conninfo(database_url: str) -> str:
"""SQLAlchemy 的地址 → psycopg 的连接串:去掉驱动名,asyncpg 写法的参数换成 libpq 的。"""
url = make_url(database_url)
query = {_ASYNCPG_TO_LIBPQ.get(k, k): v for k, v in url.query.items() if k not in _ASYNCPG_ONLY}
return url.set(drivername="postgresql", query=query).render_as_string(hide_password=False)
def describe(database_url: str) -> str:
"""写进日志的样子:主机、端口、库名、用户,不写密码。"""
url = make_url(database_url)
host = url.host or "本机套接字"
port = f":{url.port}" if url.port else ""
user = f",用户 {url.username}" if url.username else ""
return f"{host}{port}/{url.database}{user}"