diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_context.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_context.py new file mode 100644 index 0000000000000..31511c446f2ac --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_context.py @@ -0,0 +1,124 @@ +import pendulum +from airflow.models import DAG +from airflow.operators.dummy import DummyOperator +from datetime import datetime +from airflow.plugins_manager import AirflowPlugin +from airflow.decorators import task, get_current_context +from airflow.models.baseoperator import BaseOperator +from airflow.decorators import dag, task +from airflow.providers.standard.operators.python import PythonOperator + + +def access_invalid_key_in_context(**context): + print("access invalid key", context["conf"]) + + +@task +def access_invalid_key_task_out_of_dag(**context): + print("access invalid key", context.get("conf")) + + + +@dag( + schedule=None, + start_date=pendulum.datetime(2021, 1, 1, tz="UTC"), + catchup=False, + tags=[""], +) +def invalid_dag(): + @task() + def access_invalid_key_task(**context): + print("access invalid key", context.get("conf")) + + task1 = PythonOperator( + task_id="task1", + python_callable=access_invalid_key_in_context, + ) + access_invalid_key_task() >> task1 + access_invalid_key_task_out_of_dag() + + +invalid_dag() + +@task +def print_config(**context): + # This should not throw an error as logical_date is part of airflow context. + logical_date = context["logical_date"] + + # Removed usage - should trigger violations + execution_date = context["execution_date"] + next_ds = context["next_ds"] + next_ds_nodash = context["next_ds_nodash"] + next_execution_date = context["next_execution_date"] + prev_ds = context["prev_ds"] + prev_ds_nodash = context["prev_ds_nodash"] + prev_execution_date = context["prev_execution_date"] + prev_execution_date_success = context["prev_execution_date_success"] + tomorrow_ds = context["tomorrow_ds"] + yesterday_ds = context["yesterday_ds"] + yesterday_ds_nodash = context["yesterday_ds_nodash"] + +with DAG( + dag_id="example_dag", + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + template_searchpath=["/templates"], +) as dag: + task1 = DummyOperator( + task_id="task1", + params={ + # Removed variables in template + "execution_date": "{{ execution_date }}", + "next_ds": "{{ next_ds }}", + "prev_ds": "{{ prev_ds }}" + }, + ) + +class CustomMacrosPlugin(AirflowPlugin): + name = "custom_macros" + macros = { + "execution_date_macro": lambda context: context["execution_date"], + "next_ds_macro": lambda context: context["next_ds"] + } + +@task +def print_config(): + context = get_current_context() + execution_date = context["execution_date"] + next_ds = context["next_ds"] + next_ds_nodash = context["next_ds_nodash"] + next_execution_date = context["next_execution_date"] + prev_ds = context["prev_ds"] + prev_ds_nodash = context["prev_ds_nodash"] + prev_execution_date = context["prev_execution_date"] + prev_execution_date_success = context["prev_execution_date_success"] + tomorrow_ds = context["tomorrow_ds"] + yesterday_ds = context["yesterday_ds"] + yesterday_ds_nodash = context["yesterday_ds_nodash"] + +class CustomOperator(BaseOperator): + def execute(self, context): + execution_date = context["execution_date"] + next_ds = context["next_ds"] + next_ds_nodash = context["next_ds_nodash"] + next_execution_date = context["next_execution_date"] + prev_ds = context["prev_ds"] + prev_ds_nodash = context["prev_ds_nodash"] + prev_execution_date = context["prev_execution_date"] + prev_execution_date_success = context["prev_execution_date_success"] + tomorrow_ds = context["tomorrow_ds"] + yesterday_ds = context["yesterday_ds"] + yesterday_ds_nodash = context["yesterday_ds_nodash"] + +@task +def access_invalid_argument_task_out_of_dag(execution_date, **context): + print("execution date", execution_date) + print("access invalid key", context.get("conf")) + +@task(task_id="print_the_context") +def print_context(ds=None, **kwargs): + """Print the Airflow context and ds variable from the context.""" + print(ds) + print(kwargs.get("tomorrow_ds")) + +run_this = print_context() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index a312e552636c9..1e664124c4cb5 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -171,7 +171,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::NonPEP646Unpack) { pyupgrade::rules::use_pep646_unpack(checker, subscript); } - + if checker.enabled(Rule::Airflow3Removal) { + airflow::rules::removed_in_3(checker, expr); + } pandas_vet::rules::subscript(checker, value, expr); } Expr::Tuple(ast::ExprTuple { diff --git a/crates/ruff_linter/src/rules/airflow/mod.rs b/crates/ruff_linter/src/rules/airflow/mod.rs index 2f2e7dded7bb0..d7020097863ac 100644 --- a/crates/ruff_linter/src/rules/airflow/mod.rs +++ b/crates/ruff_linter/src/rules/airflow/mod.rs @@ -18,6 +18,7 @@ mod tests { #[test_case(Rule::Airflow3Removal, Path::new("AIR302_names.py"))] #[test_case(Rule::Airflow3Removal, Path::new("AIR302_class_attribute.py"))] #[test_case(Rule::Airflow3Removal, Path::new("AIR302_airflow_plugin.py"))] + #[test_case(Rule::Airflow3Removal, Path::new("AIR302_context.py"))] #[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR303.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs index f5639de80926f..6a80826682fba 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs @@ -1,8 +1,10 @@ +use crate::checkers::ast::Checker; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::helpers::map_callable; use ruff_python_ast::{ name::QualifiedName, Arguments, Expr, ExprAttribute, ExprCall, ExprContext, ExprName, - StmtClassDef, + ExprStringLiteral, ExprSubscript, Stmt, StmtClassDef, StmtFunctionDef, }; use ruff_python_semantic::analyze::typing; use ruff_python_semantic::Modules; @@ -10,8 +12,6 @@ use ruff_python_semantic::ScopeKind; use ruff_text_size::Ranged; use ruff_text_size::TextRange; -use crate::checkers::ast::Checker; - /// ## What it does /// Checks for uses of deprecated Airflow functions and values. /// @@ -71,6 +71,104 @@ impl Violation for Airflow3Removal { } } +const REMOVED_CONTEXT_KEYS: [&str; 12] = [ + "conf", + "execution_date", + "next_ds", + "next_ds_nodash", + "next_execution_date", + "prev_ds", + "prev_ds_nodash", + "prev_execution_date", + "prev_execution_date_success", + "tomorrow_ds", + "yesterday_ds", + "yesterday_ds_nodash", +]; + +fn extract_name_from_slice(slice: &Expr) -> Option { + match slice { + Expr::StringLiteral(ExprStringLiteral { value, .. }) => Some(value.to_string()), + _ => None, + } +} + +/// Checks for the use of deprecated Airflow context variables. +/// +/// The function handles context keys accessed: +/// - Directly using `context["key"]` or context.get("key"). +/// - Using `.get()` with variables derived from `get_current_context()`. +/// - In custom operators, task decorators, and within templates or macros. +/// +/// ## Examples +/// +/// The following Python code would raise lint errors: +/// +/// ```python +/// @task +/// def access_invalid_key_task(**context): +/// print("access invalid key", context.get("execution_date")) # Deprecated +/// +/// @task +/// def print_config(**context): +/// execution_date = context["execution_date"] # Deprecated +/// prev_ds = context["prev_ds"] # Deprecated +/// +/// @task +/// def print_config_from_context(): +/// context = get_current_context() +/// execution_date = context["execution_date"] # Deprecated +/// ``` +/// In templates or custom plugins: +/// +/// ```python +/// task1 = DummyOperator( +/// task_id="example_task", +/// params={ +/// "execution_date": "{{ execution_date }}", # Deprecated +/// }, +/// ) +/// +/// class CustomMacrosPlugin(AirflowPlugin): +/// name = "custom_macros" +/// macros = { +/// "execution_date_macro": lambda context: context["execution_date"], # Deprecated +/// } +/// ``` +fn removed_context_variable(checker: &mut Checker, subscript: &ExprSubscript) { + let ExprSubscript { value, slice, .. } = subscript; + + let is_context_arg = if let Expr::Name(ExprName { id, .. }) = &**value { + id.as_str() == "context" || id.as_str().starts_with("**") + } else { + false + }; + + let is_current_context = + if let Some(qualname) = typing::resolve_assignment(value, checker.semantic()) { + matches!( + qualname.segments(), + ["airflow", "utils", "context", "get_current_context"] + ) + } else { + false + }; + + if is_context_arg || is_current_context { + if let Some(key) = extract_name_from_slice(slice) { + if REMOVED_CONTEXT_KEYS.contains(&key.as_str()) { + checker.diagnostics.push(Diagnostic::new( + Airflow3Removal { + deprecated: key, + replacement: Replacement::None, + }, + slice.range(), + )); + } + } + } +} + /// AIR302 pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) { if !checker.semantic().seen_module(Modules::AIRFLOW) { @@ -87,6 +185,7 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) { check_call_arguments(checker, &qualname, arguments); }; check_method(checker, call_expr); + check_context_get(checker, call_expr); } Expr::Attribute(attribute_expr @ ExprAttribute { attr, .. }) => { check_name(checker, expr, attr.range()); @@ -100,6 +199,9 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) { } } } + Expr::Subscript(subscript_expr) => { + removed_context_variable(checker, subscript_expr); + } _ => {} } } @@ -247,6 +349,47 @@ fn check_class_attribute(checker: &mut Checker, attribute_expr: &ExprAttribute) } } +/// Check whether a removed context key is access through context.get("key"). +/// +/// ```python +/// from airflow.decorators import task +/// +/// +/// @task +/// def access_invalid_key_task_out_of_dag(**context): +/// print("access invalid key", context.get("conf")) +/// ``` +fn check_context_get(checker: &mut Checker, call_expr: &ExprCall) { + detect_removed_context_keys_in_task_decorators(checker); + + let Expr::Attribute(ExprAttribute { value, attr, .. }) = &*call_expr.func else { + return; + }; + + if !value.as_name_expr().is_some_and(|name| { + matches!(name.id.as_str(), "context" | "kwargs") || name.id.as_str().starts_with("**") + }) { + return; + } + + if attr.as_str() != "get" { + return; + } + + for removed_key in REMOVED_CONTEXT_KEYS { + if let Some(argument) = call_expr.arguments.find_argument_value(removed_key, 0) { + checker.diagnostics.push(Diagnostic::new( + Airflow3Removal { + deprecated: removed_key.to_string(), + replacement: Replacement::None, + }, + argument.range(), + )); + return; + } + } +} + /// Check whether a removed Airflow class method is called. /// /// For example: @@ -849,3 +992,74 @@ fn is_airflow_builtin_or_provider(segments: &[&str], module: &str, symbol_suffix _ => false, } } + +/// Check for the usage of removed Airflow context keys and deprecated arguments in decorator-based functions. +/// +/// This function scans the current scope for functions decorated with the `@task` decorator +/// and examines their arguments and context keys. If any deprecated arguments or context keys +/// listed in `REMOVED_CONTEXT_KEYS` are found, it emits a diagnostic error. +/// +/// ### Example +/// ```python +/// from airflow.decorators import task +/// +/// @task +/// def access_invalid_argument_task_out_of_dag(execution_date, **context): # Deprecated argument +/// print("execution date", execution_date) +/// print("access invalid key", context.get("conf")) # Deprecated context key +/// +/// @task(task_id="print_the_context") +/// def print_context(ds=None, **kwargs): +/// """Print the Airflow context and ds variable from the context.""" +/// print(ds) +/// print(kwargs.get("tomorrow_ds")) # Deprecated context key +/// ``` +fn detect_removed_context_keys_in_task_decorators(checker: &mut Checker) -> bool { + let parents: Vec<_> = checker.semantic().current_statements().collect(); + + for stmt in parents { + let Stmt::FunctionDef(function_def) = stmt else { + continue; + }; + + if !has_task_decorator(checker, function_def) { + continue; + } + + let arguments = extract_task_function_arguments(function_def); + + for arg in arguments { + if REMOVED_CONTEXT_KEYS.contains(&arg.as_str()) { + checker.diagnostics.push(Diagnostic::new( + Airflow3Removal { + deprecated: arg, + replacement: Replacement::None, + }, + function_def.name.range(), + )); + } + } + } + + false +} + +fn extract_task_function_arguments(stmt: &StmtFunctionDef) -> Vec { + let mut arguments = Vec::new(); + + for param in &stmt.parameters.args { + arguments.push(param.parameter.name.to_string()); + } + arguments +} + +fn has_task_decorator(checker: &mut Checker, stmt: &StmtFunctionDef) -> bool { + stmt.decorator_list.iter().any(|decorator| { + checker + .semantic() + .resolve_qualified_name(map_callable(&decorator.expression)) + .is_some_and(|qualified_name| { + matches!(qualified_name.segments(), ["airflow", "decorators", "task"]) + }) + }) +} diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_context.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_context.py.snap new file mode 100644 index 0000000000000..48ab722b96070 --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_context.py.snap @@ -0,0 +1,452 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +snapshot_kind: text +--- +AIR302_context.py:13:41: AIR302 `conf` is removed in Airflow 3.0 + | +12 | def access_invalid_key_in_context(**context): +13 | print("access invalid key", context["conf"]) + | ^^^^^^ AIR302 + | + +AIR302_context.py:18:45: AIR302 `conf` is removed in Airflow 3.0 + | +16 | @task +17 | def access_invalid_key_task_out_of_dag(**context): +18 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 + | + +AIR302_context.py:31:49: AIR302 `conf` is removed in Airflow 3.0 + | +29 | @task() +30 | def access_invalid_key_task(**context): +31 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +32 | +33 | task1 = PythonOperator( + | + +AIR302_context.py:49:30: AIR302 `execution_date` is removed in Airflow 3.0 + | +48 | # Removed usage - should trigger violations +49 | execution_date = context["execution_date"] + | ^^^^^^^^^^^^^^^^ AIR302 +50 | next_ds = context["next_ds"] +51 | next_ds_nodash = context["next_ds_nodash"] + | + +AIR302_context.py:50:23: AIR302 `next_ds` is removed in Airflow 3.0 + | +48 | # Removed usage - should trigger violations +49 | execution_date = context["execution_date"] +50 | next_ds = context["next_ds"] + | ^^^^^^^^^ AIR302 +51 | next_ds_nodash = context["next_ds_nodash"] +52 | next_execution_date = context["next_execution_date"] + | + +AIR302_context.py:51:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0 + | +49 | execution_date = context["execution_date"] +50 | next_ds = context["next_ds"] +51 | next_ds_nodash = context["next_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +52 | next_execution_date = context["next_execution_date"] +53 | prev_ds = context["prev_ds"] + | + +AIR302_context.py:52:35: AIR302 `next_execution_date` is removed in Airflow 3.0 + | +50 | next_ds = context["next_ds"] +51 | next_ds_nodash = context["next_ds_nodash"] +52 | next_execution_date = context["next_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +53 | prev_ds = context["prev_ds"] +54 | prev_ds_nodash = context["prev_ds_nodash"] + | + +AIR302_context.py:53:23: AIR302 `prev_ds` is removed in Airflow 3.0 + | +51 | next_ds_nodash = context["next_ds_nodash"] +52 | next_execution_date = context["next_execution_date"] +53 | prev_ds = context["prev_ds"] + | ^^^^^^^^^ AIR302 +54 | prev_ds_nodash = context["prev_ds_nodash"] +55 | prev_execution_date = context["prev_execution_date"] + | + +AIR302_context.py:54:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0 + | +52 | next_execution_date = context["next_execution_date"] +53 | prev_ds = context["prev_ds"] +54 | prev_ds_nodash = context["prev_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +55 | prev_execution_date = context["prev_execution_date"] +56 | prev_execution_date_success = context["prev_execution_date_success"] + | + +AIR302_context.py:55:35: AIR302 `prev_execution_date` is removed in Airflow 3.0 + | +53 | prev_ds = context["prev_ds"] +54 | prev_ds_nodash = context["prev_ds_nodash"] +55 | prev_execution_date = context["prev_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +56 | prev_execution_date_success = context["prev_execution_date_success"] +57 | tomorrow_ds = context["tomorrow_ds"] + | + +AIR302_context.py:56:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0 + | +54 | prev_ds_nodash = context["prev_ds_nodash"] +55 | prev_execution_date = context["prev_execution_date"] +56 | prev_execution_date_success = context["prev_execution_date_success"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +57 | tomorrow_ds = context["tomorrow_ds"] +58 | yesterday_ds = context["yesterday_ds"] + | + +AIR302_context.py:57:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +55 | prev_execution_date = context["prev_execution_date"] +56 | prev_execution_date_success = context["prev_execution_date_success"] +57 | tomorrow_ds = context["tomorrow_ds"] + | ^^^^^^^^^^^^^ AIR302 +58 | yesterday_ds = context["yesterday_ds"] +59 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:58:28: AIR302 `yesterday_ds` is removed in Airflow 3.0 + | +56 | prev_execution_date_success = context["prev_execution_date_success"] +57 | tomorrow_ds = context["tomorrow_ds"] +58 | yesterday_ds = context["yesterday_ds"] + | ^^^^^^^^^^^^^^ AIR302 +59 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:59:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0 + | +57 | tomorrow_ds = context["tomorrow_ds"] +58 | yesterday_ds = context["yesterday_ds"] +59 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +60 | +61 | with DAG( + | + +AIR302_context.py:63:5: AIR302 [*] `schedule_interval` is removed in Airflow 3.0 + | +61 | with DAG( +62 | dag_id="example_dag", +63 | schedule_interval="@daily", + | ^^^^^^^^^^^^^^^^^ AIR302 +64 | start_date=datetime(2023, 1, 1), +65 | template_searchpath=["/templates"], + | + = help: Use `schedule` instead + +ℹ Safe fix +60 60 | +61 61 | with DAG( +62 62 | dag_id="example_dag", +63 |- schedule_interval="@daily", + 63 |+ schedule="@daily", +64 64 | start_date=datetime(2023, 1, 1), +65 65 | template_searchpath=["/templates"], +66 66 | ) as dag: + +AIR302_context.py:67:13: AIR302 `airflow.operators.dummy.DummyOperator` is removed in Airflow 3.0 + | +65 | template_searchpath=["/templates"], +66 | ) as dag: +67 | task1 = DummyOperator( + | ^^^^^^^^^^^^^ AIR302 +68 | task_id="task1", +69 | params={ + | + = help: Use `airflow.operators.empty.EmptyOperator` instead + +AIR302_context.py:80:57: AIR302 `execution_date` is removed in Airflow 3.0 + | +78 | name = "custom_macros" +79 | macros = { +80 | "execution_date_macro": lambda context: context["execution_date"], + | ^^^^^^^^^^^^^^^^ AIR302 +81 | "next_ds_macro": lambda context: context["next_ds"] +82 | } + | + +AIR302_context.py:81:50: AIR302 `next_ds` is removed in Airflow 3.0 + | +79 | macros = { +80 | "execution_date_macro": lambda context: context["execution_date"], +81 | "next_ds_macro": lambda context: context["next_ds"] + | ^^^^^^^^^ AIR302 +82 | } + | + +AIR302_context.py:87:30: AIR302 `execution_date` is removed in Airflow 3.0 + | +85 | def print_config(): +86 | context = get_current_context() +87 | execution_date = context["execution_date"] + | ^^^^^^^^^^^^^^^^ AIR302 +88 | next_ds = context["next_ds"] +89 | next_ds_nodash = context["next_ds_nodash"] + | + +AIR302_context.py:88:23: AIR302 `next_ds` is removed in Airflow 3.0 + | +86 | context = get_current_context() +87 | execution_date = context["execution_date"] +88 | next_ds = context["next_ds"] + | ^^^^^^^^^ AIR302 +89 | next_ds_nodash = context["next_ds_nodash"] +90 | next_execution_date = context["next_execution_date"] + | + +AIR302_context.py:89:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0 + | +87 | execution_date = context["execution_date"] +88 | next_ds = context["next_ds"] +89 | next_ds_nodash = context["next_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +90 | next_execution_date = context["next_execution_date"] +91 | prev_ds = context["prev_ds"] + | + +AIR302_context.py:90:35: AIR302 `next_execution_date` is removed in Airflow 3.0 + | +88 | next_ds = context["next_ds"] +89 | next_ds_nodash = context["next_ds_nodash"] +90 | next_execution_date = context["next_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +91 | prev_ds = context["prev_ds"] +92 | prev_ds_nodash = context["prev_ds_nodash"] + | + +AIR302_context.py:91:23: AIR302 `prev_ds` is removed in Airflow 3.0 + | +89 | next_ds_nodash = context["next_ds_nodash"] +90 | next_execution_date = context["next_execution_date"] +91 | prev_ds = context["prev_ds"] + | ^^^^^^^^^ AIR302 +92 | prev_ds_nodash = context["prev_ds_nodash"] +93 | prev_execution_date = context["prev_execution_date"] + | + +AIR302_context.py:92:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0 + | +90 | next_execution_date = context["next_execution_date"] +91 | prev_ds = context["prev_ds"] +92 | prev_ds_nodash = context["prev_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +93 | prev_execution_date = context["prev_execution_date"] +94 | prev_execution_date_success = context["prev_execution_date_success"] + | + +AIR302_context.py:93:35: AIR302 `prev_execution_date` is removed in Airflow 3.0 + | +91 | prev_ds = context["prev_ds"] +92 | prev_ds_nodash = context["prev_ds_nodash"] +93 | prev_execution_date = context["prev_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +94 | prev_execution_date_success = context["prev_execution_date_success"] +95 | tomorrow_ds = context["tomorrow_ds"] + | + +AIR302_context.py:94:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0 + | +92 | prev_ds_nodash = context["prev_ds_nodash"] +93 | prev_execution_date = context["prev_execution_date"] +94 | prev_execution_date_success = context["prev_execution_date_success"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +95 | tomorrow_ds = context["tomorrow_ds"] +96 | yesterday_ds = context["yesterday_ds"] + | + +AIR302_context.py:95:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +93 | prev_execution_date = context["prev_execution_date"] +94 | prev_execution_date_success = context["prev_execution_date_success"] +95 | tomorrow_ds = context["tomorrow_ds"] + | ^^^^^^^^^^^^^ AIR302 +96 | yesterday_ds = context["yesterday_ds"] +97 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:96:28: AIR302 `yesterday_ds` is removed in Airflow 3.0 + | +94 | prev_execution_date_success = context["prev_execution_date_success"] +95 | tomorrow_ds = context["tomorrow_ds"] +96 | yesterday_ds = context["yesterday_ds"] + | ^^^^^^^^^^^^^^ AIR302 +97 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:97:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0 + | +95 | tomorrow_ds = context["tomorrow_ds"] +96 | yesterday_ds = context["yesterday_ds"] +97 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +98 | +99 | class CustomOperator(BaseOperator): + | + +AIR302_context.py:101:34: AIR302 `execution_date` is removed in Airflow 3.0 + | + 99 | class CustomOperator(BaseOperator): +100 | def execute(self, context): +101 | execution_date = context["execution_date"] + | ^^^^^^^^^^^^^^^^ AIR302 +102 | next_ds = context["next_ds"] +103 | next_ds_nodash = context["next_ds_nodash"] + | + +AIR302_context.py:102:27: AIR302 `next_ds` is removed in Airflow 3.0 + | +100 | def execute(self, context): +101 | execution_date = context["execution_date"] +102 | next_ds = context["next_ds"] + | ^^^^^^^^^ AIR302 +103 | next_ds_nodash = context["next_ds_nodash"] +104 | next_execution_date = context["next_execution_date"] + | + +AIR302_context.py:103:34: AIR302 `next_ds_nodash` is removed in Airflow 3.0 + | +101 | execution_date = context["execution_date"] +102 | next_ds = context["next_ds"] +103 | next_ds_nodash = context["next_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +104 | next_execution_date = context["next_execution_date"] +105 | prev_ds = context["prev_ds"] + | + +AIR302_context.py:104:39: AIR302 `next_execution_date` is removed in Airflow 3.0 + | +102 | next_ds = context["next_ds"] +103 | next_ds_nodash = context["next_ds_nodash"] +104 | next_execution_date = context["next_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +105 | prev_ds = context["prev_ds"] +106 | prev_ds_nodash = context["prev_ds_nodash"] + | + +AIR302_context.py:105:27: AIR302 `prev_ds` is removed in Airflow 3.0 + | +103 | next_ds_nodash = context["next_ds_nodash"] +104 | next_execution_date = context["next_execution_date"] +105 | prev_ds = context["prev_ds"] + | ^^^^^^^^^ AIR302 +106 | prev_ds_nodash = context["prev_ds_nodash"] +107 | prev_execution_date = context["prev_execution_date"] + | + +AIR302_context.py:106:34: AIR302 `prev_ds_nodash` is removed in Airflow 3.0 + | +104 | next_execution_date = context["next_execution_date"] +105 | prev_ds = context["prev_ds"] +106 | prev_ds_nodash = context["prev_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +107 | prev_execution_date = context["prev_execution_date"] +108 | prev_execution_date_success = context["prev_execution_date_success"] + | + +AIR302_context.py:107:39: AIR302 `prev_execution_date` is removed in Airflow 3.0 + | +105 | prev_ds = context["prev_ds"] +106 | prev_ds_nodash = context["prev_ds_nodash"] +107 | prev_execution_date = context["prev_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +108 | prev_execution_date_success = context["prev_execution_date_success"] +109 | tomorrow_ds = context["tomorrow_ds"] + | + +AIR302_context.py:108:47: AIR302 `prev_execution_date_success` is removed in Airflow 3.0 + | +106 | prev_ds_nodash = context["prev_ds_nodash"] +107 | prev_execution_date = context["prev_execution_date"] +108 | prev_execution_date_success = context["prev_execution_date_success"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +109 | tomorrow_ds = context["tomorrow_ds"] +110 | yesterday_ds = context["yesterday_ds"] + | + +AIR302_context.py:109:31: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +107 | prev_execution_date = context["prev_execution_date"] +108 | prev_execution_date_success = context["prev_execution_date_success"] +109 | tomorrow_ds = context["tomorrow_ds"] + | ^^^^^^^^^^^^^ AIR302 +110 | yesterday_ds = context["yesterday_ds"] +111 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:110:32: AIR302 `yesterday_ds` is removed in Airflow 3.0 + | +108 | prev_execution_date_success = context["prev_execution_date_success"] +109 | tomorrow_ds = context["tomorrow_ds"] +110 | yesterday_ds = context["yesterday_ds"] + | ^^^^^^^^^^^^^^ AIR302 +111 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:111:39: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0 + | +109 | tomorrow_ds = context["tomorrow_ds"] +110 | yesterday_ds = context["yesterday_ds"] +111 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +112 | +113 | @task + | + +AIR302_context.py:114:5: AIR302 `execution_date` is removed in Airflow 3.0 + | +113 | @task +114 | def access_invalid_argument_task_out_of_dag(execution_date, **context): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +115 | print("execution date", execution_date) +116 | print("access invalid key", context.get("conf")) + | + +AIR302_context.py:114:5: AIR302 `execution_date` is removed in Airflow 3.0 + | +113 | @task +114 | def access_invalid_argument_task_out_of_dag(execution_date, **context): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +115 | print("execution date", execution_date) +116 | print("access invalid key", context.get("conf")) + | + +AIR302_context.py:114:5: AIR302 `execution_date` is removed in Airflow 3.0 + | +113 | @task +114 | def access_invalid_argument_task_out_of_dag(execution_date, **context): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +115 | print("execution date", execution_date) +116 | print("access invalid key", context.get("conf")) + | + +AIR302_context.py:116:45: AIR302 `conf` is removed in Airflow 3.0 + | +114 | def access_invalid_argument_task_out_of_dag(execution_date, **context): +115 | print("execution date", execution_date) +116 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +117 | +118 | @task(task_id="print_the_context") + | + +AIR302_context.py:122:22: AIR302 `conf` is removed in Airflow 3.0 + | +120 | """Print the Airflow context and ds variable from the context.""" +121 | print(ds) +122 | print(kwargs.get("tomorrow_ds")) + | ^^^^^^^^^^^^^ AIR302 +123 | +124 | run_this = print_context() + |