ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SQLMesh 配置:为什么值得用 Python,而不是 YAML

SQLMesh 配置:为什么值得用 Python,而不是 YAML SQLMesh 的配置文件有两种config.yaml与config.py。官方的说法很直接YAML 更简单推荐大多数项目使用Python 更复杂但提供了 YAML 不支持的能力——config.py会被 Python 解释器真正执行因此配置里可以有判断、函数、第三方 SDK以及当前在什么环境、由谁运行的信息。下面用五个实战案例说明这份可执行的含金量。案例一变更自动分类——同一份配置CI 与本地行为不同执行plan时SQLMesh 要判断模型变更是 breaking 还是 non-breaking。自动分类有三种模式full全部自动无法判断时保守归为 breaking、semi无法判断时提示用户、off关闭并可按模型来源分别设置sql / python / seed / external。YAML 只能写静态值auto_categorize_changes:sql:fullpython:semiPython 则能让策略随运行环境变化fromsqlmeshimportis_cicd_environment# 识别 CI / GitHub Actions / GitLab CIfromsqlmesh.core.configimportConfig,ModelDefaultsConfig,CategorizerConfig configConfig(model_defaultsModelDefaultsConfig(dialectduckdb),# CI 里不能等人确认本地开发反而希望人工把关auto_categorize_changes(CategorizerConfig.all_full()ifis_cicd_environment()elseCategorizerConfig.all_semi()),)收益一个文件覆盖两条流水线也不必担心有人在 CI 里被交互提示卡住。案例二密钥不进仓库连接按环境自动组装密码不能进 Gitdev 与 prod 要用不同的库、角色单元测试绝不能碰生产数据。在 YAML 里这通常意味着复制多份配置或者把密文塞进环境变量。importosfromsqlmesh.core.configimport(Config,ModelDefaultsConfig,GatewayConfig,SnowflakeConnectionConfig,DuckDBConnectionConfig,)frommy_org.secretsimportget# 例boto3 / Vault / Azure Key Vaultenvos.environ.get(SQLMESH_ENV,dev)# dev | proddb{dev:DEV_ANALYTICS,prod:ANALYTICS}[env]configConfig(model_defaultsModelDefaultsConfig(dialectsnowflake),gateways{env:GatewayConfig(connectionSnowflakeConnectionConfig(accountacme,userfsvc_{env},passwordget(fsqlmesh/{env}/snowflake),# 明文不进仓库databasedb,warehousefWH_{env.upper()},),test_connectionDuckDBConnectionConfig(),# 单测只跑内存 DuckDB),},default_gatewayenv,)收益密钥只在运行时出现在内存里仓库与 CI 日志中没有明文一条SQLMESH_ENVprod就切换整套环境无需第二份配置文件test_connection把单测隔离在内存 DuckDB 上测试污染生产从流程约定变成物理隔离还需要个人开发库时再加一个GatewayConfig用--gateway指定即可。案例三before_all / after_all——把权限治理挂到 plan / run 生命周期模型跑完通常要授权视图要给分析师角色 SELECT生产环境还要给 schema 的 USAGE。逐个模型写on_virtual_update会散落各处也感知不到当前是不是生产。before_all/after_all会在sqlmesh plan与sqlmesh run的开始与结束执行 SQL 语句或 SQLMesh 宏这两个位置还能取到this_env、schemas、views。把 Python 宏与 Python 配置放在一起权限规则就有了唯一出处fromsqlmesh.core.macrosimportmacrofromsqlmesh.core.configimportConfig,ModelDefaultsConfigmacro()defgrant_select(evaluator):return[fGRANT SELECT ON VIEW{v}TO ROLE analyst;forvinevaluator.views]macro()defgrant_schema_usage(evaluator):ifevaluator.this_env!prod:# 只在生产授权return[]return[fGRANT USAGE ON SCHEMA{s}TO ROLE analyst;forsinevaluator.schemas]configConfig(model_defaultsModelDefaultsConfig(dialectduckdb),after_all[grant_select(),grant_schema_usage()],)收益授权只写一次、随每次 plan / run 自动生效并自带环境判断宏就是普通 Python 函数可以单测也能 import 你自己的权限矩阵。案例四一人一环境——默认目标环境按使用者生成多人共用一个项目时每个人都要手敲sqlmesh plan dev_tony稍不留神漏了环境名plan 就落在prod上。开发环境还会不断堆积没人清理。importosimportgetpassfromsqlmesh.core.configimport(Config,ModelDefaultsConfig,GatewayConfig,DuckDBConnectionConfig,)rawos.environ.get(GITHUB_ACTOR)orgetpass.getuser()ownerraw.split()[0].lower().replace(.,_)# 用户名需要先规范化configConfig(model_defaultsModelDefaultsConfig(dialectduckdb),gateways{local:GatewayConfig(connectionDuckDBConnectionConfig())},default_target_environmentfdev_{owner},# sqlmesh plan sqlmesh plan dev_tonyenvironment_ttlin 3 days,# 开发环境自动过期回收pinned_environments[prod],# prod 不参与清理)收益少打一个参数也顺手消除了误指 prod这一整类事故——想动生产必须显式写出环境名一人一环境加 TTL让环境自动回收。公平地说YAML 里的default_target_environment: dev_{{ user() }}也能覆盖一半场景。Python 的增量价值在于计算名字可能来自 CI 变量或工号还得去掉域名、把.换成_否则不是合法标识符甚至按角色决定命名空间与保留策略。案例五一个项目、多个引擎与区域——网关级默认值和变量同一批模型本地想用 DuckDB 快速全链路自测生产跑在 Snowflake 与 Redshift 上不同引擎的标识符大小写规则不同多区域部署还要换参数。用 YAML通常要为每个引擎复制一遍项目设置。fromsqlmesh.core.configimport(Config,ModelDefaultsConfig,GatewayConfig,DuckDBConnectionConfig,SnowflakeConnectionConfig,RedshiftConnectionConfig,)configConfig(# 全局默认模型按 Snowflake 方言书写model_defaultsModelDefaultsConfig(dialectsnowflake),variables{region:us},gateways{local:GatewayConfig(# 本地换引擎全链路自测connectionDuckDBConnectionConfig(),model_defaultsModelDefaultsConfig(dialectduckdb),),prod:GatewayConfig(# 模型仍是 Snowflake 方言connectionRedshiftConnectionConfig(host...,user...,password...),# 但按 Redshift 大小写不敏感的规则处理标识符model_defaultsModelDefaultsConfig(dialectsnowflake,normalization_strategycase_insensitive),),eu:GatewayConfig(# 同一套模型换区域参数connectionSnowflakeConnectionConfig(accountacme,usersvc,password...),variables{region:eu},),},default_gatewaylocal,)收益模型只写一份方言与标识符规范化策略交给网关决定gateway-specificmodel_defaults的用途变量按网关覆盖多区域、多租户不必 fork 项目模型里用region或context.var(region)读取默认网关指向本地 DuckDB只有显式--gateway prod才连生产。五个案例的共同点配置是对象不是文本Config、GatewayConfig、CategorizerConfig、ModelDefaultsConfig在加载时校验参数或枚举写错立刻报错IDE 还能补全YAML 把semi敲成seml常要等到 plan 才暴露。逻辑可复用工厂方法、函数与常量让环境矩阵网关矩阵只写一次而不是复制多份 YAML 再逐份改。随环境与使用者变化os.environ、getpass.getuser()、SQLMesh 自带的is_cicd_environment()都能参与决策配置因此可以随人、随时、随流水线而变化。能与外部系统对话密钥管理器、CMDB、权限系统都能在生成配置这一步接入而不是等运行时报错。一个文件多套配置用sqlmesh --config my_second_config plan在 dev / prod / CI 之间切换多仓库项目还能用project区分。什么时候仍然该用 YAML纯声明、单环境、没有动态逻辑的项目YAML 更短更直观也支持{{ env_var(...) }}、{{ user() }}与环境变量覆盖SQLMESH__开头的变量按层级拼接优先级最高。务实的做法是默认 YAML只在需要动态行为时引入config.py两者也可以并存用--config选择。风险也要承认Python 配置是可执行代码应像代码一样 review、避免副作用别在 import 时写库或发网络请求并保证运行 SQLMesh 的环境依赖齐全。结语YAML 描述配置是什么Python 描述配置如何产生。变更分类的按需自动、密钥与连接的环境化组装、权限治理的生命周期挂载、一人一环境的命名与回收、多引擎多区域的矩阵展开——五个案例背后是同一件事当项目长到需要多环境、多网关、CI/CD 与密钥治理时Python 配置把配置从静态清单升级为可复用、可测试、可演进的策略层。参考Configuration guide变更分类示例CategorizerConfig / AutoCategorizationModeGatewayConfig含 test_connection、model_defaults、variables宏变量 this_env / schemas / views
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进