| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
ruff-ecosystem resultsLinter (stable)✅ ecosystem check detected no linter changes. Linter (preview)ℹ️ ecosystem check detected linter changes. (+3 -0 violations, +0 -0 fixes in 1 projects; 55 projects unchanged) apache/airflow (+3 -0 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL
+ providers/amazon/tests/system/amazon/aws/example_dms_serverless.py:329:32: AIR201 Use the `.output` attribute on the task object for "create_replication_config" instead of `xcom_pull` in a template string + providers/google/tests/system/google/cloud/vertex_ai/example_vertex_ai_feature_store.py:174:32: AIR201 Use the `.output` attribute on the task object for "sync_task" instead of `xcom_pull` in a template string + providers/google/tests/system/google/cloud/vertex_ai/example_vertex_ai_feature_store.py:185:32: AIR201 Use the `.output` attribute on the task object for "sync_task" instead of `xcom_pull` in a template stringChanges by rule (1 rules affected)
|
Sorry, something went wrong.
would be great to also have @Lee-W to have a look when he is available. |
Sorry, something went wrong.
|
Hey, thanks for creating this. I'm actaully good with adding this, but it would be better if we had a stronger consensus across the community. I'll start a discussion in the dev list today. |
Sorry, something went wrong.
|
Thanks @Lee-W and @sjyangkevin for taking a look! I think I'll convert this back to a draft for now until there's consensus on the Airflow side. |
Sorry, something went wrong.
|
@ntBre @amyreese @Lee-W @sjyangkevin The Airflow community approved this rule, so I'm marking it "Ready for review". Refs: |
Sorry, something went wrong.
There was a problem hiding this comment.
a few nits. but overall looks good!
Sorry, something went wrong.
| # Mixed content (not just xcom_pull) | ||
| task_10 = BashOperator( | ||
| task_id="task_10", | ||
| bash_command="echo {{ ti.xcom_pull(task_ids='task_1') }}", |
There was a problem hiding this comment.
I guess we're worrying about false possitive?
Sorry, something went wrong.
There was a problem hiding this comment.
Shouldn't we? What do you suggest?
Sorry, something went wrong.
There was a problem hiding this comment.
I meant why shouldn't we replace if it's mixed content
Sorry, something went wrong.
There was a problem hiding this comment.
In other cases, the template (string) is replaced by a different type (xcom). In a mixed context we need to keep a string, which relies on the correct serializability of xcoms. IMO this is more trouble than it's worth.
However, let's consider your proposal for a second - what should be the replacement in this example?
bash_command="echo {{ ti.xcom_pull(task_ids='task_1') }}", # original
bash_command="echo {{ task_1.output }}",
bash_command="echo " + task_1.output,
bash_command=f"echo {task_1.output}",
...
Sorry, something went wrong.
There was a problem hiding this comment.
I think it should be bash_command="echo {{ task_1.output }}",
Sorry, something went wrong.
There was a problem hiding this comment.
Thanks! Overall looks good to me but one more small feedback. @Lee-W when you have time, would you mind also have a look see if the following valid?
Should we also handle an edge case that when the task is defined in a TaskGroup, let's say taskgroup_1. The callable task_id will be group_id.task_id, for example
task_4 = BashOperator(
task_id="task_4",
bash_command="{{ ti.xcom_pull('taskgroup_1.task_1') }}",
)
https://www.astronomer.io/docs/learn/task-groups#task_id-in-task-groups
Sorry, something went wrong.
|
@Lee-W, how many rules do you plan on adding? I'm asking because adding and maintaining all these airflow rules is a considerable effort on our end. Don't get me wrong, the contributions are great, but it's unfortunately not at zero cost for us. If you plan on adding many rules, I think it's best if we discuss that first and how we can scale this process. |
Sorry, something went wrong.
By all means - let's discuss!
|
Sorry, something went wrong.
|
Yeah. I know we've heard in the past that plugin support is not really priority, but possibly we could do something about it now? We would actually love if Airflow rules were somewhere outside of main ruff core and if we could just configure it or even ask our users to install plugins separately. I am not sure if plugins is something on the roadmap now @MichaReiser ? |
Sorry, something went wrong.
|
@MichaReiser, we don't have a clear understanding of whether we should add more rules at this time. However, we definitely need to discuss our next steps regarding this matter! Currently, if anyone has ideas about best practices for Airflow, they will need to initiate a discussion and build consensus within the Airflow community. This process takes time and doesn't happen often, so I don’t expect many changes. |
Sorry, something went wrong.
There was a problem hiding this comment.
LGTM
Sorry, something went wrong.
Thanks, this sounds good to me. Plugin support is on our mind. But we first want to push some highly demanded and larger user-facing features (warning severity, human-readable names, rule recategorization, unified format/check command). I suspect that plugins will be high up on our feature list once these are out. |
Sorry, something went wrong.
There was a problem hiding this comment.
Thank you. This overall looks good. The Jinja parsing makes me a bit uneasy. I think there are a few more cases that need handling and there's potential for more code reuse.
Sorry, something went wrong.
| } | ||
|
|
||
| // Check keyword arguments for xcom_pull template strings. | ||
| for keyword in &*call.arguments.keywords { |
There was a problem hiding this comment.
Does this indeed apply to all keyword arguments or can we restrict the rule to a few known keyword names? What if the arguments are passed as positional arguments (or are they required to be keyword arguments)?
Sorry, something went wrong.
There was a problem hiding this comment.
In Airflow, any operator argument can be a template field — it's determined by the operator's template_fields class attribute and varies per operator. Since the {{ ti.xcom_pull(...) }} pattern is specific enough to avoid false positives, I now check all arguments (both positional and keyword) with a code comment explaining the rationale. Positional template strings are rare in operator calls but technically possible.
Sorry, something went wrong.
There was a problem hiding this comment.
Are there some known fields that we can always skip? E.g. task_id, dag and task_group?
Sorry, something went wrong.
There was a problem hiding this comment.
Can you please elaborate or give an example of the use case you have in mind?
This rule was made to detect a common (and outdated) pattern where specifically the return value of a task is retrieved. The reason for the proliferation of the pattern is because it was the recommended (only?) way of doing things for a long time, and since this is what appeared in the docs - that's what users ended up doing. While it is possible to use positional args to call the function i.e.
ti.xcom_pull("task") # Plausible
ti.xcom_pull("task", None) # Uncommon
ti.xcom_pull("task", None, "return_value") # UncommonI think the above are so uncommon that false-negatives are not a concern in practice. Moreover, xcom_pull has 5 "positional-able" args (see below) - and we don't want to touch it if it the user provides anything except just task_ids and (optionally) key.
To conclude - I think that dealing with arbitrary templates is outside the scope of this rule.
CC: @Lee-W @sjyangkevin @potiuk
The current signature of xcom_pull:
def xcom_pull(
self,
task_ids: str | Iterable[str] | None = None,
dag_id: str | None = None,
key: str = XCOM_RETURN_KEY, # "return_value"
include_prior_dates: bool = False,
session: Session = NEW_SESSION,
*,
map_indexes: int | Iterable[int] | None = None,
default: Any = None,
run_id: str | None = None,
) -> Any:
Sorry, something went wrong.
| } | ||
|
|
||
| // Check keyword arguments for xcom_pull template strings. | ||
| for keyword in &*call.arguments.keywords { |
There was a problem hiding this comment.
Are there some known fields that we can always skip? E.g. task_id, dag and task_group?
Sorry, something went wrong.
- Rewrite parse_xcom_pull_template using ruff_python_trivia::Cursor instead of strip_prefix/strip_suffix chains - Extract reusable helpers: eat_whitespace, parse_identifier, parse_quoted_string - Add whitespace tolerance between all token pairs (e.g. ti . xcom_pull) - Bail on escaped quotes in string content - Broaden argument checking to cover both positional and keyword args with explanatory comment about template_fields - Add Fix safety doc section - Add unit tests for new whitespace/escape/unknown-keyword handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_whitespace - Use `iter_source_order` for argument iteration (MichaReiser) - Use `char::is_whitespace` instead of `is_ascii_whitespace` to match Jinja's Unicode whitespace semantics (MichaReiser) - Support reordered keyword arguments: `key='return_value', task_ids='...'` is now recognized in addition to the existing order (MichaReiser + Lee-W) - Extract `parse_task_id_value` helper for list/tuple wrapping logic - Add unit tests for reordered keyword patterns - Add fixture trigger case for reordered keywords (task_25) - Show modern `.output` replacement pattern as comment on mixed-content test - Regenerate snapshot in current insta format Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
This looks good to me, but Codex had two findings. I find it difficult to assess whether they're correct because I'm unfamiliar with airflow. Could someone take a look:
|
Sorry, something went wrong.
Codex is technically correct here, but I don't think anyone does this. If they do, it's likely a mistake, and while it may be flagged for the wrong reason, it would indicate to the user that something's up. Also, not sure a template like that is even a valid task_id (there are some rules it needs to follow which I cannot find at the moment).
This one's true, but the original syntax is already wrong for the exact same reason. We wouldn't be replacing working code with broken one. |
Sorry, something went wrong.
|
This is great work. Thank you |
Sorry, something went wrong.
Thank you for bringing this over the finish line! |
Sorry, something went wrong.
##### [\`v0.15.12\`](https://github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#01512) Released on 2026-04-24. ##### Preview features - Implement `#ruff:file-ignore` file-level suppressions ([#23599](astral-sh/ruff#23599)) - Implement `#ruff:ignore` logical-line suppressions ([#23404](astral-sh/ruff#23404)) - Revert preview changes to displayed diagnostic severity in LSP ([#24789](astral-sh/ruff#24789)) - \[`airflow`] Implement `task-branch-as-short-circuit` (`AIR004`) ([#23579](astral-sh/ruff#23579)) - \[`flake8-bugbear`] Fix `break`/`continue` handling in `loop-iterator-mutation` (`B909`) ([#24440](astral-sh/ruff#24440)) - \[`pylint`] Fix `PLC2701` for type parameter scopes ([#24576](astral-sh/ruff#24576)) ##### Rule changes - \[`pandas-vet`] Suggest `.array` as well in `PD011` ([#24805](astral-sh/ruff#24805)) ##### CLI - Respect default Unix permissions for cache files ([#24794](astral-sh/ruff#24794)) ##### Documentation - \[`pylint`] Fix `PLR0124` description not to claim self-comparison always returns the same value ([#24749](astral-sh/ruff#24749)) - \[`pyupgrade`] Expand docs on reusable `TypeVar`s and scoping (`UP046`) ([#24153](astral-sh/ruff#24153)) - Improve rules table accessibility ([#24711](astral-sh/ruff#24711)) ##### Contributors - [@dylwil3](https://github.com/dylwil3) - [@AlexWaygood](https://github.com/AlexWaygood) - [@woodruffw](https://github.com/woodruffw) - [@avasis-ai](https://github.com/avasis-ai) - [@Dev-iL](https://github.com/Dev-iL) - [@denyszhak](https://github.com/denyszhak) - [@ShipItAndPray](https://github.com/ShipItAndPray) - [@anishgirianish](https://github.com/anishgirianish) - [@augustelalande](https://github.com/augustelalande) - [@amyreese](https://github.com/amyreese) - [@majiayu000](https://github.com/majiayu000) ##### [\`v0.15.11\`](https://github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#01511) Released on 2026-04-16. ##### Preview features - \[`ruff`] Ignore `RUF029` when function is decorated with `asynccontextmanager` ([#24642](astral-sh/ruff#24642)) - \[`airflow`] Implement `airflow-xcom-pull-in-template-string` (`AIR201`) ([#23583](astral-sh/ruff#23583)) - \[`flake8-bandit`] Fix `S103` false positives and negatives in mask analysis ([#24424](astral-sh/ruff#24424)) ##### Bug fixes - \[`flake8-async`] Omit overridden methods for `ASYNC109` ([#24648](astral-sh/ruff#24648)) ##### Documentation - \[`flake8-async`] Add override mention to `ASYNC109` docs ([#24666](astral-sh/ruff#24666)) - Update Neovim config examples to use `vim.lsp.config` ([#24577](astral-sh/ruff#24577)) ##### Contributors - [@augustelalande](https://github.com/augustelalande) - [@anishgirianish](https://github.com/anishgirianish) - [@benberryallwood](https://github.com/benberryallwood) - [@charliermarsh](https://github.com/charliermarsh) - [@Dev-iL](https://github.com/Dev-iL) Renovate-Branch: renovate/2024.6-ruff-0.15.x Change-Id: I2c5de44f14ce3133db71161eae18c7b43f7ba09b Priv-Id: 9c1a7f10043a2db2338a90de9a62a4d7989df14d
| Back | FazBrowse Home | New Git URL |
Summary
Implements rule AIR004 (airflow-xcom-pull-in-template-string) that detects Airflow operator/sensor keyword arguments using a Jinja template string containing a single xcom_pull call (e.g., "{{ ti.xcom_pull(task_ids='some_task') }}") and suggests replacing it with the .output attribute on the task object (e.g., some_task.output).
Using .output instead of xcom_pull template strings:
What the rule flags
Suggested fix
Template patterns detected
What it allows (no false positives)
Unsafe fix
When the referenced task_id matches a variable in scope (either an operator assignment or a @task-decorated function), an unsafe fix is provided that replaces the template string with <variable>.output. When no matching variable is found, the diagnostic is still reported but without an auto-fix.
Test Plan
related: apache/airflow#43176