Oracle 数据库之 Python 查询返回字典:TaoToken 统一 Key 接入与 config.toml 配置骨架
1. 为什么 Oracle 查询默认返回元组而你想要字典用 Python 连 Oracle 查数据第一次跑通的人几乎都会遇到同一个别扭cursor.fetchall()吐出来的是一堆元组取值只能靠下标。比如row[0]是 ID、row[3]是创建时间代码里到处是魔法数字过两周自己都忘了第 7 列到底是啥。更麻烦的是接口层要转 JSON元组没有字段名只能再手写一层映射字段一多就容易错位。这个场景特别常见你写了一个数据同步脚本或者给前端提供一个查询接口希望fetchone()直接拿到{ID: 1, NAME: 张三}这种结构序列化出去就是标准 JSON。Oracle 的 Python 驱动其实留了口子cx_Oracle和它的继任者oracledb都支持通过cursor.rowfactory或者cursor.description把结果行改造成字典。核心思路就一句话查询执行后驱动知道每列的名字在cursor.description里我们把这个列名列表和每行数据 zip 起来就得到字典。这篇会给你两套可复制的写法一套基于rowfactory一套基于描述符手动组装再配一份config.toml配置骨架把数据库连接参数和 TaoToken 的统一 Key 通道放在一起管理。最后跑一条查询验证动作确认返回结构真的是字典而不是元组。适合正在写 Oracle 数据管道、报表导出、或者给 AI 应用喂结构化数据的同学。2. TaoToken 统一 Key 与 API 通道前置说明在动手改rowfactory之前先把「Key 从哪来、通道怎么走」这件事理清楚不然后面配置骨架会缺一块。TaoToken 在这里扮演的是一个统一入口你不需要在每台机器、每个脚本里散落不同的模型或服务凭证而是拿一个统一 Key通过它的 API 通道去调用。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。具体到操作层面你需要先拿到 Key。打开控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面生成并复制地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。这个 Key 后面会写进config.toml和 Oracle 的连接串放在同一个文件里方便统一读取。注意Key 属于敏感凭证不要硬编码进业务代码也不要提交到公开仓库。用config.toml加环境变量覆盖的方式管理是成本最低又不容易翻车的做法。如果你后面还要做模型对话验证、或者把查询结果喂给模型做分析可以顺手了解下模型对话入口 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 如果是长期跑编码类 Agent 任务Coding Plan 的说明在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入细节文档统一在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。这些先记着本篇主线还是 Oracle 返回字典。3. 可复制的 config.toml 配置骨架把配置抽出来是第一步。下面这份config.toml同时容纳 Oracle 连接信息和 TaoToken 的统一 Key用 Python 的tomllib3.11或tomli读取即可。字段名我按「一眼能看懂」的原则起你可以直接抄。# config.toml [oracle] user scott password tiger dsn 127.0.0.1:1521/ORCLPDB1 # 连接池参数按需调整 min 1 max 4 increment 1 [taotoken] api_base https://taotoken.net/api api_key sk-替换成你在控制台生成的Key timeout 30 [query] # 默认返回结构dict 或 tuple row_mode dict读取配置的代码很短注意tomllib是只读的够用import tomllib with open(config.toml, rb) as f: cfg tomllib.load(f) oracle_cfg cfg[oracle] tt_cfg cfg[taotoken] row_mode cfg[query][row_mode]这里有个容易踩的坑dsn的写法在不同 Oracle 部署下不一样。如果你用的是 Easy Connect 串就是host:port/service_name如果配了tnsnames.ora可以直接写别名。实测下来把dsn单独拎出来放配置里比散在代码里拼接要省心得多换环境只改一行。4. 两种让查询返回字典的写法4.1 rowfactory 写法最省事的字典工厂rowfactory是驱动提供的钩子每次取行时都会调用你给的工厂函数。核心就是先从cursor.description拿到列名再和行数据 zip 成字典。下面这个make_dict_factory可以直接复用def make_dict_factory(cursor): columns [d[0] for d in cursor.description] def create_row(*args): return dict(zip(columns, args)) return create_row用法是在execute()之后、fetch之前挂上去import oracledb conn oracledb.connect( useroracle_cfg[user], passwordoracle_cfg[password], dsnoracle_cfg[dsn], ) cursor conn.cursor() cursor.execute(SELECT id, name, created_at FROM users WHERE rownum 5) cursor.rowfactory make_dict_factory(cursor) for row in cursor: print(type(row), row)跑出来每一行都是dict字段名就是 SQL 里的列名Oracle 默认大写。如果你希望字段名统一小写把d[0]改成d[0].lower()即可这一点在对接前端时特别有用省得前端再转一次。4.2 描述符手动组装不依赖 rowfactory 的通用写法有些团队用的驱动版本较老或者你想在取数后统一处理不想依赖rowfactory的隐式行为那就手动用cursor.description组装。思路一样只是把工厂逻辑显式写出来cursor.execute(SELECT id, name, created_at FROM users WHERE rownum 5) columns [d[0].lower() for d in cursor.description] rows [dict(zip(columns, r)) for r in cursor.fetchall()] print(rows[0])这种写法的好处是可控性强你可以在zip之前对列名做重命名映射比如把CREATED_AT改成createdAt或者过滤掉不需要的列。坏处是每处查询都要写一遍所以建议封装成一个函数def fetch_dicts(cursor, sql, paramsNone): cursor.execute(sql, params or {}) columns [d[0].lower() for d in cursor.description] return [dict(zip(columns, r)) for r in cursor.fetchall()]两种写法怎么选日常脚本、快速验证用rowfactory一行挂载全局生效需要精细控制字段名、或者要兼容多驱动时用描述符手动组装。我试过在同一个项目里混用只要约定好「对外统一小写字典」不会冲突。4.3 顺带说下 namedtuple 变体如果你既想要字段名访问又想要元组的轻量和不可变可以用namedtuple工厂。它和字典工厂的区别只是返回类型import collections def make_namedtuple_factory(cursor): columns [d[0].lower() for d in cursor.description] Row collections.namedtuple(Row, columns) return Row挂载方式和字典工厂完全一致。取到的行既能row.name访问也能row[1]下标访问序列化时用row._asdict()转字典。适合那种「内部计算用属性、出口转 JSON」的场景。5. 验证请求确认返回结构真的是字典改完代码别急着往下写业务先跑一条最小验证确认类型对得上。下面这段可以直接复制把连接参数换成你的import oracledb def make_dict_factory(cursor): columns [d[0].lower() for d in cursor.description] def create_row(*args): return dict(zip(columns, args)) return create_row conn oracledb.connect(userscott, passwordtiger, dsn127.0.0.1:1521/ORCLPDB1) cursor conn.cursor() cursor.execute(SELECT 1 AS id, hello AS msg FROM dual) cursor.rowfactory make_dict_factory(cursor) row cursor.fetchone() print(type(row)) # 期望输出class dict print(row) # 期望输出{id: 1, msg: hello} assert isinstance(row, dict), 返回结构不是字典检查 rowfactory 是否挂载成功 print(验证通过查询返回字典结构)成功的话你会看到type(row)是dict打印出来是带字段名的键值对。如果打印的是(1, hello)这种元组说明rowfactory没生效往下看排查部分。这条验证动作建议固化成一个测试用例每次改驱动版本或连接配置后跑一遍能省掉很多「字段错位」的隐性 bug。6. 本篇常见错排查报错一AttributeError: Cursor object has no attribute rowfactory这通常是把rowfactory挂到了连接对象上而不是游标对象。正确顺序是cursor conn.cursor()然后cursor.execute(...)再cursor.rowfactory ...。另外确认你用的是cx_Oracle或oracledb其他驱动不一定有这个属性。报错二返回字典的键全是数字或空检查cursor.description是否在execute()之后才读取。description只有在语句执行后才有值提前读会拿到Nonezip出来自然是空的。把工厂函数的调用时机放在execute之后即可。报错三字段名大小写和预期不一致Oracle 默认把未加引号的列名转成大写所以SELECT name拿到的键是NAME。想要小写就在工厂里统一.lower()想要驼峰就自己写映射表。别在 SQL 里用双引号强制小写那样会让列名变成大小写敏感后续查询反而容易踩坑。报错四oracledb和cx_Oracle混用导致导入冲突两个包不能同时装在同一环境里抢Oracle命名空间。新项目直接用oracledb它是cx_Oracle的官方继任者API 基本兼容。老项目迁移时先卸载cx_Oracle再装oracledb然后全局替换 import。报错五连接超时或 DSN 解析失败先确认dsn格式Easy Connect 是host:port/service_name中间是冒号不是斜杠。如果服务名不确定用lsnrctl status看监听器注册的服务。连接池参数min/max设得太大会在启动时卡住本地调试先设min1, max2。7. 接入与排障时的入口选择上面这套配置骨架里Oracle 部分解决的是「数据怎么取」TaoToken 部分解决的是「Key 和通道怎么统一管」。如果你在接入过程中遇到 Key 校验、通道地址、超时这类问题优先去看 API Keys 页面和接入文档这两个地方覆盖了绝大多数配置类报错API Keys 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你是想先验证某个模型对查询结果的理解能力比如把字典列表丢给模型做摘要那走模型对话入口最直接https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。而如果是长期跑编码类 Agent、需要稳定通道和额度规划Coding Plan 的说明在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 适合先看清楚再决定。回到 Oracle 这条线最后给你一个实用习惯把make_dict_factory和fetch_dicts放进项目公共模块所有查询统一走这两个入口字段名大小写在工厂里一次性约定。这样无论后面换驱动、加连接池、还是把结果喂给下游服务返回结构始终是稳定的字典不会因为某处忘了挂rowfactory而突然退化成元组。