As I user of Airflow I have ended up implementing this for my environments:
pip install apache-airflow[all or select-extras] -c {official constraints} > smaller-run-time-constraints.txt pip-compile {all my requirements including development}.txt -c smaller-run-time-constraints.txt Exactly to remove the dev requirements airflow has from interfering with our own dev requirements. Though this approach notably doesn't change any of the used non-dev requirements, it's not clear to me (and I'm probably just missing something) why your diff includes non-dev requirements. Damian -----Original Message----- From: Jarek Potiuk <ja...@potiuk.com> Sent: Tuesday, July 22, 2025 11:04 AM To: dev@airflow.apache.org Subject: [DISCUSS] Removing dev dependencies from PyPI constraints ? Hello here, *Tl;DR; We had an interesting discussion on Slack today and I wanted to bring it here to discuss what we should do - whether we should keep devel dependencies in our PyPI constraints or not.* *A bit of context:* In our CI we generate several types of constraints. one of them are "source" constraints that we use during our CI testing - including all our testing tools, mypy, ruff etc., and another type is "PyPI" constraint - which is more <What should be the "golden" versions of dependencies when you install Airflow using `pip install` for production>. The main difference between the two (roughly) is that "source constraints" only uses dependencies from "branch tip" ("main", "v3-0-test"), where "PyPI constraints" uses "Airflow core" branch-tip dependencies - but all the providers are installed from PyPI. There are few nuances when some packages are being released but basically - for example for Airflow 3.0.3, "PyPI" constraints is "what is the current set of dependencies and providers when we install airflow from PyPI at the moment of release". So target for "source" constraints are "Airflow contributors" where target for "PyPI constraints" are "Airflow users". *The problem* In the slack discussion https://apache-airflow.slack.com/archives/C06K9Q5G2UA/p1753169914722049 - slack user @dashmug (sorry - do not know name) noticed that the "PyPI" constraints also contain Airflow development dependencies listed (like ruff or mypy). That made me think a bit and I quickly came up with PR that could "fix" it - i.e. only keep the "real" constraints in. https://github.com/apache/airflow/pull/53631 - it's as easy as adding the `--exact` (and `--strict` for verification) flag to the `uv pip install` command we use. I agree it's a bit confusing to have the devel dependencies there - it is not the "intention", but there is very little problem with it - the fact that mypy or ruff are listed with a version in constraints, does not mean it must be installed - constraints mainly say "if you install this, it should be this version" . And there is no problem whatsoever (and it is even recommended by us) to have "airflow with its deps" installed with constraints, and then anything else you need to install or update - without. So other than potential confusion, it usually does not cause harm. Now... It might seem that removing devel deps has no side effects and we should **just do it** - but it's not that easy. Main problem is that those devel dependencies might "hold" other dependencies (directly or transitively) from being upgraded - and when we remove the devel dependencies and do the resolution again - we might have a DIFFERENT set of dependencies, simply because removing the devel dependencies, might "free" the other deps and they might get upgraded. Yeah - I know it's not obvious for someone who did not spend 3 years solving dependency issues :). And the problem with it is that because we need those devel deps to test things - the upgraded dependencies might have never been tested in our CI - because we never upgraded to later versions. It's a bit of Heisenberg-effect. By using test tools we are changing the state of the tested thing. Currently our "PyPI" constraints reflect the state of Airflow "when the testing tools are installed" - and we are generally not able to test the state of Airflow "without the testing tools" - by the sheer fact that we do not have those tools :) Just to illustrate the case - you can see what happens when we removed devel deps: https://github.com/apache/airflow/actions/runs/16444553234 - in summary you can see a colored version that is a bit clearer but I dumped the diff below from an example - Python 3.10 case. Left side < is "what was with testing tools" and > is without them. You can see a LOT of test tools removed (pytest, ruff and others - including dependencies used only by those tools) But also you can see (likely some of the devel/test deps limited those) * Authlib - upgraded from 1.3.1 to 1.6.1 * Google - genai upgraded from 1.2.0 to 1.20.0 * Httpx - upgraded from 0.27.0 to 0.28.1 * Pinotdb - upgraded from 5.6.0 to 5.7.0 * Sentry-sdk - upgraded from 2.33.1 to 2.33.2 (this is false positive as it has just been released and new version was picked) * validators - upgraded form 0.34.0 to 0.35.0 * weaviate-client - upgraded from 4.9.0 to 4.16.3 * websockets - upgraded from 14.2 to 15.0.1 This means that all the "upgraded" packages above have not been - likely ever tested in our CI. And when we put them in "versioned" constraints, it might mean that they will be obviously failing. So ... there is a risk connected with just generating the "non-devel" constraints without additional tests. But that also means that the user who does not use constraints might get those versions anyway - default behaviour of `pip install airflow` will be to install those upgraded versions above if you do not use constraints. But also that is no different than installing a released airflow version a few weeks or months after release without constraints - there will be tens of packages that were released since our release and installing airflow without constraints will pick those. However - when we manually install RC candidates - say by running airflow from rc1 image, we will use those "upgraded" dependencies - so some "obvious" problems might be caught during the rc manual testing - where you do not use test tools but run airflow "as if" it was installed in production. *The solution ? * There are few ways we can proceed with it: a) leave it as is - no big harm, potential confusion, by people who see it - potential differences b) remove devel following my PR and take the risk that we never tested (in CI) the dependencies we set in the constraints. That's a bit risky, but we can always reactively downgrade some of those packages after the fact if users will start reporting the issues c) potentially most complex thing - trying to remove devel dependencies somewhat combining the two - for example removing all the deps that would be removed in b) from a) - but that has also some edge cases - when different versions of dependencies can add or upgrade their dependencies, we are very likely to occasionally remove or add too much. I wonder (for those who managed to read that far - likely not many - what do you think here? What would be the best option ? Maybe there are other options I have not thought about ? J ----- The diff for Python 3.10 34c34 < Authlib==1.3.1 --- > Authlib==1.6.1 64d63 < Sphinx==8.2.3 78d76 < aioresponses==0.7.8 82d79 < alabaster==1.0.0 198d194 < arro3-core==0.5.1 202d197 < astroid==3.3.11 211,212d205 < aws-sam-translator==1.99.0 < aws-xray-sdk==2.14.0 258,259d250 < cfgv==3.4.0 < cfn-lint==1.38.0 262d252 < checksumdir==1.2.0 277d266 < coverage==7.9.2 291d279 < deltalake==1.1.0 293d280 < diagrams==0.24.4 301,302d287 < docutils==0.21.2 < duckdb==1.3.2 308d292 < eralchemy2==1.4.1 311d294 < execnet==2.1.1 321,322d303 < flit==3.12.0 < flit_core==3.12.0 386c367 < google-genai==1.2.0 --- > google-genai==1.20.0 389d369 < graphql-core==3.2.6 398d377 < grpcio-tools==1.62.3 403,404d381 < hatch==1.14.1 < hatchling==1.27.0 413c390 < httpx==0.27.0 --- > httpx==0.28.1 418d394 < hyperlink==21.0.0 421,423d396 < icdiff==2.0.7 < id==1.5.0 < identify==2.6.12 426d398 < imagesize==1.4.1 430d401 < incremental==24.7.2 433,435d403 < iniconfig==2.1.0 < inputimeout==1.0.4 < ipdb==0.13.13 449d416 < joserfc==1.2.2 451d417 < jsonpatch==1.33 455,456d420 < jsonpointer==3.0.0 < jsonschema-path==0.3.4 463d426 < kerberos==1.3.1 465,466d427 < keyrings.alt==5.0.2 < kgb==7.2 494,495d454 < mmh3==5.1.0 < mongomock==4.3.0 497,498d455 < moto==5.1.8 < mpmath==1.3.0 508,512d464 < mypy-boto3-appflow==1.39.0 < mypy-boto3-rds==1.39.1 < mypy-boto3-redshift-data==1.39.0 < mypy-boto3-s3==1.39.5 < mypy==1.17.0 514c466 < mysql-connector-python==9.3.0 --- > mysql-connector-python==9.4.0 521,523d472 < networkx==3.5 < nh3==0.3.0 < nodeenv==1.9.1 528,529d476 < openapi-schema-validator==0.6.3 < openapi-spec-validator==0.7.2 560d506 < pathable==0.4.4 563d508 < pdbr==0.9.2 569,570c514 < pinotdb==5.6.0 < pipdeptree==2.28.0 --- > pinotdb==5.7.0 574d517 < plyvel==1.5.1 577,579d519 < pprintpp==0.4.0 < pre-commit-uv==4.1.4 < pre_commit==4.2.0 592d531 < py-partiql-parser==0.6.1 608d546 < pyenchant==3.2.2 610,611d547 < pygraphviz==1.14 < pyiceberg==0.9.1 622,632d557 < pytest-asyncio==0.25.0 < pytest-cov==6.2.1 < pytest-custom-exit-code==0.3.0 < pytest-icdiff==0.9 < pytest-instafail==0.5.0 < pytest-mock==3.14.1 < pytest-rerunfailures==15.1 < pytest-timeouts==1.2.1 < pytest-unordered==0.7.0 < pytest-xdist==3.8.0 < pytest==8.4.1 642d566 < python-on-whales==0.78.0 652d575 < readme_renderer==44.0 659d581 < requests-mock==1.12.1 664,665d585 < responses==0.25.7 < restructuredtext_lint==1.4.0 667,668d586 < rfc3339-validator==0.1.4 < rfc3986==2.0.0 670d587 < rich-click==1.8.9 674d590 < roman-numerals-py==3.1.0 679d594 < ruff==0.12.3 688d602 < semver==3.0.4 690,691c604 < sentinels==1.0.0 < sentry-sdk==2.33.1 --- > sentry-sdk==2.33.2 703d615 < snowballstemmer==3.0.1 709,725d620 < sphinx-argparse==0.5.2 < sphinx-autoapi==3.6.0 < sphinx-autobuild==2024.10.3 < sphinx-copybutton==0.5.2 < sphinx-jinja==2.0.2 < sphinx-rtd-theme==3.0.2 < sphinx_design==0.6.1 < sphinxcontrib-applehelp==2.0.0 < sphinxcontrib-devhelp==2.0.0 < sphinxcontrib-htmlhelp==2.1.0 < sphinxcontrib-httpdomain==1.8.1 < sphinxcontrib-jquery==4.1 < sphinxcontrib-jsmath==1.0.1 < sphinxcontrib-qthelp==2.0.0 < sphinxcontrib-redoc==1.6.0 < sphinxcontrib-serializinghtml==2.0.0 < sphinxcontrib-spelling==8.0.1 737d631 < strictyaml==1.7.3 740d633 < sympy==1.14.0 752d644 < time-machine==2.16.0 755d646 < tomli_w==1.2.0 758d648 < towncrier==24.8.0 762,763d651 < trove-classifiers==2025.5.9.12 < twine==6.1.0 765,774d652 < types-Deprecated==1.2.15.20250304 < types-Markdown==3.8.0.20250708 < types-PyMySQL==1.1.0.20250711 < types-PyYAML==6.0.12.20250516 < types-aiofiles==24.1.0.20250708 < types-certifi==2021.10.8.3 < types-cffi==1.17.0.20250523 < types-croniter==6.0.0.20250626 < types-docutils==0.21.0.20250722 < types-paramiko==3.5.0.20250708 776,778d653 < types-pyOpenSSL==24.1.0.20240722 < types-python-dateutil==2.9.0.20250708 < types-python-slugify==8.0.2.20240310 780d654 < types-redis==4.6.0.20241004 782,784d655 < types-setuptools==80.9.0.20250529 < types-tabulate==0.9.0.20241207 < types-toml==0.10.8.20240310 794d664 < userpath==1.9.2 799c669 < validators==0.34.0 --- > validators==0.35.0 806c676 < weaviate-client==4.9.6 --- > weaviate-client==4.16.3 809c679 < websockets==14.2 --- > websockets==15.0.1 815d684 < yamllint==1.37.1 ________________________________ Strike Technologies, LLC (“Strike”) is part of the GTS family of companies. Strike is a technology solutions provider, and is not a broker or dealer and does not transact any securities related business directly whatsoever. This communication is the property of Strike and its affiliates, and does not constitute an offer to sell or the solicitation of an offer to buy any security in any jurisdiction. It is intended only for the person to whom it is addressed and may contain information that is privileged, confidential, or otherwise protected from disclosure. Distribution or copying of this communication, or the information contained herein, by anyone other than the intended recipient is prohibited. If you have received this communication in error, please immediately notify Strike at i...@striketechnologies.com, and delete and destroy any copies hereof. ________________________________ CONFIDENTIALITY / PRIVILEGE NOTICE: This transmission and any attachments are intended solely for the addressee. This transmission is covered by the Electronic Communications Privacy Act, 18 U.S.C ''2510-2521. The information contained in this transmission is confidential in nature and protected from further use or disclosure under U.S. Pub. L. 106-102, 113 U.S. Stat. 1338 (1999), and may be subject to attorney-client or other legal privilege. Your use or disclosure of this information for any purpose other than that intended by its transmittal is strictly prohibited, and may subject you to fines and/or penalties under federal and state law. If you are not the intended recipient of this transmission, please DESTROY ALL COPIES RECEIVED and confirm destruction to the sender via return transmittal.