Latest Results
[`airflow`] Flag `Variable.get()` calls outside of task execution context (`AIR003`) (#23584)
## Summary
Implements a new rule `AIR003` (`airflow-variable-get-outside-task`)
that flags `Variable.get()` calls outside of task execution context.
Per the [Airflow best practices
documentation](https://airflow.apache.org/docs/apache-airflow/stable/best-practices.html#airflow-variables),
calling `Variable.get()` at module level or inside operator constructor
arguments causes a database query **every time the DAG file is parsed**
by the scheduler. This can degrade parsing performance and even cause
DAG file timeouts. The recommended alternative is to pass variables via
Jinja templates (`{{ var.value.my_var }}`), which defer the lookup until
task execution.
### What the rule flags
```python
from airflow.sdk import Variable
from airflow.operators.bash import BashOperator
# Top-level Variable.get() — runs on every DAG parse
foo = Variable.get("foo")
# Variable.get() in operator constructor args — also runs on every DAG parse
BashOperator(
task_id="bad",
bash_command="echo $FOO",
env={"FOO": Variable.get("foo")},
)
# Variable.get() in a regular helper function — not a task execution context
def helper():
return Variable.get("foo")
```
### What it allows
`Variable.get()` is fine inside code that only runs during task
execution:
- `@task`-decorated functions (including `@task()`, `@task.branch`,
`@task.short_circuit`, etc.)
- `execute()` methods on `BaseOperator` subclasses
### Implementation details
- Resolves `Variable.get` via the semantic model, matching both
`airflow.models.Variable` and `airflow.sdk.Variable` import paths.
- Determines "task execution context" by walking the statement hierarchy
(`current_statements()`) looking for a parent `FunctionDef` that is
either:
- decorated with `@task` / `@task.<variant>` (resolved via
`airflow.decorators.task`), or
- named `execute` inside a class inheriting from `BaseOperator`.
- Handles both `@task` and `@task()` forms via `map_callable`, and
attribute-style decorators like `@task.branch` via `Expr::Attribute`
resolution.
## Test Plan
Added snapshot tests in `AIR005.py` covering:
- **Violations**: `Variable.get()` at module level, inside operator
constructor keyword arguments, inside f-string interpolation in operator
args, and in a regular helper function. Tested both
`airflow.models.Variable` and `airflow.sdk.Variable` import paths.
- **Non-violations**: `Variable.get()` inside `@task`, `@task()`,
`@task.branch` decorated functions, inside a `BaseOperator.execute()`
method, and Jinja template usage (no `Variable.get()` call).
related: https://github.com/apache/airflow/issues/43176 Active Branches
#222120%
#238940%
#237010%
© 2026 CodSpeed Technology