Skip to main content

A linting tool for Apache Airflow DAG files

Project description

DAGLint

A linting tool for Apache Airflow DAG files. Uses Python's AST to enforce standardisation and best practices across your DAG codebase.

Inspired by "Mastering Airflow DAG Standardization with Python's AST"

Features

Rule Description
dag_id_convention Enforces snake_case naming for DAG IDs
task_id_convention Enforces snake_case naming for task IDs
group_id_convention Enforces snake_case naming for task group IDs
owner_validation Validates DAG owners against an approved list
tag_requirements Validates required tags are present
required_dag_params Ensures required default_args keys are set
retry_configuration Checks retry values are within configured limits
no_duplicate_task_ids Prevents duplicate task IDs within a DAG (task-group aware: group.task paths)
max_active_runs_validation Ensures max_active_runs is explicitly set
catchup_validation Checks catchup is explicitly set
schedule_validation Ensures schedule_interval / schedule is set

Installation

# From PyPI
pip install daglint

# From source
pip install -e .

Usage

# Lint a single file or directory
daglint check dags/my_dag.py
daglint check dags/

# Run specific rules only
daglint check dags/ --rules dag_id_convention,owner_validation

# Machine-readable output for CI and editors
daglint check dags/ --format json

# GitHub Actions annotations (issues appear inline on PRs)
daglint check dags/ --format github

# Fail on warnings too, not just errors
daglint check dags/ --strict

# Skip extra directories when scanning (adds to the built-in defaults)
daglint check . --exclude generated --exclude fixtures

# List all available rules
daglint rules

# Generate a default configuration file
daglint init

Example output

$ daglint check examples/invalid_dag.py
✗ examples/invalid_dag.py
  ERROR   [owner_validation]           Line  8: Invalid owner 'invalid-team'. Must be one of: data-team, analytics-team, airflow
  ERROR   [required_dag_params]        Line  8: Missing required parameters in default_args: retries, start_date
  ERROR   [dag_id_convention]          Line 19: DAG ID 'InvalidDAGID' does not match pattern '^[a-z][a-z0-9_]*$'
  WARNING [doc_md_validation]          Line 19: DAG is missing doc_md documentation
  WARNING [tag_requirements]           Line 19: Missing required tags: team, environment
  WARNING [max_active_runs_validation] Line 19: max_active_runs must be explicitly set to 1
  WARNING [catchup_validation]         Line 19: Catchup parameter not set. Consider setting it explicitly to False
  WARNING [schedule_validation]        Line 19: schedule must be explicitly set
  ERROR   [task_id_convention]         Line 30: Task ID 'InvalidTaskID' does not match pattern '^[a-z][a-z0-9_]*$'
  ERROR   [task_id_convention]         Line 36: Task ID 'InvalidTaskID' does not match pattern '^[a-z][a-z0-9_]*$'
  ERROR   [no_duplicate_task_ids]      Line 36: Duplicate task_id 'InvalidTaskID' (first seen at line 30)

--------------------------------------------------
Found 11 issue(s) (6 error(s), 5 warning(s)) in 1 file(s).

$ daglint check examples/valid_dag.py
✓ examples/valid_dag.py

--------------------------------------------------
All checks passed!

Output formats

--format selects how issues are reported (default text):

  • text — colorized human-readable output, as above.

  • json — a machine-readable envelope for CI systems, editors, and wrappers:

    {
      "issues": [
        {
          "rule_id": "owner_validation",
          "severity": "warning",
          "file": "dags/etl.py",
          "line": 12,
          "column": 0,
          "message": "DAG must have an owner"
        }
      ],
      "summary": {"files_checked": 8, "errors": 0, "warnings": 1, "infos": 0}
    }
    
  • github — GitHub Actions workflow commands (::error file=...,line=...::message), so issues show up as inline annotations on pull requests.

Exit codes

Code Meaning
0 No issues found, or only warning/info issues without --strict
1 At least one error-severity issue (with --strict: any issue at all)
2 Usage error (unknown rule, bad flag, missing path)

Warning-severity issues are advisory and do not fail the build by default; pass --strict to make them fail CI.

Configuration

Generate a starter config with daglint init, or create .daglint.yaml manually:

rules:
  dag_id_convention:
    enabled: true
    pattern: "^[a-z][a-z0-9_]*$"
    severity: error

  owner_validation:
    enabled: true
    valid_owners:
      - data-team
      - analytics-team
    severity: error

  tag_requirements:
    enabled: true
    required_tags:
      - environment
      - team
    severity: warning

  retry_configuration:
    enabled: true
    min_retries: 1
    max_retries: 5
    severity: warning

  max_active_runs_validation:
    enabled: true
    max_active_runs: 1
    severity: warning

severity must be one of error, warning, or info; any other value fails at startup with exit code 2.

Excluding directories

Directory scans always skip hidden directories (.venv/, .tox/, .git/, …) and these defaults: venv/, env/, build/, dist/, site-packages/. Add your own directory-name patterns on top — they are matched with shell-style globs against each directory name, anywhere in the tree:

exclude:
  - generated
  - "*_fixtures"

The repeatable --exclude CLI flag is additive with both the defaults and the config list. A file named explicitly on the command line is always linted, regardless of excludes.

Development

make install-dev   # Install with dev dependencies
make test          # Run tests
make test-cov      # Run tests with coverage report
make format        # Format code (black + isort)
make lint          # Run all linters
make check         # lint + test
make help          # List all targets

Project structure

src/daglint/
  cli.py              # Click CLI: check, rules, init commands
  linter.py           # DAGLinter: orchestrates rule execution
  config.py           # Config: loads .daglint.yaml or uses defaults
  models.py           # LintIssue dataclass
  rules/
    base.py           # BaseRule ABC
    __init__.py       # AVAILABLE_RULES registry
    naming.py         # dag_id_convention, task_id_convention, group_id_convention
    configuration.py  # retry_configuration, schedule_validation, catchup_validation
    validation.py     # no_duplicate_task_ids
    metadata/         # owner_validation, tag_requirements, required_dag_params,
                      # max_active_runs_validation, doc_md_validation
tests/
examples/
  valid_dag.py        # Compliant DAG example
  invalid_dag.py      # DAG with multiple violations

Further reading

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

daglint-1.1.0.tar.gz (42.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

daglint-1.1.0-py3-none-any.whl (25.9 kB view details)

Uploaded Python 3

File details

Details for the file daglint-1.1.0.tar.gz.

File metadata

  • Download URL: daglint-1.1.0.tar.gz
  • Upload date:
  • Size: 42.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for daglint-1.1.0.tar.gz
Algorithm Hash digest
SHA256 30a7fae89b62a4dc32bfb138d6a3660b121456c4fe57c542b0f3e00230bcad9a
MD5 660a0dbed707a11cb85c9019cd4e2ab0
BLAKE2b-256 cbd1fa8f573e0bb20ab32463476b4a86648024b60d94080792be215b47daf99e

See more details on using hashes here.

Provenance

The following attestation bundles were made for daglint-1.1.0.tar.gz:

Publisher: release.yml on why-pengo/daglint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file daglint-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: daglint-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 25.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for daglint-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4729967e954d264df4f4947323fe9a7ab5aae0b478b39861f3815fb867f48ff5
MD5 abed624ae1bf4fbc751313b388513060
BLAKE2b-256 38ee8555ee13e140f65b93fee1b83a03946ca7a860ee6e4d113615608270277b

See more details on using hashes here.

Provenance

The following attestation bundles were made for daglint-1.1.0-py3-none-any.whl:

Publisher: release.yml on why-pengo/daglint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page