news 2026/9/11 10:38:12

Apache Airflow 依赖与 Extras 管理全解:从 uv Workspace 到约束文件(Constraints)的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apache Airflow 依赖与 Extras 管理全解:从 uv Workspace 到约束文件(Constraints)的工程实践

Apache Airflow 依赖与 Extras 管理全解:从 uv Workspace 到约束文件(Constraints)的工程实践

【免费下载链接】airflowApache Airflow - A platform to programmatically author, schedule, and monitor workflows项目地址: https://gitcode.com/GitHub_Trending/ai/airflow

本文以 Apache Airflow 仓库中的 contributing-docs/13_airflow_dependencies_and_extras.rst 为主线,系统讲解 Airflow 如何在单仓库(monorepo)中管理超过 700 个依赖、100 多个 Python 发行包(distribution),以及如何通过"约束文件"机制让同一个项目既作为应用稳定安装、又作为库灵活复用。读完本文,你将掌握 Airflow 的pyproject.toml依赖分区规则、uvworkspace 的工作方式、跨发行包引用的# use next version协作约定,以及constraints-*.txt系列约束文件的使用方法,并能在自己的 Airflow 开发中正确添加、修改和校验依赖。

一、Airflow 的依赖管理全景:为什么这是一个复杂工程

Apache Airflow 不是一个普通的 Python 项目。在 pyproject.toml 中,主发行包apache-airflow的元数据清楚地表明了这一特殊性:

[project] name = "apache-airflow" requires-python = ">=3.10,!=3.15" version = "3.4.0" dependencies = [ "apache-airflow-task-sdk<1.5.0,>=1.4.0", "apache-airflow-core==3.4.0", ]

从源码结构看,Airflow 仓库是一个典型的 monorepo,包含超过 100 个 Python 发行包:主包apache-airflow、核心包apache-airflow-core、任务 SDKapache-airflow-task-sdk、控制面工具apache-airflow-ctl、大量apache-airflow-providers-*提供商包(每个 provider 一个发行包),以及若干apache-airflow-shared-*共享库(见 shared 目录)。这些发行包之间相互依赖、协同演进,依赖管理因此成为开发流程中不可回避的核心环节——每当新增功能引入新依赖,或新增一个用于开发、测试、文档构建的工具时,都需要同步更新依赖列表。

管理如此庞大的依赖体系,Airflow 遵循了三个 PEP 标准:

  • PEP 518:规定pyproject.toml作为项目构建配置的存放位置;
  • PEP 621:规定[project]表用于声明元数据和依赖;
  • PEP 735:引入依赖组(dependency group),Airflow 在所有pyproject.toml中统一使用dev依赖组来定义开发依赖。

二、uv Workspace:把 monorepo 绑定在一起的粘合剂

2.1 Workspace 的定义位置

所有发行包通过uv的 workspace 特性连接起来,workspace 定义在仓库根目录 pyproject.toml 的[tool.uv.workspace]表中:

[tool.uv.workspace] members = [ ".", "airflow-core", "airflow-e2e-tests", "dev/breeze", "dev/mypy", "airflow-ctl", "task-sdk", "chart", "kubernetes-tests", "shared/configuration", "shared/logging", "shared/timezones", # Automatically generated provider workspace members (update_airflow_pyproject_toml.py) "providers/airbyte", "providers/amazon", "providers/apache/beam", "providers/google", # ... # End of automatically generated provider workspace members ]

注意其中以# Automatically generated provider workspace members注释包裹的区块——这些是自动生成的,由prek工具负责维护,开发者不应手工修改。

同文件中的[tool.uv]还定义了 workspace 层面的约束,例如:

[tool.uv] required-version = ">=0.11.8" exclude-newer = "4 days"

required-version是贡献者必须安装的最低uv版本(而非 CI 固定的版本),exclude-newer则限制解析时忽略过于新的包版本,保证依赖解析的可复现性。

2.2 Workspace 带来的开发体验

Workspace 特性使开发者可以在仓库根目录直接运行uv sync,一次性完成三件事:

  1. 以 editable 模式安装所有发行包;
  2. 将所有发行包的依赖放在一起统一解析,从而提前发现不同包之间是否存在版本冲突;
  3. 发行包之间按名称互相引用,因此本地解析时优先使用仓库中的本地版本,而不是 PyPI 上已发布版本。

这意味着你可以同时开发跨多个发行包的改动——例如给被多个 provider 共用的公共发行包新增一个功能,并在本地把所有依赖它的 provider 一起测试,再统一发布。这正是 Airflow 能以单个 monorepo 维持整个生态的前提。

2.3 仓库中的佐证:[tool.uv]的依赖覆盖表

在 pyproject.toml 的[tool.uv.exclude-newer-package]区块中,所有 workspace 成员(包括自动生成的 provider 列表)都被标记为false,即不对这些本地发行包应用exclude-newer限制——因为它们在本地解析时使用源码版本,与"新近发布"无关。这从实现层面印证了 workspace 成员优先使用本地源码的机制。

三、pyproject.toml 中的三个依赖区段

每个发行包的 pyproject.toml(例如 airflow-core/pyproject.toml、task-sdk/pyproject.toml、airflow-ctl/pyproject.toml、各 providers 下的 provider 包)都在以下三个区段中声明依赖:

区段作用何时被安装
[project.dependencies]包的必需依赖不带 extras 安装该包时即安装
[project.optional-dependencies]可选依赖(extras)带 extras 安装时安装,如pip install apache-airflow[ssh]
[dependency-group.dev]开发依赖uv sync默认安装,同时包以 editable 模式安装

以 providers/google/pyproject.toml 为例,其[project]下同时出现了普通依赖与带环境标记的依赖:

dependencies = [ "apache-airflow>=2.11.0", "apache-airflow-providers-common-compat>=1.13.0", "apache-airflow-providers-common-sql>=1.32.0", "asgiref>=3.5.2; python_version < '3.14'", "asgiref>=3.11.1; python_version >= '3.14'", "google-cloud-bigquery-storage>=2.31.0;python_version<'3.13'", "google-cloud-bigquery-storage>=2.33.0;python_version>='3.13'", ]

同一依赖可以针对不同 Python 版本给出不同下界,这也是 Airflow 支持 Python 3.10~3.14 多种解释器的具体体现(见根 pyproject.toml 的 classifiers)。

四、如何正确添加和修改依赖

在 Airflow 中,添加/修改依赖的方式是编辑对应发行包的pyproject.toml。提交前需要遵循一套明确的规则。

4.1 放到正确的区段

新增依赖时首先判断:它是主依赖([project.dependencies])、可选依赖([project.optional-dependencies]),还是开发依赖([dependency-group.dev])。

4.2 警惕自动生成的依赖区块

部分依赖由prekhooks自动生成并覆盖prek通过分析源码中的 import 和项目结构,能自动推导出必要的依赖。以根 pyproject.toml 的[project.optional-dependencies]为例,可以看到明显的自动生成区块:

[project.optional-dependencies] # Automatically generated airflow optional dependencies (update_airflow_pyproject_toml.py) "all-core" = [ "apache-airflow-core[all]" ] "async" = [ "apache-airflow-core[async]" ] # ... "common.compat" = [ "apache-airflow-providers-common-compat>=1.2.1" ] # ... # End of automatically generated airflow optional dependencies

凡是位于# Automatically generated ...# End of automatically generated ...注释之间的内容,都不应手工修改——它们会在prekhook 运行时被整体重写。如果你需要调整,应当修改这些依赖的根来源(如update_airflow_pyproject_toml.py脚本中的生成逻辑),而手工新增的依赖则应放在注释区块之外。

4.3 版本说明符的两条铁律

  • 上界要尽量开放:Airflow 极少对依赖设置上界(upper-bound),只有当已知某个新版本会破坏安装或测试时才上界,且必须注释说明原因。仓库中 providers/google/pyproject.toml 就有一个典型例子:
"google-ads>=26.0.0,!=28.0.0.post2",

以及带详细说明的"抬高下界"注释:

# Floor raised to 2.30.3: google-api-core 2.28.1-2.30.2 has an import-time # performance regression (it scans the whole venv on import), which trips the # Dag import timeout for operators that import it. Fixed in 2.30.3. "google-api-core>=2.30.3",
  • 下界必须存在:任何从 PyPI 解析的依赖都必须有下界。没有下界时,解析器可能选择历史上任意一个旧版本,导致最终安装的版本取决于解析过程而非代码实际需求。prekcheck-dependency-lower-boundshook 会在project.dependenciesproject.optional-dependenciesdependency-groups以及build-system.requires中全面强制这一规则。下界的取值原则是:使用你愿意测试的最老版本,例如:
"pyspark>=4.0.0",

两条豁免情形:属于uvworkspace 成员的发行包(它们从本地源码解析,版本区间无意义)以及直接指定 URL 的依赖(URL 本身已精确指代构件)。

4.4 修改后必须验证

修改依赖后,务必运行uv sync验证 workspace 内各包依赖之间没有冲突:

  • 在仓库根目录运行:同步所有包;
  • 在修改的包目录运行:只同步该包及其依赖;
  • 更彻底的验证:在根目录运行uv sync --all-packages --all-extras,确认所有包连同所有 extras 能一起无冲突安装。

最后再运行全部测试,确保改动没有破坏任何功能。需要注意,--all-extras模式可能较慢且困难,因为部分 extras(如mysqlpostgres)要求系统预先安装客户端库。

4.5 CI 的兜底校验

即使本地没有发现问题,CI 也会对依赖做下界检查——例如逐个 provider 尝试用"尽可能低"的依赖版本解析并运行测试,防止把下界定得过低。因此本地先跑uv sync能尽早发现问题,避免浪费 CI 资源。

五、跨发行包引用:常规依赖与"共享依赖"机制

仓库内存在大量发行包,开发时经常需要在一个包中引用另一个包的功能。Airflow 提供两种方式:

5.1 常规包依赖(Workspace 名称引用)

直接用发行包名称声明依赖即可。例如在apache-airflow-providers-google中引用apache-airflow-providers-common-compat

"apache-airflow-providers-common-compat>=1.13.0",

得益于 workspace 特性,uv sync时会自动使用本地版本。这类依赖如果位于源码顶层 import 中,prekhook 通常能自动识别并写入自动生成区块;如果依赖没有出现在顶层 import(例如条件导入),则需要手工添加。

5.2 共享依赖(Shared Dependencies)机制

对于需要在多个发行包之间"静态链接"的公共代码,Airflow 采用了自研的 shared dependencies 机制,避免不必要的耦合和循环依赖。详细设计见 shared/README.md,其核心要点包括:

  • 共享方式:使用仓库内符号链接(symlink),同一份代码只保存一份,无需prek更新多份拷贝;
  • 动机:如果两个发行包同时依赖 PyPI 上的某个共享发行包,就会引入"版本地狱";改为类似 vendoring/静态链接的方式,每个发行包自带它验证过的版本,可以在同一 Python 环境中共存;
  • 导入约束:共享库内部引用其他共享库必须使用相对导入(与 Airflow 主代码库禁止相对导入的约定相反),例如from ..timezones.timezone import is_naive
  • 目录结构:共享库按airflow_shared/<name>组织,如shared/timezonesshared/logging
  • 打包集成:使用共享库的发行包需要在pyproject.toml[tool.airflow]中声明shared_distributions,并在[tool.hatch.build.targets.sdist.force-include]中把共享源码"复制"进 sdist(符号链接在构建 sdist 时无效,构建 wheel 时会解析符号链接)。

六、# use next version:跨包新特性的协作约定

当你给某个公共发行包(如apache-airflow-providers-common-compat)添加新特性,并希望同一 PR 中的另一个发行包立即使用它时,就会面临一个时间差问题:新特性只会在公共包未来的发布中才可用,你不能在依赖里写上尚未发布的版本号。

Airflow 的解决方案是:贡献者永远不手工修改跨包依赖的版本,版本号只由 Release Manager 在同时准备两个包的发布时统一提升。贡献者的责任是在依赖行末尾加上一个精确的注释

"apache-airflow-providers-google>=1.2.0", "apache-airflow-providers-common-compat>=5.5.0", # use next version "requests>=2.25.1",

⚠️警告:必须使用精确的注释文本# use next version,否则自动化工具无法识别。

配套的自动化机制包括:

  • 检查普通 PR 不会修改这类跨依赖版本;
  • 在准备发布时,自动将带此注释的依赖更新为即将发布的下一个版本(例如上例中的5.6.0),并确保两个包同步发布;
  • prekhook 自动生成了跨包依赖行时(位于自动生成注释区块内),你应当把这行复制到注释区块并加上# use next version,下次运行prek时自动生成的行会被删除,只保留手工添加的那行。

6.1 common.compat 的特殊检查

common.compatprovider 是变更最频繁、最常触发连锁更新的公共包,因此对它有专门的自动化检查:一旦修改了common.compat且其他 provider 需要随之更新,Selective Check CI 任务会报错提醒你为相关 provider 添加# use next version注释。如果确认没有其他 provider 需要更新,可以给 PR 打上skip common compat check标签跳过检查(该标签只有 maintainer 和 collaborator 能添加)。

6.2 强制最低版本(MIN_VERSION_OVERRIDE)

部分依赖存在强制最低版本,主要出于 Airflow 3 最低版本兼容性考虑(例如gitcommon.messaging等 provider),或已知旧版本功能已失效(如amazonfab)。这些版本由 scripts/ci/prek/update_airflow_pyproject_toml.py 中的MIN_VERSION_OVERRIDE字典控制(第 91 行起定义),生成的依赖行带固定注释:

"apache-airflow-providers-fab>=2.2.0", # Set from MIN_VERSION_OVERRIDE in update_airflow_pyproject_toml.py

如果某发行包依赖了比强制值更新的版本,该注释不会出现。你可以自由把这类版本改成更高值,prek会自动移除注释。

七、Airflow 的双重身份与约束文件方案

7.1 为什么需要约束文件

Airflow 不是标准的 Python 项目。绝大多数 Python 项目可以归入两类:

  • 应用(application):依赖应该被固定(pin),保证未来安装的稳定性——因为新的(甚至传递的)依赖可能导致安装失败;
  • 库(library):依赖应该保持开放,允许多个有相同需求的库共存。

而 Airflow 同时是两者:它是用户要安装的应用,也是开发者编写自定义 operator 和 DAG 时依赖的。这个看似无解的矛盾,最终靠固定的约束文件(pinned constraints files)解决。

7.2 为什么不用标准方案

因为 Python 生态的现有标准尚未跟上"既是库又是应用"的复杂项目的可复现安装需求。Airflow 的做法更像一个弥补现有工具局限的"hack"。近年来标准讨论有所进展:PEP 751提出了pylock.toml格式用于记录安装可复现的依赖,但截至 2025 年 11 月,该格式在pip中仍是实验性的,uv也只是将其作为自有锁文件(面向开发环境)的导出格式,尚不足以支撑 PEP 751 设想的可复现安装场景,PEP 751 本身也尚未完整到能支持该用途。社区仍在推进后续 PEP 以支持 Airflow 多年前用约束文件 hack 实现的这种可复现安装流程。

7.3 官方支持的安装工具

只有 `pip` 和 `uv` 的安装方式得到官方支持。
  • 虽然用poetrypip-tools安装也有成功案例,但它们与pip的工作流不共享——尤其在约束(constraint)与需求(requirement)的管理上差异明显,目前不支持通过 Poetry 或 pip-tools 安装。uv通过uv pip遵循pip的方式,因此可以正常工作。
  • 已知bazel存在可能导致循环依赖的问题,遇到时请改用piprules_python社区已在跟进解决,较新版本的 bazel 或许能处理)。
  • 若坚持使用上述工具,应把约束文件转换为目标工具要求的格式和工作流后再使用。

7.4 三套约束文件

默认情况下,pip install apache-airflow安装的依赖尽可能开放,因此当某个直接或传递依赖发布破坏性新版本时,安装可能失败。此时需要提供额外约束,例如:

pip install apache-airflow==1.10.2 Werkzeug<1.0.0

Airflow 维护三套约束文件(均以 Python 主次版本号命名,如constraints-3.10.txt):

约束集生成方式用途
constraints匹配当前源码中的 Airflow 版本 + 从 PyPI 安装的 providers普通用户用 pip 安装 Airflow
constraints-source-providers使用当前源码安装的 providers 生成CI 系统维持"稳定"约束;从源码以 editable 模式安装时使用
constraints-no-providers仅 Apache Airflow 本体,不含任何 provider想单独管理 Airflow、再逐个添加 provider 的场景

从 PyPI 包安装(可重复安装):

pip install "apache-airflow[google,amazon,async]==3.0.0" \ --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-3.0.0/constraints-3.10.txt"

从源码以 editable 模式安装(应使用constraints-source-providers,它考虑了部分 provider 尚未发布、需求可能冲突的情况):

pip install -e ".[devel]" \ --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-main/constraints-source-providers-3.10.txt"

带 extras 从源码安装

pip install ".[ssh]" \ --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-main/constraints-source-providers-3.10.txt"

只更新 Airflow 本体依赖、忽略 providers

pip install . --upgrade \ --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-main/constraints-no-providers-3.10.txt"

注意:不同 Python 主/次版本对应不同的约束文件,务必为当前解释器选择正确的文件。

7.5 约束文件的生成机制

约束文件由仓库中提交的uv.lock文件生成:通过uv export --frozen把锁文件导出为适合pip install --constraint的扁平固定版本列表。这意味着约束文件始终与开发者uv sync安装的依赖版本保持一致

在仓库的 constraints 目录中可以查看相关说明(constraints/README.md)。此外,constraints-<PYTHON_MAJOR_MINOR_VERSION>.txtconstraints-no-providers-<PYTHON_MAJOR_MINOR_VERSION>.txt会在pyproject.toml更新并推送后由 CI 任务在测试通过时自动重新生成——开发者无需手工维护这两个文件。

八、apache-airflow 包的可选依赖(Extras)

安装 Airflow 时可以指定大量 extras,例如pip install -e .[ssh](editable 安装)或pip install apache-airflow[ssh](普通安装)。extras 分两类:

  • 常规 extras:面向最终用户,如sshgoogleamazonasync
  • 开发类 extras:editable 模式下用于本地测试的devel,以及用于构建文档的doc(安装文档构建工具)。

需要特别说明的是:部分 extras 只定义在元发行包apache-airflow,并不在airflow-core或其他包中定义。从根 pyproject.toml 的[project.optional-dependencies]可以看到这种"代理"式定义——很多 extras 只是转发到 core 包或 provider 包:

"async" = [ "apache-airflow-core[async]" ] "amazon" = [ "apache-airflow-providers-amazon>=9.0.0" ] "common.compat" = [ "apache-airflow-providers-common-compat>=1.2.1" ]

把 provider 依赖和这类 extras 从apache-airflow包自动复制到各发行包,同样由prekhooks 完成。此外还有一些为常用可选功能手工定义的 extras。

完整的 extras 清单可查阅仓库内的 airflow-core/docs/extra-packages-ref.rst(extras reference)。

九、小结与延伸阅读

Airflow 的依赖管理可以概括为三句话:

  1. 开发期:用uvworkspace 把所有发行包绑定在 monorepo 中统一解析、editable 安装,prekhooks 自动维护可推导的依赖区块;
  2. 协作期:跨包依赖的版本号一律交给 Release Manager,贡献者只用# use next version注释表达"发布时请提升版本"的意图;
  3. 发布与安装期:用从uv.lock导出的三套constraints-*.txt约束文件,让 Airflow 同时满足"应用级可复现安装"与"库级开放依赖"的双重需求。

对开发者而言,动手修改依赖时的核心检查清单是:选对区段、避开自动生成区块、保证下界存在且合理、上界只在必要时添加并注释理由、修改后运行uv sync(必要时加--all-packages --all-extras)并跑全量测试。

如果你接下来需要更新 Airflow 的元数据库结构,可以继续阅读 contributing-docs/14_metadata_database_updates.rst 了解迁移流程。

【免费下载链接】airflowApache Airflow - A platform to programmatically author, schedule, and monitor workflows项目地址: https://gitcode.com/GitHub_Trending/ai/airflow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 10:37:26

MuJoCo惯性参数:从“模型飞散“到稳定仿真的5行XML起步路径

MuJoCo惯性参数&#xff1a;从"模型飞散"到稳定仿真的5行XML起步路径 【免费下载链接】mujoco Multi-Joint dynamics with Contact. A general purpose physics simulator. 项目地址: https://gitcode.com/GitHub_Trending/mu/mujoco MuJoCo惯性参数配置是决定…

作者头像 李华
网站建设 2026/9/11 10:36:59

大模型应用的上下文管理:context-mode 调度方案实战总结

你搜一下 context-mode 这个词&#xff0c;能看到很多种答案&#xff1a;编辑器里的上下文模式、终端工具的上下文感知、甚至游戏设备的按键配置方案。但在我做大半年大模型应用之后&#xff0c;对这个词有了自己的理解——它是夹在会话状态和大模型 API 之间的一整套上下文调度…

作者头像 李华
网站建设 2026/9/11 10:35:54

G-Helper:华硕笔记本轻量控制工具,一个 exe 替代 Armoury Crate

G-Helper&#xff1a;华硕笔记本轻量控制工具&#xff0c;一个 exe 替代 Armoury Crate 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, V…

作者头像 李华
网站建设 2026/9/11 10:35:13

MNIST手写数字识别CNN实战:从数据下载404到99%准确率

刚开始学手写数字识别 CNN 模型的时候&#xff0c;我以为最麻烦的部分在卷积层怎么设计、梯度怎么回传。结果真正动手第一天就被数据集卡住了——torchvision 下载 MNIST 一直报 404&#xff0c;进度条走到一半直接失败&#xff0c;重试三次都一样。后来把问题彻底查清楚&#…

作者头像 李华
网站建设 2026/9/11 10:34:27

新人入职第一天,Agent 就能告诉他“这个接口为什么这么设计“

新人问&#xff1a;"这个接口为什么要用回调而不是同步&#xff1f;"以前只有老员工知道答案。如果 Agent 也能回答呢&#xff1f; 新人上手慢&#xff0c;从来不是因为不会写代码 做了几年技术 TL&#xff0c;带过的新人不少。我发现一个规律&#xff1a;上手慢的…

作者头像 李华