DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →

SQLAlchemy 反射 PostgreSQL 18 的 PERIOD 外键时报 NoneType.groups

PostgreSQL 18 时间外键可触发旧 SQLAlchemy 的反射正则错误。使用包含修复的正式版本后仍须复核迁移,因为 PERIOD 语义尚未完整保存在反射结果中。

数据库里已经存在合法的时间外键,调用 inspect(engine).get_foreign_keys() 或 Alembic 自动生成迁移时,却在 PGDialect._parse_fk 报:

AttributeError: 'NoneType' object has no attribute 'groups'

如果环境是 PostgreSQL 18,约束定义包含 PERIOD,应检查 SQLAlchemy 对新语法的反射支持。这个 NoneType 来自约束文本解析未匹配,不是数据库返回了一个空外键对象。

PERIOD 让旧解析规则匹配不到约束

例如,已有约束的文本可能具有下面的形状:

FOREIGN KEY (parent_id, PERIOD valid)
REFERENCES timeline(id, PERIOD valid)

timeline、valid 是示例名称。这只是已有约束的形状,不应直接复制去生产创建外键;实际表还要满足时间范围、唯一约束及关联关系的要求。

旧方言按普通列名列表解析 REFERENCES 后的列,不能处理 PERIOD valid 中的关键字与列名组合。正则匹配返回 None 后,继续调用 .groups(),整张表的反射就失败了。改用户名、驱动连接参数或给数据库列补默认值,都不会修正这个解析入口。

先用包含修复的正式版本恢复反射

明确记录过问题的组合是 SQLAlchemy 2.1.1、psycopg 3.3.6 与 PostgreSQL 18.6。已核对的正式 SQLAlchemy 2.0.55 与 2.1.5 包含对应解析修复;这是确认包含修复的版本,不要求现有项目为了一个问题跨到另一个主分支。

在隔离环境恢复项目锁定依赖、使用包含修复的版本后,对已有目标表进行只读反射:

from sqlalchemy import inspect

# engine 为应用已有、获准访问相应测试数据库的 Engine
foreign_keys = inspect(engine).get_foreign_keys("timeline")
for key in foreign_keys:
    print(key["constrained_columns"], key["referred_columns"])

这里不创建或删除表。若数据库使用非默认 schema,还要在反射调用中传入正确的 schema,避免检查到另一张同名表。

能反射列名,不代表能完整重建时间外键

修复版本能够识别这段语法并保留普通外键列,当前反射结果却仍会丢弃 PERIOD 的标记。不能据此从反射对象重新生成 DDL,再认为时间约束一定原样保留。

尤其是 Alembic autogenerate:先生成候选迁移,逐项对照数据库原约束定义,检查有没有错误删除时间约束或改建为普通外键。保留数据库结构备份和原迁移,不要看到自动生成成功就直接执行。

暂时无法升级时,按表名在反射前排除受影响表可以缩小范围;include_object 若在反射后才执行,未必来得及阻止解析失败。排除还要与 target_metadata 的管理范围保持一致,防止把未反射到的表误认成待创建对象。仅为绕过一次自动生成,删除真实数据库外键并不合适。

完成标准包括原表反射成功,以及生成迁移没有丢掉时间关系语义。若其他普通约束仍报同样异常,应保留它的实际定义继续检查,不能将所有 .groups() 错误都归到 PostgreSQL 18。