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 00000000000000..2d2764f5ddf2c9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR302_context.py @@ -0,0 +1,127 @@ +from datetime import datetime + +import pendulum + +from airflow.decorators import dag, task +from airflow.models import DAG +from airflow.models.baseoperator import BaseOperator +from airflow.operators.dummy import DummyOperator +from airflow.plugins_manager import AirflowPlugin +from airflow.providers.standard.operators.python import PythonOperator +from airflow.utils.context import get_current_context + + +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, tomorrow_ds, logical_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")) + c = get_current_context() + c.get("execution_date") + +class CustomOperatorNew(BaseOperator): + def execute(self, context): + execution_date = context.get("execution_date") + next_ds = context.get("next_ds") diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index ce5425a2ab95a2..74d877f91ba57f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -175,7 +175,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/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 414010e6317062..dcb3ceea625eea 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -376,6 +376,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::PytestParameterWithDefaultArgument) { flake8_pytest_style::rules::parameter_with_default_argument(checker, function_def); } + if checker.enabled(Rule::Airflow3Removal) { + airflow::rules::removed_in_3_function_def(checker, function_def); + } if checker.enabled(Rule::NonPEP695GenericFunction) { pyupgrade::rules::non_pep695_generic_function(checker, function_def); } diff --git a/crates/ruff_linter/src/rules/airflow/mod.rs b/crates/ruff_linter/src/rules/airflow/mod.rs index 2f2e7dded7bb08..d7020097863acf 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 6c9238b6ad4ff6..41b0e932ff5fec 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,17 +1,19 @@ +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::AnyParameterRef; 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; use ruff_python_semantic::ScopeKind; +use ruff_python_semantic::SemanticModel; 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,7 +73,111 @@ impl Violation for Airflow3Removal { } } -/// AIR302 +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, + } +} + +/// Check if a subscript expression accesses a removed Airflow context variable. +/// If a removed key is found, push a corresponding diagnostic. +fn check_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(), + )); + } + } + } +} + +// Function to handle `var.get(...)` outside of @task-decorated functions +fn check_removed_context_keys_get_anywhere(checker: &mut Checker, call_expr: &ExprCall) { + let Expr::Attribute(ExprAttribute { attr, value, .. }) = &*call_expr.func else { + return; + }; + + if attr.as_str() != "get" { + return; + } + + // Check if the value is a context argument + 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 { + for removed_key in REMOVED_CONTEXT_KEYS { + if let Some(argument) = call_expr.arguments.find_positional(0) { + if let Expr::StringLiteral(ExprStringLiteral { value, .. }) = argument { + if value == removed_key { + checker.diagnostics.push(Diagnostic::new( + Airflow3Removal { + deprecated: removed_key.to_string(), + replacement: Replacement::None, + }, + argument.range(), + )); + } + } + } + } + } +} + +// Modify the `removed_in_3` function to call the new check for `var.get(...)` pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) { if !checker.semantic().seen_module(Modules::AIRFLOW) { return; @@ -87,6 +193,8 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) { check_call_arguments(checker, &qualname, arguments); }; check_method(checker, call_expr); + check_removed_context_keys_usage(checker, call_expr); + check_removed_context_keys_get_anywhere(checker, call_expr); } Expr::Attribute(attribute_expr @ ExprAttribute { attr, .. }) => { check_name(checker, expr, attr.range()); @@ -100,6 +208,9 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) { } } } + Expr::Subscript(subscript_expr) => { + check_context_variable(checker, subscript_expr); + } _ => {} } } @@ -252,6 +363,133 @@ fn check_class_attribute(checker: &mut Checker, attribute_expr: &ExprAttribute) } } +/// Finds the parameter definition for a given name expression in a function. +fn find_parameter<'a>( + semantic: &'a SemanticModel, + name: &'a ExprName, +) -> Option> { + let binding_id = semantic.only_binding(name)?; + let binding = semantic.binding(binding_id); + let StmtFunctionDef { parameters, .. } = binding.statement(semantic)?.as_function_def_stmt()?; + parameters + .iter() + .find(|parameter| parameter.name().range() == binding.range()) +} + +/// Checks whether an Airflow 3.0–removed context key is used in a function decorated with `@task`. +/// +/// Specifically, it flags two scenarios for task decorated function: +/// 1. A removed context variable passed in as a function parameter name (e.g., `execution_date`). +/// 2. A removed context key accessed via `context.get("...")`. +/// +/// # Examples +/// +/// **Removed key used in `context.get(...)`:** +/// ```python +/// from airflow.decorators import task +/// +/// @task +/// def my_task(**context): +/// # 'conf' is removed in Airflow 3.0 +/// print(context.get("conf")) +/// ``` +/// +/// **Accessing multiple keys:** +/// ```python +/// from airflow.decorators import task +/// +/// @task +/// def more_keys(**context): +/// # 'prev_ds' is also removed in Airflow 3.0 +/// print(context.get("prev_ds")) +/// ``` +fn check_removed_context_keys_usage(checker: &mut Checker, call_expr: &ExprCall) { + if !is_taskflow(checker) { + return; + } + + let Expr::Attribute(ExprAttribute { value, attr, .. }) = &*call_expr.func else { + return; + }; + + let is_named_context = if let Expr::Name(name) = &**value { + if let Some(parameter) = find_parameter(checker.semantic(), name) { + matches!(parameter.name().as_str(), "context" | "kwargs") + || parameter.name().as_str().starts_with("**") + } else { + false + } + } else { + false + }; + + let is_assigned_from_get_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_named_context || is_assigned_from_get_current_context) { + return; + } + + if attr.as_str() != "get" { + return; + } + + for removed_key in REMOVED_CONTEXT_KEYS { + if let Some(argument) = call_expr.arguments.find_positional(0) { + if let Expr::StringLiteral(ExprStringLiteral { value, .. }) = argument { + if value == removed_key { + checker.diagnostics.push(Diagnostic::new( + Airflow3Removal { + deprecated: removed_key.to_string(), + replacement: Replacement::None, + }, + argument.range(), + )); + } + } + } + } +} + +/// Check whether the function is decorated by @task +/// +/// +/// Examples for the above patterns: +/// ```python +/// from airflow.decorators import task +/// +/// +/// @task +/// def access_invalid_key_task_out_of_dag(**context): +/// print("access invalid key", context.get("conf")) +/// ``` +fn is_taskflow(checker: &mut Checker) -> bool { + let mut parents = checker.semantic().current_statements(); + if let Some(Stmt::FunctionDef(StmtFunctionDef { decorator_list, .. })) = + parents.find(|stmt| stmt.is_function_def_stmt()) + { + for decorator in decorator_list { + if checker + .semantic() + .resolve_qualified_name(map_callable(&decorator.expression)) + .is_some_and(|qualified_name| { + matches!(qualified_name.segments(), ["airflow", "decorators", "task"]) + }) + { + return true; + } + } + } + false +} + /// Check whether a removed Airflow class method is called. /// /// For example: @@ -860,3 +1098,54 @@ fn is_airflow_builtin_or_provider(segments: &[&str], module: &str, symbol_suffix _ => false, } } + +/// AIR302 Check the function argument for removed context variable. +/// For example: +/// **Removed context variable as a parameter:** +/// ```python +/// from airflow.decorators import task +/// +/// @task +/// def another_task(execution_date, **kwargs): +/// # 'execution_date' is removed in Airflow 3.0 +/// pass +/// ``` +pub(crate) fn removed_in_3_function_def(checker: &mut Checker, function_def: &StmtFunctionDef) { + if !checker.semantic().seen_module(Modules::AIRFLOW) { + return; + } + + if !is_airflow_task(function_def, checker.semantic()) { + return; + } + + for param in function_def + .parameters + .posonlyargs + .iter() + .chain(function_def.parameters.args.iter()) + .chain(function_def.parameters.kwonlyargs.iter()) + { + let param_name = param.parameter.name.as_str(); + if REMOVED_CONTEXT_KEYS.contains(¶m_name) { + checker.diagnostics.push(Diagnostic::new( + Airflow3Removal { + deprecated: param_name.to_string(), + replacement: Replacement::None, + }, + param.parameter.name.range(), + )); + } + } +} + +/// Returns `true` if the given function is decorated with `@airflow.decorators.task`. +fn is_airflow_task(function_def: &StmtFunctionDef, semantic: &SemanticModel) -> bool { + function_def.decorator_list.iter().any(|decorator| { + 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 00000000000000..2ada0b8fdd55fc --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR302_AIR302_context.py.snap @@ -0,0 +1,514 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +snapshot_kind: text +--- +AIR302_context.py:15:41: AIR302 `conf` is removed in Airflow 3.0 + | +14 | def access_invalid_key_in_context(**context): +15 | print("access invalid key", context["conf"]) + | ^^^^^^ AIR302 +16 | +17 | @task + | + +AIR302_context.py:19:45: AIR302 `conf` is removed in Airflow 3.0 + | +17 | @task +18 | def access_invalid_key_task_out_of_dag(**context): +19 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +20 | +21 | @dag( + | + +AIR302_context.py:19:45: AIR302 `conf` is removed in Airflow 3.0 + | +17 | @task +18 | def access_invalid_key_task_out_of_dag(**context): +19 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +20 | +21 | @dag( + | + +AIR302_context.py:30:49: AIR302 `conf` is removed in Airflow 3.0 + | +28 | @task() +29 | def access_invalid_key_task(**context): +30 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +31 | +32 | task1 = PythonOperator( + | + +AIR302_context.py:30:49: AIR302 `conf` is removed in Airflow 3.0 + | +28 | @task() +29 | def access_invalid_key_task(**context): +30 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +31 | +32 | task1 = PythonOperator( + | + +AIR302_context.py:47:30: AIR302 `execution_date` is removed in Airflow 3.0 + | +46 | # Removed usage - should trigger violations +47 | execution_date = context["execution_date"] + | ^^^^^^^^^^^^^^^^ AIR302 +48 | next_ds = context["next_ds"] +49 | next_ds_nodash = context["next_ds_nodash"] + | + +AIR302_context.py:48:23: AIR302 `next_ds` is removed in Airflow 3.0 + | +46 | # Removed usage - should trigger violations +47 | execution_date = context["execution_date"] +48 | next_ds = context["next_ds"] + | ^^^^^^^^^ AIR302 +49 | next_ds_nodash = context["next_ds_nodash"] +50 | next_execution_date = context["next_execution_date"] + | + +AIR302_context.py:49:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0 + | +47 | execution_date = context["execution_date"] +48 | next_ds = context["next_ds"] +49 | next_ds_nodash = context["next_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +50 | next_execution_date = context["next_execution_date"] +51 | prev_ds = context["prev_ds"] + | + +AIR302_context.py:50:35: AIR302 `next_execution_date` is removed in Airflow 3.0 + | +48 | next_ds = context["next_ds"] +49 | next_ds_nodash = context["next_ds_nodash"] +50 | next_execution_date = context["next_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +51 | prev_ds = context["prev_ds"] +52 | prev_ds_nodash = context["prev_ds_nodash"] + | + +AIR302_context.py:51:23: AIR302 `prev_ds` is removed in Airflow 3.0 + | +49 | next_ds_nodash = context["next_ds_nodash"] +50 | next_execution_date = context["next_execution_date"] +51 | prev_ds = context["prev_ds"] + | ^^^^^^^^^ AIR302 +52 | prev_ds_nodash = context["prev_ds_nodash"] +53 | prev_execution_date = context["prev_execution_date"] + | + +AIR302_context.py:52:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0 + | +50 | next_execution_date = context["next_execution_date"] +51 | prev_ds = context["prev_ds"] +52 | prev_ds_nodash = context["prev_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +53 | prev_execution_date = context["prev_execution_date"] +54 | prev_execution_date_success = context["prev_execution_date_success"] + | + +AIR302_context.py:53:35: AIR302 `prev_execution_date` is removed in Airflow 3.0 + | +51 | prev_ds = context["prev_ds"] +52 | prev_ds_nodash = context["prev_ds_nodash"] +53 | prev_execution_date = context["prev_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +54 | prev_execution_date_success = context["prev_execution_date_success"] +55 | tomorrow_ds = context["tomorrow_ds"] + | + +AIR302_context.py:54:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0 + | +52 | prev_ds_nodash = context["prev_ds_nodash"] +53 | prev_execution_date = context["prev_execution_date"] +54 | prev_execution_date_success = context["prev_execution_date_success"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +55 | tomorrow_ds = context["tomorrow_ds"] +56 | yesterday_ds = context["yesterday_ds"] + | + +AIR302_context.py:55:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +53 | prev_execution_date = context["prev_execution_date"] +54 | prev_execution_date_success = context["prev_execution_date_success"] +55 | tomorrow_ds = context["tomorrow_ds"] + | ^^^^^^^^^^^^^ AIR302 +56 | yesterday_ds = context["yesterday_ds"] +57 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:56:28: AIR302 `yesterday_ds` is removed in Airflow 3.0 + | +54 | prev_execution_date_success = context["prev_execution_date_success"] +55 | tomorrow_ds = context["tomorrow_ds"] +56 | yesterday_ds = context["yesterday_ds"] + | ^^^^^^^^^^^^^^ AIR302 +57 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:57:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0 + | +55 | tomorrow_ds = context["tomorrow_ds"] +56 | yesterday_ds = context["yesterday_ds"] +57 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +58 | +59 | with DAG( + | + +AIR302_context.py:61:5: AIR302 [*] `schedule_interval` is removed in Airflow 3.0 + | +59 | with DAG( +60 | dag_id="example_dag", +61 | schedule_interval="@daily", + | ^^^^^^^^^^^^^^^^^ AIR302 +62 | start_date=datetime(2023, 1, 1), +63 | template_searchpath=["/templates"], + | + = help: Use `schedule` instead + +ℹ Safe fix +58 58 | +59 59 | with DAG( +60 60 | dag_id="example_dag", +61 |- schedule_interval="@daily", + 61 |+ schedule="@daily", +62 62 | start_date=datetime(2023, 1, 1), +63 63 | template_searchpath=["/templates"], +64 64 | ) as dag: + +AIR302_context.py:65:13: AIR302 `airflow.operators.dummy.DummyOperator` is removed in Airflow 3.0 + | +63 | template_searchpath=["/templates"], +64 | ) as dag: +65 | task1 = DummyOperator( + | ^^^^^^^^^^^^^ AIR302 +66 | task_id="task1", +67 | params={ + | + = help: Use `airflow.operators.empty.EmptyOperator` instead + +AIR302_context.py:78:57: AIR302 `execution_date` is removed in Airflow 3.0 + | +76 | name = "custom_macros" +77 | macros = { +78 | "execution_date_macro": lambda context: context["execution_date"], + | ^^^^^^^^^^^^^^^^ AIR302 +79 | "next_ds_macro": lambda context: context["next_ds"] +80 | } + | + +AIR302_context.py:79:50: AIR302 `next_ds` is removed in Airflow 3.0 + | +77 | macros = { +78 | "execution_date_macro": lambda context: context["execution_date"], +79 | "next_ds_macro": lambda context: context["next_ds"] + | ^^^^^^^^^ AIR302 +80 | } + | + +AIR302_context.py:85:30: AIR302 `execution_date` is removed in Airflow 3.0 + | +83 | def print_config(): +84 | context = get_current_context() +85 | execution_date = context["execution_date"] + | ^^^^^^^^^^^^^^^^ AIR302 +86 | next_ds = context["next_ds"] +87 | next_ds_nodash = context["next_ds_nodash"] + | + +AIR302_context.py:86:23: AIR302 `next_ds` is removed in Airflow 3.0 + | +84 | context = get_current_context() +85 | execution_date = context["execution_date"] +86 | next_ds = context["next_ds"] + | ^^^^^^^^^ AIR302 +87 | next_ds_nodash = context["next_ds_nodash"] +88 | next_execution_date = context["next_execution_date"] + | + +AIR302_context.py:87:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0 + | +85 | execution_date = context["execution_date"] +86 | next_ds = context["next_ds"] +87 | next_ds_nodash = context["next_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +88 | next_execution_date = context["next_execution_date"] +89 | prev_ds = context["prev_ds"] + | + +AIR302_context.py:88:35: AIR302 `next_execution_date` is removed in Airflow 3.0 + | +86 | next_ds = context["next_ds"] +87 | next_ds_nodash = context["next_ds_nodash"] +88 | next_execution_date = context["next_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +89 | prev_ds = context["prev_ds"] +90 | prev_ds_nodash = context["prev_ds_nodash"] + | + +AIR302_context.py:89:23: AIR302 `prev_ds` is removed in Airflow 3.0 + | +87 | next_ds_nodash = context["next_ds_nodash"] +88 | next_execution_date = context["next_execution_date"] +89 | prev_ds = context["prev_ds"] + | ^^^^^^^^^ AIR302 +90 | prev_ds_nodash = context["prev_ds_nodash"] +91 | prev_execution_date = context["prev_execution_date"] + | + +AIR302_context.py:90:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0 + | +88 | next_execution_date = context["next_execution_date"] +89 | prev_ds = context["prev_ds"] +90 | prev_ds_nodash = context["prev_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +91 | prev_execution_date = context["prev_execution_date"] +92 | prev_execution_date_success = context["prev_execution_date_success"] + | + +AIR302_context.py:91:35: AIR302 `prev_execution_date` is removed in Airflow 3.0 + | +89 | prev_ds = context["prev_ds"] +90 | prev_ds_nodash = context["prev_ds_nodash"] +91 | prev_execution_date = context["prev_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +92 | prev_execution_date_success = context["prev_execution_date_success"] +93 | tomorrow_ds = context["tomorrow_ds"] + | + +AIR302_context.py:92:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0 + | +90 | prev_ds_nodash = context["prev_ds_nodash"] +91 | prev_execution_date = context["prev_execution_date"] +92 | prev_execution_date_success = context["prev_execution_date_success"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +93 | tomorrow_ds = context["tomorrow_ds"] +94 | yesterday_ds = context["yesterday_ds"] + | + +AIR302_context.py:93:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +91 | prev_execution_date = context["prev_execution_date"] +92 | prev_execution_date_success = context["prev_execution_date_success"] +93 | tomorrow_ds = context["tomorrow_ds"] + | ^^^^^^^^^^^^^ AIR302 +94 | yesterday_ds = context["yesterday_ds"] +95 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:94:28: AIR302 `yesterday_ds` is removed in Airflow 3.0 + | +92 | prev_execution_date_success = context["prev_execution_date_success"] +93 | tomorrow_ds = context["tomorrow_ds"] +94 | yesterday_ds = context["yesterday_ds"] + | ^^^^^^^^^^^^^^ AIR302 +95 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:95:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0 + | +93 | tomorrow_ds = context["tomorrow_ds"] +94 | yesterday_ds = context["yesterday_ds"] +95 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +96 | +97 | class CustomOperator(BaseOperator): + | + +AIR302_context.py:99:34: AIR302 `execution_date` is removed in Airflow 3.0 + | + 97 | class CustomOperator(BaseOperator): + 98 | def execute(self, context): + 99 | execution_date = context["execution_date"] + | ^^^^^^^^^^^^^^^^ AIR302 +100 | next_ds = context["next_ds"] +101 | next_ds_nodash = context["next_ds_nodash"] + | + +AIR302_context.py:100:27: AIR302 `next_ds` is removed in Airflow 3.0 + | + 98 | def execute(self, context): + 99 | execution_date = context["execution_date"] +100 | next_ds = context["next_ds"] + | ^^^^^^^^^ AIR302 +101 | next_ds_nodash = context["next_ds_nodash"] +102 | next_execution_date = context["next_execution_date"] + | + +AIR302_context.py:101:34: AIR302 `next_ds_nodash` is removed in Airflow 3.0 + | + 99 | execution_date = context["execution_date"] +100 | next_ds = context["next_ds"] +101 | next_ds_nodash = context["next_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +102 | next_execution_date = context["next_execution_date"] +103 | prev_ds = context["prev_ds"] + | + +AIR302_context.py:102:39: AIR302 `next_execution_date` is removed in Airflow 3.0 + | +100 | next_ds = context["next_ds"] +101 | next_ds_nodash = context["next_ds_nodash"] +102 | next_execution_date = context["next_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +103 | prev_ds = context["prev_ds"] +104 | prev_ds_nodash = context["prev_ds_nodash"] + | + +AIR302_context.py:103:27: AIR302 `prev_ds` is removed in Airflow 3.0 + | +101 | next_ds_nodash = context["next_ds_nodash"] +102 | next_execution_date = context["next_execution_date"] +103 | prev_ds = context["prev_ds"] + | ^^^^^^^^^ AIR302 +104 | prev_ds_nodash = context["prev_ds_nodash"] +105 | prev_execution_date = context["prev_execution_date"] + | + +AIR302_context.py:104:34: AIR302 `prev_ds_nodash` is removed in Airflow 3.0 + | +102 | next_execution_date = context["next_execution_date"] +103 | prev_ds = context["prev_ds"] +104 | prev_ds_nodash = context["prev_ds_nodash"] + | ^^^^^^^^^^^^^^^^ AIR302 +105 | prev_execution_date = context["prev_execution_date"] +106 | prev_execution_date_success = context["prev_execution_date_success"] + | + +AIR302_context.py:105:39: AIR302 `prev_execution_date` is removed in Airflow 3.0 + | +103 | prev_ds = context["prev_ds"] +104 | prev_ds_nodash = context["prev_ds_nodash"] +105 | prev_execution_date = context["prev_execution_date"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +106 | prev_execution_date_success = context["prev_execution_date_success"] +107 | tomorrow_ds = context["tomorrow_ds"] + | + +AIR302_context.py:106:47: AIR302 `prev_execution_date_success` is removed in Airflow 3.0 + | +104 | prev_ds_nodash = context["prev_ds_nodash"] +105 | prev_execution_date = context["prev_execution_date"] +106 | prev_execution_date_success = context["prev_execution_date_success"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302 +107 | tomorrow_ds = context["tomorrow_ds"] +108 | yesterday_ds = context["yesterday_ds"] + | + +AIR302_context.py:107:31: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +105 | prev_execution_date = context["prev_execution_date"] +106 | prev_execution_date_success = context["prev_execution_date_success"] +107 | tomorrow_ds = context["tomorrow_ds"] + | ^^^^^^^^^^^^^ AIR302 +108 | yesterday_ds = context["yesterday_ds"] +109 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:108:32: AIR302 `yesterday_ds` is removed in Airflow 3.0 + | +106 | prev_execution_date_success = context["prev_execution_date_success"] +107 | tomorrow_ds = context["tomorrow_ds"] +108 | yesterday_ds = context["yesterday_ds"] + | ^^^^^^^^^^^^^^ AIR302 +109 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | + +AIR302_context.py:109:39: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0 + | +107 | tomorrow_ds = context["tomorrow_ds"] +108 | yesterday_ds = context["yesterday_ds"] +109 | yesterday_ds_nodash = context["yesterday_ds_nodash"] + | ^^^^^^^^^^^^^^^^^^^^^ AIR302 +110 | +111 | @task + | + +AIR302_context.py:112:45: AIR302 `execution_date` is removed in Airflow 3.0 + | +111 | @task +112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context): + | ^^^^^^^^^^^^^^ AIR302 +113 | print("execution date", execution_date) +114 | print("access invalid key", context.get("conf")) + | + +AIR302_context.py:112:61: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +111 | @task +112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context): + | ^^^^^^^^^^^ AIR302 +113 | print("execution date", execution_date) +114 | print("access invalid key", context.get("conf")) + | + +AIR302_context.py:114:45: AIR302 `conf` is removed in Airflow 3.0 + | +112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context): +113 | print("execution date", execution_date) +114 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +115 | +116 | @task(task_id="print_the_context") + | + +AIR302_context.py:114:45: AIR302 `conf` is removed in Airflow 3.0 + | +112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context): +113 | print("execution date", execution_date) +114 | print("access invalid key", context.get("conf")) + | ^^^^^^ AIR302 +115 | +116 | @task(task_id="print_the_context") + | + +AIR302_context.py:120:22: AIR302 `tomorrow_ds` is removed in Airflow 3.0 + | +118 | """Print the Airflow context and ds variable from the context.""" +119 | print(ds) +120 | print(kwargs.get("tomorrow_ds")) + | ^^^^^^^^^^^^^ AIR302 +121 | c = get_current_context() +122 | c.get("execution_date") + | + +AIR302_context.py:122:11: AIR302 `execution_date` is removed in Airflow 3.0 + | +120 | print(kwargs.get("tomorrow_ds")) +121 | c = get_current_context() +122 | c.get("execution_date") + | ^^^^^^^^^^^^^^^^ AIR302 +123 | +124 | class CustomOperatorNew(BaseOperator): + | + +AIR302_context.py:122:11: AIR302 `execution_date` is removed in Airflow 3.0 + | +120 | print(kwargs.get("tomorrow_ds")) +121 | c = get_current_context() +122 | c.get("execution_date") + | ^^^^^^^^^^^^^^^^ AIR302 +123 | +124 | class CustomOperatorNew(BaseOperator): + | + +AIR302_context.py:126:38: AIR302 `execution_date` is removed in Airflow 3.0 + | +124 | class CustomOperatorNew(BaseOperator): +125 | def execute(self, context): +126 | execution_date = context.get("execution_date") + | ^^^^^^^^^^^^^^^^ AIR302 +127 | next_ds = context.get("next_ds") + | + +AIR302_context.py:127:31: AIR302 `next_ds` is removed in Airflow 3.0 + | +125 | def execute(self, context): +126 | execution_date = context.get("execution_date") +127 | next_ds = context.get("next_ds") + | ^^^^^^^^^ AIR302 + |