Migration Helper
airflow-migration-helper is a Python project for helping migrate Apache
Airflow code from Airflow 2 to Airflow 3. The PyPI distribution installs the
command-line tool as migration-helper.
The current implementation is focused on static analysis and migration assistance for DAG code. It includes checks based on Ruff AIR rules, Airflow configuration linting, DAG import checks, dependency compatibility checks, base reporting, shared subprocess helpers, file selection helpers, and tests.
How it works
migration-helper migrates code from Airflow 2 to Airflow 3, so it is run
against a target project that is currently on Airflow 2:
- The analyzed project (DAGs, configs, dependencies) stays on Airflow 2.
- The tool runs in its own dedicated virtual environment (see Requirements and setup).
Which Airflow version the checker environment needs depends on the checker:
ruffanddep_checkanalyze the target statically and do not require Airflow at all.airflow_configchecks config keys against a built-in static rule table, so it also works without Airflow. When Airflow is installed in the checker environment, the checker additionally runsairflow config lintand reports more findings. Note that the Airflow CLI commands used by this checker operate on the active configuration of the checker environment (AIRFLOW_HOME/AIRFLOW_CONFIG), not onairflow.cfgfiles found in the target; only the static scan covers target files. To lint or update a specific config file through the CLI path, pointAIRFLOW_CONFIGat it.dag_checkrunsairflow dags list-import-errorsfrom the checker environment, so it parses the Airflow 2 DAGs under the Airflow installed in that environment (Airflow 3 for full compatibility checks).db_migrateruns the one-shot, read-onlyairflow db check-migrationscommand from the checker environment. No running Airflow processes are needed, but a reachable metadata database must be configured for that environment. The stage is only enabled with--db-migrate.
For full functionality (all checkers, including dag_check), install the tool
together with Airflow 3 in the same virtual environment:
python -m venv .venv-mh
.venv-mh/bin/pip install "airflow-migration-helper[airflow]"
Expected Output
After running migration-helper, you get a practical migration report for the
selected Airflow project. The report lists compatibility issues by file and rule,
shows error/warning severity, and explains whether each finding can be handled
automatically or needs manual review.
Reports can be written in JSON and HTML formats. The JSON report is intended for automation and CI, while the HTML report is intended for manual review. Both formats expose the same issue details.
The JSON report has this top-level structure:
generated_at: UTC timestamp when the report was created.target: analyzed file or directory.summary: total number of findings, split intoerrorsandwarnings.by_file: findings grouped by affected file path.by_type: findings grouped byrule_id.
Each finding contains:
file: affected file or logical target, for exampleairflow.cfgorrequirements.txt.lineandcol: source location when the checker can provide it; otherwisenull.rule_id: checker-specific rule or error identifier, for exampleAIR301,CFG001, orDEP_RESOLUTION_FAILED.message: short explanation of the problem.severity:error,warning, orinfo.source: checker that produced the finding, for exampleruff,airflow_config,dag_check,dep_check, ordb_migrate.
Reports use these fields to describe fixability:
fix_safety: "safe"means the checker knows a safe mechanical fix or safe migration action.migration-helper fix TARGET --safe-onlyapplies safe Ruff fixes and, when Airflow is available in the checker environment, also runsairflow config update --fixon that environment's active config (see How it works). Both paths are blocked by--dry-run.fix_safety: "unsafe"means the tool knows the likely change, but a human should review it before applying.fix_safety: null(orfix_safety: "manual"in HTML report) means there is no automatic fix path in the current implementation. Treat it as manual work.fix_descriptionis the human-readable hint for what to change or which command/path is relevant.
Today, DAG import errors, dependency conflicts, and DB migration findings are diagnostic/manual. Source-level DAG fixes outside Ruff are still planned.
Requirements and setup
- Python
>=3.10 gitavailable inPATH- Work in a dedicated virtual environment (see below)
Install airflow-migration-helper and its dependencies (ruff and others)
into a dedicated virtual environment, not into the system Python. This keeps
the tool's dependencies isolated from the analyzed project.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
Install from PyPI:
pip install airflow-migration-helper[airflow]
Offline installation
To install on a machine without access to PyPI, build a wheelhouse on a machine with internet access and install from it:
# On a machine with internet access
pip download airflow-migration-helper[airflow]==1.0.3 -d wheelhouse/
# On the offline machine
pip install --no-index --find-links ./wheelhouse airflow-migration-helper
Note: ruff is a platform-specific binary package. Download the wheelhouse on
a machine with the same OS and the same Python version as the target machine.
If the wheelhouse must be prepared for a different platform than the download
machine, pass the target platform tags explicitly. This works only with
pre-built wheels, so --only-binary=:all: is required:
# Build a wheelhouse for manylinux x86_64 + CPython 3.11 from any machine
pip download airflow-migration-helper[airflow]==1.0.3 -d wheelhouse/ \
--only-binary=:all: \
--platform manylinux2014_x86_64 \
--python-version 311 \
--implementation cp \
--abi cp311
ruff ships platform-specific wheels (py3-none-manylinux2014_x86_64,
macosx_11_0_arm64, win_amd64, ...) so the --platform tag matters most;
packaging and tomli are pure Python and work everywhere. Repeat the command
per target platform (for example macosx_11_0_arm64 or win_amd64) or build
the wheelhouse on a machine matching the target configuration.
Alternatives: copy a prepared virtual environment as a whole (same OS /
architecture / Python required), or serve the packages from an internal PyPI
mirror (devpi, Nexus, Artifactory) and use pip config set global.index-url http://<mirror>/.
Offline usage notes: Airflow 3.2.1 constraints for Python 3.10-3.14 are bundled
with the package, so the default dependency check works offline. Other Airflow
versions require network access to download constraints, and the deep
dependency check (pip install --dry-run / uv pip compile) needs access to a
package index; point it at an internal one via PIP_INDEX_URL / UV_INDEX_URL
if needed. git must still be available in PATH.
For development, install with test and Airflow extras instead:
pip install -e ".[dev]"
This editable install is required if you want to run the CLI as
migration-helper from the repository checkout. Without it, use
python -m migration_helper.cli ... from the repository root.
Usage
migration-helper COMMAND [TARGET] [OPTIONS]
Use the built-in help commands to see available options:
migration-helper --help
migration-helper analyze --help
migration-helper fix --help
Commands
analyze: scanTARGETfor Airflow 3 compatibility issues in read-only mode.fix: scanTARGETand apply or dry-run available auto-fix paths.
Shared Options
TARGET: directory or file to scan. Defaults to the current directory.--report-dir PATH,-r PATH: writemigration_report.jsonand/ormigration_report.htmlintoPATH.--output-format json|html|all: report format when--report-diris set. Default:all.--modified-only: only check files reported bygit status --porcelainunder the target path. If git state cannot be resolved, all files are checked.--target-airflow-version VERSION: Airflow version for dependency checks. Default:3.2.1.--target-python-version VERSION: Python major/minor version for dependency constraints. Defaults to the current interpreter version.--dependency-file FILE: dependency file to check at the target root. Supports requirements-style.txtfiles andpyproject.toml. Default:requirements.txt.--use-uv: useuvinstead ofpipfor dependency resolver checks.--db-migrate: enable the DB migration stage. The stage runs the read-onlyairflow db check-migrationscommand and reports a warning (DB001) when the configured metadata database has pending migrations. By default it uses the checker environment's own database; to check the Airflow 2 cluster metadata database, point the environment's connection string (for exampleAIRFLOW__DATABASE__SQL_ALCHEMY_CONN) at it. Applying migrations is not automated; runairflow db migrateyourself after reviewing the report.--quiet,-q: suppress progress and summary output. Exit codes are still set.
Fix Options
--safe-only: enable safe mechanical fixes where they are currently implemented. Today this mainly drives Ruff/config fix paths; the source-level DAG fixer is still a placeholder.--unsafe: also enable unsafe fixes that require human review. Implies safe fixes.--dry-run: guarantee that files and the database are not modified. In the current implementation, dry-run disables Ruff/config fix application before the pipeline runs, so it is mostly a scan/report safety check rather than a full diff preview.--fix-config: request Airflow config updates explicitly. In the current implementation any fix mode (--safe-only/--unsafe) already enables the config update path, so the flag is effectively redundant. Config update still respects--dry-run.
Exit Codes
0: no error-severity issues found. Warnings may still be present.1: one or more error-severity issues found.2: invalid arguments or theTARGETpath does not exist.
Examples
# Read-only scan of all DAGs in ./dags/
migration-helper analyze ./dags/
# Scan and write HTML + JSON reports
migration-helper analyze ./dags/ --report-dir ./reports/
# Check dependencies against a specific Airflow/Python target
migration-helper analyze ./dags/ --target-airflow-version 3.2.1 --target-python-version 3.10
# Only report issues in files changed in the current git working tree
migration-helper analyze ./dags/ --modified-only
# Dry-run the current fix command path without writing files
migration-helper fix ./dags/ --safe-only --dry-run
# Apply currently available safe fixes in-place
migration-helper fix ./dags/ --safe-only
# Quiet mode for CI
migration-helper analyze ./dags/ --quiet
echo $? # 0 = clean, 1 = errors
License
Licensed under the Apache License, Version 2.0.
See the LICENSE file for the full license text.
Common Commands
Run unit tests:
pytest tests/unit
Run integration tests:
pytest -m integration tests/integration
Run all regular tests, excluding integration and e2e by default:
pytest
Run e2e tests explicitly:
python -m pytest tests/e2e -m e2e
If you would like to test the tool with your own DAGs, place them in test_dags
or another local folder and run the tool based on the examples above.
E2E Testing
The project keeps a small Airflow 2 DAG corpus under
tests/e2e/airflow2-dag-corpus/ and separate runtime provider fixtures under
tests/e2e/airflow2-provider-fixtures/. The corpus is the migration-helper
target; provider fixtures are import support only.
Run all e2e checks explicitly:
python -m pytest tests/e2e -m e2e
Run the Airflow 2 baseline import smoke:
cd tests/e2e
docker compose -f docker-compose.airflow2.yml run --rm airflow2-smoke
For the current e2e layout, DAG corpus contents, dry-run artifacts under
tests/e2e/runs/, and the difference between analyze and fix --dry-run, see
tests/e2e/README.md.
Docker
The project includes a multi-stage Dockerfile and docker-compose.yml.
Two images are built:
- production: runtime dependencies used for running
migration-helpercommands. - dev: development/test dependencies used for running tests.
Build
docker compose build
Prepare DAG files
Place your DAG files in test_dags/ (this directory is git-ignored):
mkdir -p test_dags
cp path/to/your_dag.py test_dags/
Run analysis (read-only)
The migration-helper-analyze service mounts ./test_dags at /dags as
read-only, which is appropriate for analyze:
docker compose run --rm migration-helper-analyze
Or pass any command and options directly:
docker compose run --rm migration-helper-analyze analyze /dags
docker compose run --rm migration-helper-analyze analyze /dags --modified-only
docker compose run --rm migration-helper-analyze analyze /dags/demo.py
Apply fixes (writable)
The migration-helper-fix service mounts ./test_dags at /dags as writable
so Ruff can rewrite .py files in place:
docker compose run --rm migration-helper-fix
Examples:
docker compose run --rm migration-helper-fix fix /dags --safe-only --dry-run
docker compose run --rm migration-helper-fix fix /dags/demo.py --safe-only
docker compose run --rm migration-helper-fix fix /dags/demo.py --unsafe
Run tests
Unit tests:
docker compose run --rm test
Integration tests:
docker compose run --rm test -m integration tests/integration
E2E tests:
docker compose run --rm test tests/e2e -m e2e
E2E dry-run reports are written to:
tests/e2e/runs/<UTC timestamp>/
Open the latest run directory and check
analyze/analyze-report/migration_report.html,
fix-dry-run/fix-dry-run-report/migration_report.html,
fix-safe/fix-safe-report/migration_report.html, or
fix-unsafe/fix-unsafe-report/migration_report.html.
The main docker-compose.yml mounts ./tests to /app/tests, so reports
created in the test container are available on the host immediately. No
docker cp is needed.
Release files for airflow-migration-helper 1.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| airflow_migration_helper-1.0.3.tar.gz | 217.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| airflow_migration_helper-1.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 297.0 kB
Release files / airflow_migration_helper-1.0.3.tar.gz
| Download URL | airflow_migration_helper-1.0.3.tar.gz |
|---|---|
| Size | 217.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7fe2dc1f08256f39488cdb61758e4f71b964bebb51b37afb59f5b47e347baf76
|
|
BLAKE2b-256 checksum How to use checksums |
b5b010f61f95e0b0262925a064a3de3b0d17207a5daeea46ce65321868b201fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Release files / airflow_migration_helper-1.0.3-py3-none-any.whl
| Download URL | airflow_migration_helper-1.0.3-py3-none-any.whl |
|---|---|
| Size | 79.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
07b2887a3bac852feb1a4699ebcf7924b5c3a655039d1bb00dc0c63e7ebcc707
|
|
BLAKE2b-256 checksum How to use checksums |
64847630c744f8e1e4f92009d61812dc8f27eba84a95e835d1072ad4bad6c3d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|