diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_args.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_args.py index f422e97411..1783913133 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_args.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_args.py @@ -22,12 +22,6 @@ DAG(dag_id="class_schedule_interval", schedule_interval="@hourly") DAG(dag_id="class_timetable", timetable=NullTimetable()) -def sla_callback(*arg, **kwargs): - pass - - -DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback) - DAG(dag_id="class_fail_stop", fail_stop=True) DAG(dag_id="class_default_view", default_view="dag_default_view") @@ -53,11 +47,6 @@ def decorator_timetable(): pass -@dag(sla_miss_callback=sla_callback) -def decorator_sla_callback(): - pass - - @dag() def decorator_deprecated_operator_args(): trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator( diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py index 4f6dd40ef0..bd9606b918 100644 --- a/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR301_names.py @@ -9,9 +9,6 @@ from airflow import ( PY311, PY312, ) -from airflow import ( - Dataset as DatasetFromRoot, -) from airflow.api_connexion.security import requires_access, requires_access_dataset from airflow.auth.managers.base_auth_manager import is_authorized_dataset from airflow.auth.managers.models.resource_details import DatasetDetails @@ -26,25 +23,16 @@ from airflow.configuration import ( set, ) from airflow.contrib.aws_athena_hook import AWSAthenaHook -from airflow.datasets import ( - Dataset, - DatasetAlias, - DatasetAliasEvent, - DatasetAll, - DatasetAny, - expand_alias_to_datasets, -) +from airflow.datasets import DatasetAliasEvent from airflow.datasets.manager import ( DatasetManager, dataset_manager, resolve_dataset_manager, ) -from airflow.datasets.metadata import Metadata from airflow.hooks.base_hook import BaseHook from airflow.lineage.hook import DatasetLineageInfo from airflow.listeners.spec.dataset import on_dataset_changed, on_dataset_created from airflow.metrics.validators import AllowListValidator, BlockListValidator -from airflow.models.baseoperator import chain, chain_linear, cross_downstream from airflow.operators.subdag import SubDagOperator from airflow.providers.amazon.aws.auth_manager.avp.entities import AvpEntities from airflow.providers.amazon.aws.datasets import s3 @@ -61,12 +49,10 @@ from airflow.providers.trino.datasets import trino from airflow.secrets.local_filesystem import LocalFilesystemBackend, load_connections from airflow.security.permissions import RESOURCE_DATASET from airflow.sensors.base_sensor_operator import BaseSensorOperator -from airflow.timetables.datasets import DatasetOrTimeSchedule from airflow.timetables.simple import DatasetTriggeredTimetable from airflow.triggers.external_task import TaskStateTrigger from airflow.utils import dates from airflow.utils.dag_cycle_tester import test_cycle -from airflow.utils.dag_parsing_context import get_parsing_context from airflow.utils.dates import ( date_range, datetime_to_nano, @@ -105,14 +91,9 @@ get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set # airflow.contrib.* AWSAthenaHook() + # airflow.datasets -Dataset() -DatasetAlias() DatasetAliasEvent() -DatasetAll() -DatasetAny() -Metadata() -expand_alias_to_datasets # airflow.datasets.manager DatasetManager() @@ -134,10 +115,6 @@ AllowListValidator() BlockListValidator() -# airflow.models.baseoperator -chain, chain_linear, cross_downstream - - # airflow.operators.branch_operator BaseBranchOperator() @@ -207,7 +184,6 @@ BaseSensorOperator() # airflow.timetables -DatasetOrTimeSchedule() DatasetTriggeredTimetable() # airflow.triggers.external_task @@ -231,8 +207,6 @@ dates.datetime_to_nano # airflow.utils.dag_cycle_tester test_cycle -# airflow.utils.dag_parsing_context -get_parsing_context # airflow.utils.db create_session diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_args.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_args.py new file mode 100644 index 0000000000..dbceffc602 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_args.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from datetime import timedelta + +from airflow import DAG, dag +from airflow.operators.datetime import BranchDateTimeOperator + + +def sla_callback(*arg, **kwargs): + pass + + +DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback) + + +@dag(sla_miss_callback=sla_callback) +def decorator_sla_callback(): + pass + + +@dag() +def decorator_deprecated_operator_args(): + branch_dt_op2 = BranchDateTimeOperator( + task_id="branch_dt_op2", + sla=timedelta(seconds=10), + ) + + branch_dt_op2 diff --git a/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py new file mode 100644 index 0000000000..a100fbbfe0 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from airflow import Dataset as DatasetFromRoot +from airflow.datasets import ( + Dataset, + DatasetAlias, + DatasetAll, + DatasetAny, + expand_alias_to_datasets, +) +from airflow.datasets.metadata import Metadata +from airflow.models.baseoperator import chain, chain_linear, cross_downstream +from airflow.models.baseoperatorlink import BaseOperatorLink +from airflow.timetables.datasets import DatasetOrTimeSchedule +from airflow.utils.dag_parsing_context import get_parsing_context + +DatasetFromRoot() + +# airflow.datasets +Dataset() +DatasetAlias() +DatasetAll() +DatasetAny() +Metadata() +expand_alias_to_datasets() + +# airflow.models.baseoperator +chain() +chain_linear() +cross_downstream() + +# airflow.models.baseoperatolinker +BaseOperatorLink() + +# airflow.timetables.datasets +DatasetOrTimeSchedule() + +# airflow.utils.dag_parsing_context +get_parsing_context() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index ebae9fb17d..896b6f9dfe 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -229,6 +229,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { if checker.enabled(Rule::Airflow3Removal) { airflow::rules::airflow_3_removal_expr(checker, expr); } + if checker.enabled(Rule::Airflow3SuggestedUpdate) { + airflow::rules::airflow_3_0_suggested_update_expr(checker, expr); + } if checker.enabled(Rule::Airflow3MovedToProvider) { airflow::rules::moved_to_provider_in_3(checker, expr); } @@ -451,6 +454,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { if checker.enabled(Rule::Airflow3Removal) { airflow::rules::airflow_3_removal_expr(checker, expr); } + if checker.enabled(Rule::Airflow3SuggestedUpdate) { + airflow::rules::airflow_3_0_suggested_update_expr(checker, expr); + } if checker.enabled(Rule::Airflow3MovedToProvider) { airflow::rules::moved_to_provider_in_3(checker, expr); } @@ -1152,6 +1158,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { if checker.enabled(Rule::Airflow3Removal) { airflow::rules::airflow_3_removal_expr(checker, expr); } + if checker.enabled(Rule::Airflow3SuggestedUpdate) { + airflow::rules::airflow_3_0_suggested_update_expr(checker, expr); + } if checker.enabled(Rule::UnnecessaryCastToInt) { ruff::rules::unnecessary_cast_to_int(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 2271954d24..dce86a56a8 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1072,6 +1072,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Airflow, "002") => (RuleGroup::Preview, rules::airflow::rules::AirflowDagNoScheduleArgument), (Airflow, "301") => (RuleGroup::Preview, rules::airflow::rules::Airflow3Removal), (Airflow, "302") => (RuleGroup::Preview, rules::airflow::rules::Airflow3MovedToProvider), + (Airflow, "311") => (RuleGroup::Preview, rules::airflow::rules::Airflow3SuggestedUpdate), (Airflow, "312") => (RuleGroup::Preview, rules::airflow::rules::Airflow3SuggestedToMoveToProvider), // perflint diff --git a/crates/ruff_linter/src/rules/airflow/helpers.rs b/crates/ruff_linter/src/rules/airflow/helpers.rs index 959c07f05c..23aaaa07d4 100644 --- a/crates/ruff_linter/src/rules/airflow/helpers.rs +++ b/crates/ruff_linter/src/rules/airflow/helpers.rs @@ -81,3 +81,45 @@ fn try_block_contains_undeprecated_import(try_node: &StmtTry, replacement: &Repl import_searcher.visit_body(&try_node.body); import_searcher.found_import } + +/// Check whether the segments corresponding to the fully qualified name points to a symbol that's +/// either a builtin or coming from one of the providers in Airflow. +/// +/// The pattern it looks for are: +/// - `airflow.providers.**..**.*` for providers +/// - `airflow..**.*` for builtins +/// +/// where `**` is one or more segments separated by a dot, and `*` is one or more characters. +/// +/// Examples for the above patterns: +/// - `airflow.providers.google.cloud.secrets.secret_manager.CloudSecretManagerBackend` (provider) +/// - `airflow.secrets.base_secrets.BaseSecretsBackend` (builtin) +pub(crate) fn is_airflow_builtin_or_provider( + segments: &[&str], + module: &str, + symbol_suffix: &str, +) -> bool { + match segments { + ["airflow", "providers", rest @ ..] => { + if let (Some(pos), Some(last_element)) = + (rest.iter().position(|&s| s == module), rest.last()) + { + // Check that the module is not the last element i.e., there's a symbol that's + // being used from the `module` that ends with `symbol_suffix`. + pos + 1 < rest.len() && last_element.ends_with(symbol_suffix) + } else { + false + } + } + + ["airflow", first, rest @ ..] => { + if let Some(last) = rest.last() { + *first == module && last.ends_with(symbol_suffix) + } else { + false + } + } + + _ => false, + } +} diff --git a/crates/ruff_linter/src/rules/airflow/mod.rs b/crates/ruff_linter/src/rules/airflow/mod.rs index c2be3df3b6..69a35b5dd3 100644 --- a/crates/ruff_linter/src/rules/airflow/mod.rs +++ b/crates/ruff_linter/src/rules/airflow/mod.rs @@ -22,6 +22,8 @@ mod tests { #[test_case(Rule::Airflow3Removal, Path::new("AIR301_airflow_plugin.py"))] #[test_case(Rule::Airflow3Removal, Path::new("AIR301_context.py"))] #[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302.py"))] + #[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_args.py"))] + #[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_names.py"))] #[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312.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/mod.rs b/crates/ruff_linter/src/rules/airflow/rules/mod.rs index 333c11afe5..9b71032b5f 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/mod.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/mod.rs @@ -2,10 +2,12 @@ pub(crate) use dag_schedule_argument::*; pub(crate) use moved_to_provider_in_3::*; pub(crate) use removal_in_3::*; pub(crate) use suggested_to_move_to_provider_in_3::*; +pub(crate) use suggested_to_update_3_0::*; pub(crate) use task_variable_name::*; mod dag_schedule_argument; mod moved_to_provider_in_3; mod removal_in_3; mod suggested_to_move_to_provider_in_3; +mod suggested_to_update_3_0; mod task_variable_name; 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 59f9f6853d..b73bb11e51 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,6 +1,8 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; -use crate::rules::airflow::helpers::{is_guarded_by_try_except, Replacement}; +use crate::rules::airflow::helpers::{ + is_airflow_builtin_or_provider, is_guarded_by_try_except, Replacement, +}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::helpers::map_callable; @@ -207,14 +209,9 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum // without replacement checker.report_diagnostics(diagnostic_for_argument(arguments, "default_view", None)); checker.report_diagnostics(diagnostic_for_argument(arguments, "orientation", None)); - checker.report_diagnostics(diagnostic_for_argument( - arguments, - "sla_miss_callback", - None, - )); } - _ => { - if is_airflow_auth_manager(qualified_name.segments()) { + segments => { + if is_airflow_auth_manager(segments) { if !arguments.is_empty() { checker.report_diagnostic(Diagnostic::new( Airflow3Removal { @@ -226,20 +223,19 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum arguments.range(), )); } - } else if is_airflow_task_handler(qualified_name.segments()) { + } else if is_airflow_task_handler(segments) { checker.report_diagnostics(diagnostic_for_argument( arguments, "filename_template", None, )); - } else if is_airflow_operator(qualified_name.segments()) { - checker.report_diagnostics(diagnostic_for_argument(arguments, "sla", None)); + } else if is_airflow_builtin_or_provider(segments, "operators", "Operator") { checker.report_diagnostics(diagnostic_for_argument( arguments, "task_concurrency", Some("max_active_tis_per_dag"), )); - match qualified_name.segments() { + match segments { ["airflow", .., "operators", "trigger_dagrun", "TriggerDagRunOperator"] => { checker.report_diagnostics(diagnostic_for_argument( arguments, @@ -615,22 +611,8 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { "resolve_dataset_manager" => Replacement::Name("airflow.assets.resolve_asset_manager"), _ => return, }, - ["airflow", "datasets", "metadata", "Metadata"] => { - Replacement::Name("airflow.sdk.Metadata") - } // airflow.datasets - ["airflow", "Dataset"] | ["airflow", "datasets", "Dataset"] => Replacement::AutoImport { - module: "airflow.sdk", - name: "Asset", - }, - ["airflow", "datasets", rest] => match *rest { - "DatasetAliasEvent" => Replacement::None, - "DatasetAlias" => Replacement::Name("airflow.sdk.AssetAlias"), - "DatasetAll" => Replacement::Name("airflow.sdk.AssetAll"), - "DatasetAny" => Replacement::Name("airflow.sdk.AssetAny"), - "expand_alias_to_datasets" => Replacement::Name("airflow.sdk.expand_alias_to_assets"), - _ => return, - }, + ["airflow", "datasets", "DatasetAliasEvent"] => Replacement::None, // airflow.hooks ["airflow", "hooks", "base_hook", "BaseHook"] => { @@ -665,18 +647,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { _ => return, }, - // airflow.models.baseoperator - ["airflow", "models", "baseoperator", rest] => match *rest { - "chain" | "chain_linear" | "cross_downstream" => Replacement::SourceModuleMoved { - module: "airflow.sdk", - name: (*rest).to_string(), - }, - "BaseOperatorLink" => { - Replacement::Name("airflow.sdk.definitions.baseoperatorlink.BaseOperatorLink") - } - _ => return, - }, - // airflow.notifications ["airflow", "notifications", "basenotifier", "BaseNotifier"] => { Replacement::Name("airflow.sdk.BaseNotifier") @@ -703,15 +673,9 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { } // airflow.timetables - ["airflow", "timetables", rest @ ..] => match &rest { - ["datasets", "DatasetOrTimeSchedule"] => { - Replacement::Name("airflow.timetables.assets.AssetOrTimeSchedule") - } - ["simple", "DatasetTriggeredTimetable"] => { - Replacement::Name("airflow.timetables.simple.AssetTriggeredTimetable") - } - _ => return, - }, + ["airflow", "timetables", "simple", "DatasetTriggeredTimetable"] => { + Replacement::Name("airflow.timetables.simple.AssetTriggeredTimetable") + } // airflow.triggers ["airflow", "triggers", "external_task", "TaskStateTrigger"] => Replacement::None, @@ -980,12 +944,6 @@ fn is_airflow_hook(segments: &[&str]) -> bool { is_airflow_builtin_or_provider(segments, "hooks", "Hook") } -/// Check whether the symbol is coming from the `operators` builtin or provider module which ends -/// with `Operator`. -fn is_airflow_operator(segments: &[&str]) -> bool { - is_airflow_builtin_or_provider(segments, "operators", "Operator") -} - /// Check whether the symbol is coming from the `log` builtin or provider module which ends /// with `TaskHandler`. fn is_airflow_task_handler(segments: &[&str]) -> bool { @@ -1018,44 +976,6 @@ fn is_airflow_auth_manager(segments: &[&str]) -> bool { } } -/// Check whether the segments corresponding to the fully qualified name points to a symbol that's -/// either a builtin or coming from one of the providers in Airflow. -/// -/// The pattern it looks for are: -/// - `airflow.providers.**..**.*` for providers -/// - `airflow..**.*` for builtins -/// -/// where `**` is one or more segments separated by a dot, and `*` is one or more characters. -/// -/// Examples for the above patterns: -/// - `airflow.providers.google.cloud.secrets.secret_manager.CloudSecretManagerBackend` (provider) -/// - `airflow.secrets.base_secrets.BaseSecretsBackend` (builtin) -fn is_airflow_builtin_or_provider(segments: &[&str], module: &str, symbol_suffix: &str) -> bool { - match segments { - ["airflow", "providers", rest @ ..] => { - if let (Some(pos), Some(last_element)) = - (rest.iter().position(|&s| s == module), rest.last()) - { - // Check that the module is not the last element i.e., there's a symbol that's - // being used from the `module` that ends with `symbol_suffix`. - pos + 1 < rest.len() && last_element.ends_with(symbol_suffix) - } else { - false - } - } - - ["airflow", first, rest @ ..] => { - if let Some(last) = rest.last() { - *first == module && last.ends_with(symbol_suffix) - } else { - false - } - } - - _ => false, - } -} - /// Returns `true` if the current statement hierarchy has a function that's decorated with /// `@airflow.decorators.task`. fn in_airflow_task_function(semantic: &SemanticModel) -> bool { diff --git a/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs new file mode 100644 index 0000000000..dff3bfdf16 --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs @@ -0,0 +1,261 @@ +use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use crate::rules::airflow::helpers::{ + is_airflow_builtin_or_provider, is_guarded_by_try_except, Replacement, +}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::{name::QualifiedName, Arguments, Expr, ExprAttribute, ExprCall, ExprName}; +use ruff_python_semantic::Modules; +use ruff_text_size::Ranged; +use ruff_text_size::TextRange; + +/// ## What it does +/// Checks for uses of deprecated Airflow functions and values that still have +/// a compatibility layer. +/// +/// ## Why is this bad? +/// Airflow 3.0 removed various deprecated functions, members, and other +/// values. Some have more modern replacements. Others are considered too niche +/// and not worth to be maintained in Airflow. +/// Even though these symbols still work fine on Airflow 3.0, they are expected to be removed in a future version. +/// The user is suggested to replace the original usage with the new ones. +/// +/// ## Example +/// ```python +/// from airflow import Dataset +/// +/// +/// Dataset(uri="test://test/") +/// ``` +/// +/// Use instead: +/// ```python +/// from airflow.sdk import Asset +/// +/// +/// Asset(uri="test://test/") +/// ``` +#[derive(ViolationMetadata)] +pub(crate) struct Airflow3SuggestedUpdate { + deprecated: String, + replacement: Replacement, +} + +impl Violation for Airflow3SuggestedUpdate { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + let Airflow3SuggestedUpdate { + deprecated, + replacement, + } = self; + match replacement { + Replacement::None + | Replacement::Name(_) + | Replacement::AutoImport { module: _, name: _ } + | Replacement::SourceModuleMoved { module: _, name: _ } => { + format!( + "`{deprecated}` is removed in Airflow 3.0; \ + It still works in Airflow 3.0 but is expected to be removed in a future version." + ) + } + Replacement::Message(message) => { + format!( + "`{deprecated}` is removed in Airflow 3.0; \ + It still works in Airflow 3.0 but is expected to be removed in a future version.; \ + {message}" + ) + } + } + } + + fn fix_title(&self) -> Option { + let Airflow3SuggestedUpdate { replacement, .. } = self; + match replacement { + Replacement::Name(name) => Some(format!("Use `{name}` instead")), + Replacement::AutoImport { module, name } => { + Some(format!("Use `{module}.{name}` instead")) + } + Replacement::SourceModuleMoved { module, name } => { + Some(format!("Use `{module}.{name}` instead")) + } + _ => None, + } + } +} + +/// AIR311 +pub(crate) fn airflow_3_0_suggested_update_expr(checker: &Checker, expr: &Expr) { + if !checker.semantic().seen_module(Modules::AIRFLOW) { + return; + } + + match expr { + Expr::Call(ExprCall { + func, arguments, .. + }) => { + if let Some(qualified_name) = checker.semantic().resolve_qualified_name(func) { + check_call_arguments(checker, &qualified_name, arguments); + } + } + Expr::Attribute(ExprAttribute { attr, .. }) => { + check_name(checker, expr, attr.range()); + } + Expr::Name(ExprName { + id: _, + ctx: _, + range, + }) => { + check_name(checker, expr, *range); + } + _ => {} + } +} + +/// Check if the `deprecated` keyword argument is being used and create a diagnostic if so along +/// with a possible `replacement`. +fn diagnostic_for_argument( + arguments: &Arguments, + deprecated: &str, + replacement: Option<&'static str>, +) -> Option { + let keyword = arguments.find_keyword(deprecated)?; + let mut diagnostic = Diagnostic::new( + Airflow3SuggestedUpdate { + deprecated: deprecated.to_string(), + replacement: match replacement { + Some(name) => Replacement::Name(name), + None => Replacement::None, + }, + }, + keyword + .arg + .as_ref() + .map_or_else(|| keyword.range(), Ranged::range), + ); + + if let Some(replacement) = replacement { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + replacement.to_string(), + diagnostic.range, + ))); + } + + Some(diagnostic) +} +/// Check whether a removed Airflow argument is passed. +/// +/// For example: +/// +/// ```python +/// from airflow import DAG +/// +/// DAG(sla="@daily") +/// ``` +fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, arguments: &Arguments) { + match qualified_name.segments() { + ["airflow", .., "DAG" | "dag"] => { + checker.report_diagnostics(diagnostic_for_argument( + arguments, + "sla_miss_callback", + None, + )); + } + segments => { + if is_airflow_builtin_or_provider(segments, "operators", "Operator") { + checker.report_diagnostics(diagnostic_for_argument(arguments, "sla", None)); + } + } + } +} + +/// Check whether a removed Airflow name is used. +/// +/// For example: +/// +/// ```python +/// from airflow import Dataset +/// from airflow import datasets +/// +/// # Accessing via attribute +/// datasets.Dataset() +/// +/// # Or, directly +/// Dataset() +/// ``` +fn check_name(checker: &Checker, expr: &Expr, range: TextRange) { + let semantic = checker.semantic(); + + let Some(qualified_name) = semantic.resolve_qualified_name(expr) else { + return; + }; + + let replacement = match qualified_name.segments() { + // airflow.datasets.metadata + ["airflow", "datasets", "metadata", "Metadata"] => { + Replacement::Name("airflow.sdk.Metadata") + } + // airflow.datasets + ["airflow", "Dataset"] | ["airflow", "datasets", "Dataset"] => Replacement::AutoImport { + module: "airflow.sdk", + name: "Asset", + }, + ["airflow", "datasets", rest] => match *rest { + "DatasetAliasEvent" => Replacement::None, + "DatasetAlias" => Replacement::Name("airflow.sdk.AssetAlias"), + "DatasetAll" => Replacement::Name("airflow.sdk.AssetAll"), + "DatasetAny" => Replacement::Name("airflow.sdk.AssetAny"), + "expand_alias_to_datasets" => Replacement::Name("airflow.sdk.expand_alias_to_assets"), + _ => return, + }, + // airflow.models.baseoperator + ["airflow", "models", "baseoperator", rest] => match *rest { + "chain" | "chain_linear" | "cross_downstream" => Replacement::SourceModuleMoved { + module: "airflow.sdk", + name: (*rest).to_string(), + }, + "BaseOperatorLink" => { + Replacement::Name("airflow.sdk.definitions.baseoperatorlink.BaseOperatorLink") + } + _ => return, + }, + // airflow.timetables + ["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => { + Replacement::Name("airflow.timetables.assets.AssetOrTimeSchedule") + } + // airflow.utils + ["airflow", "utils", "dag_parsing_context", "get_parsing_context"] => { + Replacement::Name("airflow.sdk.get_parsing_context") + } + + _ => return, + }; + + if is_guarded_by_try_except(expr, &replacement, semantic) { + return; + } + + let mut diagnostic = Diagnostic::new( + Airflow3SuggestedUpdate { + deprecated: qualified_name.to_string(), + replacement: replacement.clone(), + }, + range, + ); + + if let Replacement::AutoImport { module, name } = replacement { + diagnostic.try_set_fix(|| { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import_from(module, name), + expr.start(), + checker.semantic(), + )?; + let replacement_edit = Edit::range_replacement(binding, range); + Ok(Fix::safe_edits(import_edit, [replacement_edit])) + }); + } + + checker.report_diagnostic(diagnostic); +} diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap index e3978f3c14..4731893731 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_args.py.snap @@ -39,256 +39,229 @@ AIR301_args.py:22:31: AIR301 [*] `timetable` is removed in Airflow 3.0 22 |+DAG(dag_id="class_timetable", schedule=NullTimetable()) 23 23 | 24 24 | -25 25 | def sla_callback(*arg, **kwargs): +25 25 | DAG(dag_id="class_fail_stop", fail_stop=True) -AIR301_args.py:29:34: AIR301 `sla_miss_callback` is removed in Airflow 3.0 +AIR301_args.py:25:31: AIR301 [*] `fail_stop` is removed in Airflow 3.0 | -29 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback) - | ^^^^^^^^^^^^^^^^^ AIR301 -30 | -31 | DAG(dag_id="class_fail_stop", fail_stop=True) - | - -AIR301_args.py:31:31: AIR301 [*] `fail_stop` is removed in Airflow 3.0 - | -29 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback) -30 | -31 | DAG(dag_id="class_fail_stop", fail_stop=True) +25 | DAG(dag_id="class_fail_stop", fail_stop=True) | ^^^^^^^^^ AIR301 -32 | -33 | DAG(dag_id="class_default_view", default_view="dag_default_view") +26 | +27 | DAG(dag_id="class_default_view", default_view="dag_default_view") | = help: Use `fail_fast` instead ℹ Safe fix +22 22 | DAG(dag_id="class_timetable", timetable=NullTimetable()) +23 23 | +24 24 | +25 |-DAG(dag_id="class_fail_stop", fail_stop=True) + 25 |+DAG(dag_id="class_fail_stop", fail_fast=True) +26 26 | +27 27 | DAG(dag_id="class_default_view", default_view="dag_default_view") 28 28 | -29 29 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback) -30 30 | -31 |-DAG(dag_id="class_fail_stop", fail_stop=True) - 31 |+DAG(dag_id="class_fail_stop", fail_fast=True) -32 32 | -33 33 | DAG(dag_id="class_default_view", default_view="dag_default_view") -34 34 | -AIR301_args.py:33:34: AIR301 `default_view` is removed in Airflow 3.0 +AIR301_args.py:27:34: AIR301 `default_view` is removed in Airflow 3.0 | -31 | DAG(dag_id="class_fail_stop", fail_stop=True) -32 | -33 | DAG(dag_id="class_default_view", default_view="dag_default_view") +25 | DAG(dag_id="class_fail_stop", fail_stop=True) +26 | +27 | DAG(dag_id="class_default_view", default_view="dag_default_view") | ^^^^^^^^^^^^ AIR301 -34 | -35 | DAG(dag_id="class_orientation", orientation="BT") +28 | +29 | DAG(dag_id="class_orientation", orientation="BT") | -AIR301_args.py:35:33: AIR301 `orientation` is removed in Airflow 3.0 +AIR301_args.py:29:33: AIR301 `orientation` is removed in Airflow 3.0 | -33 | DAG(dag_id="class_default_view", default_view="dag_default_view") -34 | -35 | DAG(dag_id="class_orientation", orientation="BT") +27 | DAG(dag_id="class_default_view", default_view="dag_default_view") +28 | +29 | DAG(dag_id="class_orientation", orientation="BT") | ^^^^^^^^^^^ AIR301 -36 | -37 | allow_future_exec_dates_dag = DAG(dag_id="class_allow_future_exec_dates") +30 | +31 | allow_future_exec_dates_dag = DAG(dag_id="class_allow_future_exec_dates") | -AIR301_args.py:46:6: AIR301 [*] `schedule_interval` is removed in Airflow 3.0 +AIR301_args.py:40:6: AIR301 [*] `schedule_interval` is removed in Airflow 3.0 | -46 | @dag(schedule_interval="0 * * * *") +40 | @dag(schedule_interval="0 * * * *") | ^^^^^^^^^^^^^^^^^ AIR301 -47 | def decorator_schedule_interval(): -48 | pass +41 | def decorator_schedule_interval(): +42 | pass | = help: Use `schedule` instead ℹ Safe fix -43 43 | pass -44 44 | -45 45 | -46 |-@dag(schedule_interval="0 * * * *") - 46 |+@dag(schedule="0 * * * *") -47 47 | def decorator_schedule_interval(): -48 48 | pass -49 49 | +37 37 | pass +38 38 | +39 39 | +40 |-@dag(schedule_interval="0 * * * *") + 40 |+@dag(schedule="0 * * * *") +41 41 | def decorator_schedule_interval(): +42 42 | pass +43 43 | -AIR301_args.py:51:6: AIR301 [*] `timetable` is removed in Airflow 3.0 +AIR301_args.py:45:6: AIR301 [*] `timetable` is removed in Airflow 3.0 | -51 | @dag(timetable=NullTimetable()) +45 | @dag(timetable=NullTimetable()) | ^^^^^^^^^ AIR301 -52 | def decorator_timetable(): -53 | pass +46 | def decorator_timetable(): +47 | pass | = help: Use `schedule` instead ℹ Safe fix -48 48 | pass -49 49 | -50 50 | -51 |-@dag(timetable=NullTimetable()) - 51 |+@dag(schedule=NullTimetable()) -52 52 | def decorator_timetable(): -53 53 | pass -54 54 | +42 42 | pass +43 43 | +44 44 | +45 |-@dag(timetable=NullTimetable()) + 45 |+@dag(schedule=NullTimetable()) +46 46 | def decorator_timetable(): +47 47 | pass +48 48 | -AIR301_args.py:56:6: AIR301 `sla_miss_callback` is removed in Airflow 3.0 +AIR301_args.py:53:39: AIR301 [*] `execution_date` is removed in Airflow 3.0 | -56 | @dag(sla_miss_callback=sla_callback) - | ^^^^^^^^^^^^^^^^^ AIR301 -57 | def decorator_sla_callback(): -58 | pass - | - -AIR301_args.py:64:39: AIR301 [*] `execution_date` is removed in Airflow 3.0 - | -62 | def decorator_deprecated_operator_args(): -63 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator( -64 | task_id="trigger_dagrun_op1", execution_date="2024-12-04" +51 | def decorator_deprecated_operator_args(): +52 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator( +53 | task_id="trigger_dagrun_op1", execution_date="2024-12-04" | ^^^^^^^^^^^^^^ AIR301 -65 | ) -66 | trigger_dagrun_op2 = TriggerDagRunOperator( +54 | ) +55 | trigger_dagrun_op2 = TriggerDagRunOperator( | = help: Use `logical_date` instead ℹ Safe fix -61 61 | @dag() -62 62 | def decorator_deprecated_operator_args(): -63 63 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator( -64 |- task_id="trigger_dagrun_op1", execution_date="2024-12-04" - 64 |+ task_id="trigger_dagrun_op1", logical_date="2024-12-04" -65 65 | ) -66 66 | trigger_dagrun_op2 = TriggerDagRunOperator( -67 67 | task_id="trigger_dagrun_op2", execution_date="2024-12-04" +50 50 | @dag() +51 51 | def decorator_deprecated_operator_args(): +52 52 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator( +53 |- task_id="trigger_dagrun_op1", execution_date="2024-12-04" + 53 |+ task_id="trigger_dagrun_op1", logical_date="2024-12-04" +54 54 | ) +55 55 | trigger_dagrun_op2 = TriggerDagRunOperator( +56 56 | task_id="trigger_dagrun_op2", execution_date="2024-12-04" -AIR301_args.py:67:39: AIR301 [*] `execution_date` is removed in Airflow 3.0 +AIR301_args.py:56:39: AIR301 [*] `execution_date` is removed in Airflow 3.0 | -65 | ) -66 | trigger_dagrun_op2 = TriggerDagRunOperator( -67 | task_id="trigger_dagrun_op2", execution_date="2024-12-04" +54 | ) +55 | trigger_dagrun_op2 = TriggerDagRunOperator( +56 | task_id="trigger_dagrun_op2", execution_date="2024-12-04" | ^^^^^^^^^^^^^^ AIR301 -68 | ) +57 | ) | = help: Use `logical_date` instead ℹ Safe fix -64 64 | task_id="trigger_dagrun_op1", execution_date="2024-12-04" -65 65 | ) -66 66 | trigger_dagrun_op2 = TriggerDagRunOperator( -67 |- task_id="trigger_dagrun_op2", execution_date="2024-12-04" - 67 |+ task_id="trigger_dagrun_op2", logical_date="2024-12-04" -68 68 | ) -69 69 | -70 70 | branch_dt_op = datetime.BranchDateTimeOperator( +53 53 | task_id="trigger_dagrun_op1", execution_date="2024-12-04" +54 54 | ) +55 55 | trigger_dagrun_op2 = TriggerDagRunOperator( +56 |- task_id="trigger_dagrun_op2", execution_date="2024-12-04" + 56 |+ task_id="trigger_dagrun_op2", logical_date="2024-12-04" +57 57 | ) +58 58 | +59 59 | branch_dt_op = datetime.BranchDateTimeOperator( -AIR301_args.py:71:33: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0 +AIR301_args.py:60:33: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0 | -70 | branch_dt_op = datetime.BranchDateTimeOperator( -71 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 +59 | branch_dt_op = datetime.BranchDateTimeOperator( +60 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 | ^^^^^^^^^^^^^^^^^^^^^^ AIR301 -72 | ) -73 | branch_dt_op2 = BranchDateTimeOperator( +61 | ) +62 | branch_dt_op2 = BranchDateTimeOperator( | = help: Use `use_task_logical_date` instead ℹ Safe fix -68 68 | ) -69 69 | -70 70 | branch_dt_op = datetime.BranchDateTimeOperator( -71 |- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 - 71 |+ task_id="branch_dt_op", use_task_logical_date=True, task_concurrency=5 -72 72 | ) -73 73 | branch_dt_op2 = BranchDateTimeOperator( -74 74 | task_id="branch_dt_op2", +57 57 | ) +58 58 | +59 59 | branch_dt_op = datetime.BranchDateTimeOperator( +60 |- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 + 60 |+ task_id="branch_dt_op", use_task_logical_date=True, task_concurrency=5 +61 61 | ) +62 62 | branch_dt_op2 = BranchDateTimeOperator( +63 63 | task_id="branch_dt_op2", -AIR301_args.py:71:62: AIR301 [*] `task_concurrency` is removed in Airflow 3.0 +AIR301_args.py:60:62: AIR301 [*] `task_concurrency` is removed in Airflow 3.0 | -70 | branch_dt_op = datetime.BranchDateTimeOperator( -71 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 +59 | branch_dt_op = datetime.BranchDateTimeOperator( +60 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 | ^^^^^^^^^^^^^^^^ AIR301 -72 | ) -73 | branch_dt_op2 = BranchDateTimeOperator( +61 | ) +62 | branch_dt_op2 = BranchDateTimeOperator( | = help: Use `max_active_tis_per_dag` instead ℹ Safe fix -68 68 | ) -69 69 | -70 70 | branch_dt_op = datetime.BranchDateTimeOperator( -71 |- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 - 71 |+ task_id="branch_dt_op", use_task_execution_day=True, max_active_tis_per_dag=5 -72 72 | ) -73 73 | branch_dt_op2 = BranchDateTimeOperator( -74 74 | task_id="branch_dt_op2", +57 57 | ) +58 58 | +59 59 | branch_dt_op = datetime.BranchDateTimeOperator( +60 |- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5 + 60 |+ task_id="branch_dt_op", use_task_execution_day=True, max_active_tis_per_dag=5 +61 61 | ) +62 62 | branch_dt_op2 = BranchDateTimeOperator( +63 63 | task_id="branch_dt_op2", -AIR301_args.py:75:9: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0 +AIR301_args.py:64:9: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0 | -73 | branch_dt_op2 = BranchDateTimeOperator( -74 | task_id="branch_dt_op2", -75 | use_task_execution_day=True, +62 | branch_dt_op2 = BranchDateTimeOperator( +63 | task_id="branch_dt_op2", +64 | use_task_execution_day=True, | ^^^^^^^^^^^^^^^^^^^^^^ AIR301 -76 | sla=timedelta(seconds=10), -77 | ) +65 | sla=timedelta(seconds=10), +66 | ) | = help: Use `use_task_logical_date` instead ℹ Safe fix -72 72 | ) -73 73 | branch_dt_op2 = BranchDateTimeOperator( -74 74 | task_id="branch_dt_op2", -75 |- use_task_execution_day=True, - 75 |+ use_task_logical_date=True, -76 76 | sla=timedelta(seconds=10), -77 77 | ) -78 78 | +61 61 | ) +62 62 | branch_dt_op2 = BranchDateTimeOperator( +63 63 | task_id="branch_dt_op2", +64 |- use_task_execution_day=True, + 64 |+ use_task_logical_date=True, +65 65 | sla=timedelta(seconds=10), +66 66 | ) +67 67 | -AIR301_args.py:76:9: AIR301 `sla` is removed in Airflow 3.0 +AIR301_args.py:87:15: AIR301 `filename_template` is removed in Airflow 3.0 | -74 | task_id="branch_dt_op2", -75 | use_task_execution_day=True, -76 | sla=timedelta(seconds=10), - | ^^^ AIR301 -77 | ) +86 | # deprecated filename_template argument in FileTaskHandler +87 | S3TaskHandler(filename_template="/tmp/test") + | ^^^^^^^^^^^^^^^^^ AIR301 +88 | HdfsTaskHandler(filename_template="/tmp/test") +89 | ElasticsearchTaskHandler(filename_template="/tmp/test") | -AIR301_args.py:98:15: AIR301 `filename_template` is removed in Airflow 3.0 - | - 97 | # deprecated filename_template argument in FileTaskHandler - 98 | S3TaskHandler(filename_template="/tmp/test") - | ^^^^^^^^^^^^^^^^^ AIR301 - 99 | HdfsTaskHandler(filename_template="/tmp/test") -100 | ElasticsearchTaskHandler(filename_template="/tmp/test") - | +AIR301_args.py:88:17: AIR301 `filename_template` is removed in Airflow 3.0 + | +86 | # deprecated filename_template argument in FileTaskHandler +87 | S3TaskHandler(filename_template="/tmp/test") +88 | HdfsTaskHandler(filename_template="/tmp/test") + | ^^^^^^^^^^^^^^^^^ AIR301 +89 | ElasticsearchTaskHandler(filename_template="/tmp/test") +90 | GCSTaskHandler(filename_template="/tmp/test") + | -AIR301_args.py:99:17: AIR301 `filename_template` is removed in Airflow 3.0 - | - 97 | # deprecated filename_template argument in FileTaskHandler - 98 | S3TaskHandler(filename_template="/tmp/test") - 99 | HdfsTaskHandler(filename_template="/tmp/test") - | ^^^^^^^^^^^^^^^^^ AIR301 -100 | ElasticsearchTaskHandler(filename_template="/tmp/test") -101 | GCSTaskHandler(filename_template="/tmp/test") - | +AIR301_args.py:89:26: AIR301 `filename_template` is removed in Airflow 3.0 + | +87 | S3TaskHandler(filename_template="/tmp/test") +88 | HdfsTaskHandler(filename_template="/tmp/test") +89 | ElasticsearchTaskHandler(filename_template="/tmp/test") + | ^^^^^^^^^^^^^^^^^ AIR301 +90 | GCSTaskHandler(filename_template="/tmp/test") + | -AIR301_args.py:100:26: AIR301 `filename_template` is removed in Airflow 3.0 - | - 98 | S3TaskHandler(filename_template="/tmp/test") - 99 | HdfsTaskHandler(filename_template="/tmp/test") -100 | ElasticsearchTaskHandler(filename_template="/tmp/test") - | ^^^^^^^^^^^^^^^^^ AIR301 -101 | GCSTaskHandler(filename_template="/tmp/test") - | +AIR301_args.py:90:16: AIR301 `filename_template` is removed in Airflow 3.0 + | +88 | HdfsTaskHandler(filename_template="/tmp/test") +89 | ElasticsearchTaskHandler(filename_template="/tmp/test") +90 | GCSTaskHandler(filename_template="/tmp/test") + | ^^^^^^^^^^^^^^^^^ AIR301 +91 | +92 | FabAuthManager(None) + | -AIR301_args.py:101:16: AIR301 `filename_template` is removed in Airflow 3.0 - | - 99 | HdfsTaskHandler(filename_template="/tmp/test") -100 | ElasticsearchTaskHandler(filename_template="/tmp/test") -101 | GCSTaskHandler(filename_template="/tmp/test") - | ^^^^^^^^^^^^^^^^^ AIR301 -102 | -103 | FabAuthManager(None) - | - -AIR301_args.py:103:15: AIR301 `appbuilder` is removed in Airflow 3.0; The constructor takes no parameter now - | -101 | GCSTaskHandler(filename_template="/tmp/test") -102 | -103 | FabAuthManager(None) - | ^^^^^^ AIR301 - | +AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0; The constructor takes no parameter now + | +90 | GCSTaskHandler(filename_template="/tmp/test") +91 | +92 | FabAuthManager(None) + | ^^^^^^ AIR301 + | diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap index 516d464990..7d69175385 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_class_attribute.py.snap @@ -1,29 +1,6 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_class_attribute.py:24:21: AIR301 [*] `airflow.Dataset` is removed in Airflow 3.0 - | -23 | # airflow.Dataset -24 | dataset_from_root = DatasetFromRoot() - | ^^^^^^^^^^^^^^^ AIR301 -25 | dataset_from_root.iter_datasets() -26 | dataset_from_root.iter_dataset_aliases() - | - = help: Use `airflow.sdk.Asset` instead - -ℹ Safe fix -19 19 | from airflow.providers_manager import ProvidersManager -20 20 | from airflow.secrets.base_secrets import BaseSecretsBackend -21 21 | from airflow.secrets.local_filesystem import LocalFilesystemBackend - 22 |+from airflow.sdk import Asset -22 23 | -23 24 | # airflow.Dataset -24 |-dataset_from_root = DatasetFromRoot() - 25 |+dataset_from_root = Asset() -25 26 | dataset_from_root.iter_datasets() -26 27 | dataset_from_root.iter_dataset_aliases() -27 28 | - AIR301_class_attribute.py:25:19: AIR301 [*] `iter_datasets` is removed in Airflow 3.0 | 23 | # airflow.Dataset @@ -65,34 +42,6 @@ AIR301_class_attribute.py:26:19: AIR301 [*] `iter_dataset_aliases` is removed in 28 28 | # airflow.datasets 29 29 | dataset_to_test_method_call = Dataset() -AIR301_class_attribute.py:29:31: AIR301 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0 - | -28 | # airflow.datasets -29 | dataset_to_test_method_call = Dataset() - | ^^^^^^^ AIR301 -30 | dataset_to_test_method_call.iter_datasets() -31 | dataset_to_test_method_call.iter_dataset_aliases() - | - = help: Use `airflow.sdk.Asset` instead - -ℹ Safe fix -19 19 | from airflow.providers_manager import ProvidersManager -20 20 | from airflow.secrets.base_secrets import BaseSecretsBackend -21 21 | from airflow.secrets.local_filesystem import LocalFilesystemBackend - 22 |+from airflow.sdk import Asset -22 23 | -23 24 | # airflow.Dataset -24 25 | dataset_from_root = DatasetFromRoot() --------------------------------------------------------------------------------- -26 27 | dataset_from_root.iter_dataset_aliases() -27 28 | -28 29 | # airflow.datasets -29 |-dataset_to_test_method_call = Dataset() - 30 |+dataset_to_test_method_call = Asset() -30 31 | dataset_to_test_method_call.iter_datasets() -31 32 | dataset_to_test_method_call.iter_dataset_aliases() -32 33 | - AIR301_class_attribute.py:30:29: AIR301 [*] `iter_datasets` is removed in Airflow 3.0 | 28 | # airflow.datasets @@ -134,17 +83,6 @@ AIR301_class_attribute.py:31:29: AIR301 [*] `iter_dataset_aliases` is removed in 33 33 | alias_to_test_method_call = DatasetAlias() 34 34 | alias_to_test_method_call.iter_datasets() -AIR301_class_attribute.py:33:29: AIR301 `airflow.datasets.DatasetAlias` is removed in Airflow 3.0 - | -31 | dataset_to_test_method_call.iter_dataset_aliases() -32 | -33 | alias_to_test_method_call = DatasetAlias() - | ^^^^^^^^^^^^ AIR301 -34 | alias_to_test_method_call.iter_datasets() -35 | alias_to_test_method_call.iter_dataset_aliases() - | - = help: Use `airflow.sdk.AssetAlias` instead - AIR301_class_attribute.py:34:27: AIR301 [*] `iter_datasets` is removed in Airflow 3.0 | 33 | alias_to_test_method_call = DatasetAlias() @@ -185,17 +123,6 @@ AIR301_class_attribute.py:35:27: AIR301 [*] `iter_dataset_aliases` is removed in 37 37 | any_to_test_method_call = DatasetAny() 38 38 | any_to_test_method_call.iter_datasets() -AIR301_class_attribute.py:37:27: AIR301 `airflow.datasets.DatasetAny` is removed in Airflow 3.0 - | -35 | alias_to_test_method_call.iter_dataset_aliases() -36 | -37 | any_to_test_method_call = DatasetAny() - | ^^^^^^^^^^ AIR301 -38 | any_to_test_method_call.iter_datasets() -39 | any_to_test_method_call.iter_dataset_aliases() - | - = help: Use `airflow.sdk.AssetAny` instead - AIR301_class_attribute.py:38:25: AIR301 [*] `iter_datasets` is removed in Airflow 3.0 | 37 | any_to_test_method_call = DatasetAny() diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap index 72e1fe1b5c..0943b18e59 100644 --- a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR301_AIR301_names.py.snap @@ -1,897 +1,741 @@ --- source: crates/ruff_linter/src/rules/airflow/mod.rs --- -AIR301_names.py:91:1: AIR301 `airflow.PY36` is removed in Airflow 3.0 +AIR301_names.py:77:1: AIR301 `airflow.PY36` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:91:7: AIR301 `airflow.PY37` is removed in Airflow 3.0 +AIR301_names.py:77:7: AIR301 `airflow.PY37` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:91:13: AIR301 `airflow.PY38` is removed in Airflow 3.0 +AIR301_names.py:77:13: AIR301 `airflow.PY38` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:91:19: AIR301 `airflow.PY39` is removed in Airflow 3.0 +AIR301_names.py:77:19: AIR301 `airflow.PY39` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:91:25: AIR301 `airflow.PY310` is removed in Airflow 3.0 +AIR301_names.py:77:25: AIR301 `airflow.PY310` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:91:32: AIR301 `airflow.PY311` is removed in Airflow 3.0 +AIR301_names.py:77:32: AIR301 `airflow.PY311` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:91:39: AIR301 `airflow.PY312` is removed in Airflow 3.0 +AIR301_names.py:77:39: AIR301 `airflow.PY312` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 +76 | # airflow root +77 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 | ^^^^^ AIR301 -92 | DatasetFromRoot() +78 | DatasetFromRoot() | = help: Use `sys.version_info` instead -AIR301_names.py:92:1: AIR301 [*] `airflow.Dataset` is removed in Airflow 3.0 +AIR301_names.py:81:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0 | -90 | # airflow root -91 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 -92 | DatasetFromRoot() +80 | # airflow.api_connexion.security +81 | requires_access, requires_access_dataset | ^^^^^^^^^^^^^^^ AIR301 -93 | -94 | # airflow.api_connexion.security - | - = help: Use `airflow.sdk.Asset` instead - -ℹ Safe fix -86 86 | from airflow.utils.trigger_rule import TriggerRule -87 87 | from airflow.www.auth import has_access, has_access_dataset -88 88 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key - 89 |+from airflow.sdk import Asset -89 90 | -90 91 | # airflow root -91 92 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 -92 |-DatasetFromRoot() - 93 |+Asset() -93 94 | -94 95 | # airflow.api_connexion.security -95 96 | requires_access, requires_access_dataset - -AIR301_names.py:95:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0 - | -94 | # airflow.api_connexion.security -95 | requires_access, requires_access_dataset - | ^^^^^^^^^^^^^^^ AIR301 -96 | -97 | # airflow.auth.managers +82 | +83 | # airflow.auth.managers | = help: Use `airflow.api_connexion.security.requires_access_*` instead -AIR301_names.py:95:18: AIR301 [*] `airflow.api_connexion.security.requires_access_dataset` is removed in Airflow 3.0 +AIR301_names.py:81:18: AIR301 [*] `airflow.api_connexion.security.requires_access_dataset` is removed in Airflow 3.0 | -94 | # airflow.api_connexion.security -95 | requires_access, requires_access_dataset +80 | # airflow.api_connexion.security +81 | requires_access, requires_access_dataset | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -96 | -97 | # airflow.auth.managers +82 | +83 | # airflow.auth.managers | = help: Use `airflow.api_connexion.security.requires_access_asset` instead ℹ Safe fix -12 12 | from airflow import ( -13 13 | Dataset as DatasetFromRoot, -14 14 | ) -15 |-from airflow.api_connexion.security import requires_access, requires_access_dataset - 15 |+from airflow.api_connexion.security import requires_access, requires_access_dataset, requires_access_asset -16 16 | from airflow.auth.managers.base_auth_manager import is_authorized_dataset -17 17 | from airflow.auth.managers.models.resource_details import DatasetDetails -18 18 | from airflow.configuration import ( +9 9 | PY311, +10 10 | PY312, +11 11 | ) +12 |-from airflow.api_connexion.security import requires_access, requires_access_dataset + 12 |+from airflow.api_connexion.security import requires_access, requires_access_dataset, requires_access_asset +13 13 | from airflow.auth.managers.base_auth_manager import is_authorized_dataset +14 14 | from airflow.auth.managers.models.resource_details import DatasetDetails +15 15 | from airflow.configuration import ( -------------------------------------------------------------------------------- -92 92 | DatasetFromRoot() -93 93 | -94 94 | # airflow.api_connexion.security -95 |-requires_access, requires_access_dataset - 95 |+requires_access, requires_access_asset -96 96 | -97 97 | # airflow.auth.managers -98 98 | is_authorized_dataset +78 78 | DatasetFromRoot() +79 79 | +80 80 | # airflow.api_connexion.security +81 |-requires_access, requires_access_dataset + 81 |+requires_access, requires_access_asset +82 82 | +83 83 | # airflow.auth.managers +84 84 | is_authorized_dataset -AIR301_names.py:98:1: AIR301 `airflow.auth.managers.base_auth_manager.is_authorized_dataset` is removed in Airflow 3.0 +AIR301_names.py:84:1: AIR301 `airflow.auth.managers.base_auth_manager.is_authorized_dataset` is removed in Airflow 3.0 | -97 | # airflow.auth.managers -98 | is_authorized_dataset +83 | # airflow.auth.managers +84 | is_authorized_dataset | ^^^^^^^^^^^^^^^^^^^^^ AIR301 -99 | DatasetDetails() +85 | DatasetDetails() | = help: Use `airflow.api_fastapi.auth.managers.base_auth_manager.is_authorized_asset` instead -AIR301_names.py:99:1: AIR301 `airflow.auth.managers.models.resource_details.DatasetDetails` is removed in Airflow 3.0 +AIR301_names.py:85:1: AIR301 `airflow.auth.managers.models.resource_details.DatasetDetails` is removed in Airflow 3.0 + | +83 | # airflow.auth.managers +84 | is_authorized_dataset +85 | DatasetDetails() + | ^^^^^^^^^^^^^^ AIR301 +86 | +87 | # airflow.configuration + | + = help: Use `airflow.api_fastapi.auth.managers.models.resource_details.AssetDetails` instead + +AIR301_names.py:88:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^ AIR301 + | + = help: Use `airflow.configuration.conf.get` instead + +AIR301_names.py:88:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^^^ AIR301 + | + = help: Use `airflow.configuration.conf.getboolean` instead + +AIR301_names.py:88:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^ AIR301 + | + = help: Use `airflow.configuration.conf.getfloat` instead + +AIR301_names.py:88:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^ AIR301 + | + = help: Use `airflow.configuration.conf.getint` instead + +AIR301_names.py:88:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^^^ AIR301 + | + = help: Use `airflow.configuration.conf.has_option` instead + +AIR301_names.py:88:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^^^^^^^ AIR301 + | + = help: Use `airflow.configuration.conf.remove_option` instead + +AIR301_names.py:88:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^^^^^ AIR301 + | + = help: Use `airflow.configuration.conf.as_dict` instead + +AIR301_names.py:88:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0 + | +87 | # airflow.configuration +88 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set + | ^^^ AIR301 + | + = help: Use `airflow.configuration.conf.set` instead + +AIR301_names.py:92:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0; The whole `airflow.contrib` module has been removed. + | +91 | # airflow.contrib.* +92 | AWSAthenaHook() + | ^^^^^^^^^^^^^ AIR301 + | + +AIR301_names.py:96:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0 + | +95 | # airflow.datasets +96 | DatasetAliasEvent() + | ^^^^^^^^^^^^^^^^^ AIR301 +97 | +98 | # airflow.datasets.manager + | + +AIR301_names.py:99:1: AIR301 `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0 | - 97 | # airflow.auth.managers - 98 | is_authorized_dataset - 99 | DatasetDetails() + 98 | # airflow.datasets.manager + 99 | DatasetManager() | ^^^^^^^^^^^^^^ AIR301 -100 | -101 | # airflow.configuration - | - = help: Use `airflow.api_fastapi.auth.managers.models.resource_details.AssetDetails` instead - -AIR301_names.py:102:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^ AIR301 - | - = help: Use `airflow.configuration.conf.get` instead - -AIR301_names.py:102:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^^^ AIR301 - | - = help: Use `airflow.configuration.conf.getboolean` instead - -AIR301_names.py:102:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^ AIR301 - | - = help: Use `airflow.configuration.conf.getfloat` instead - -AIR301_names.py:102:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^ AIR301 - | - = help: Use `airflow.configuration.conf.getint` instead - -AIR301_names.py:102:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^^^ AIR301 - | - = help: Use `airflow.configuration.conf.has_option` instead - -AIR301_names.py:102:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^^^^^^^ AIR301 - | - = help: Use `airflow.configuration.conf.remove_option` instead - -AIR301_names.py:102:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^^^^^ AIR301 - | - = help: Use `airflow.configuration.conf.as_dict` instead - -AIR301_names.py:102:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0 - | -101 | # airflow.configuration -102 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set - | ^^^ AIR301 - | - = help: Use `airflow.configuration.conf.set` instead - -AIR301_names.py:106:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0; The whole `airflow.contrib` module has been removed. - | -105 | # airflow.contrib.* -106 | AWSAthenaHook() - | ^^^^^^^^^^^^^ AIR301 -107 | -108 | # airflow.datasets - | - -AIR301_names.py:109:1: AIR301 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0 - | -108 | # airflow.datasets -109 | Dataset() - | ^^^^^^^ AIR301 -110 | DatasetAlias() -111 | DatasetAliasEvent() - | - = help: Use `airflow.sdk.Asset` instead - -ℹ Safe fix -86 86 | from airflow.utils.trigger_rule import TriggerRule -87 87 | from airflow.www.auth import has_access, has_access_dataset -88 88 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key - 89 |+from airflow.sdk import Asset -89 90 | -90 91 | # airflow root -91 92 | PY36, PY37, PY38, PY39, PY310, PY311, PY312 --------------------------------------------------------------------------------- -106 107 | AWSAthenaHook() -107 108 | -108 109 | # airflow.datasets -109 |-Dataset() - 110 |+Asset() -110 111 | DatasetAlias() -111 112 | DatasetAliasEvent() -112 113 | DatasetAll() - -AIR301_names.py:110:1: AIR301 `airflow.datasets.DatasetAlias` is removed in Airflow 3.0 - | -108 | # airflow.datasets -109 | Dataset() -110 | DatasetAlias() - | ^^^^^^^^^^^^ AIR301 -111 | DatasetAliasEvent() -112 | DatasetAll() - | - = help: Use `airflow.sdk.AssetAlias` instead - -AIR301_names.py:111:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0 - | -109 | Dataset() -110 | DatasetAlias() -111 | DatasetAliasEvent() - | ^^^^^^^^^^^^^^^^^ AIR301 -112 | DatasetAll() -113 | DatasetAny() - | - -AIR301_names.py:112:1: AIR301 `airflow.datasets.DatasetAll` is removed in Airflow 3.0 - | -110 | DatasetAlias() -111 | DatasetAliasEvent() -112 | DatasetAll() - | ^^^^^^^^^^ AIR301 -113 | DatasetAny() -114 | Metadata() - | - = help: Use `airflow.sdk.AssetAll` instead - -AIR301_names.py:113:1: AIR301 `airflow.datasets.DatasetAny` is removed in Airflow 3.0 - | -111 | DatasetAliasEvent() -112 | DatasetAll() -113 | DatasetAny() - | ^^^^^^^^^^ AIR301 -114 | Metadata() -115 | expand_alias_to_datasets - | - = help: Use `airflow.sdk.AssetAny` instead - -AIR301_names.py:114:1: AIR301 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0 - | -112 | DatasetAll() -113 | DatasetAny() -114 | Metadata() - | ^^^^^^^^ AIR301 -115 | expand_alias_to_datasets - | - = help: Use `airflow.sdk.Metadata` instead - -AIR301_names.py:115:1: AIR301 `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0 - | -113 | DatasetAny() -114 | Metadata() -115 | expand_alias_to_datasets - | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -116 | -117 | # airflow.datasets.manager - | - = help: Use `airflow.sdk.expand_alias_to_assets` instead - -AIR301_names.py:118:1: AIR301 `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0 - | -117 | # airflow.datasets.manager -118 | DatasetManager() - | ^^^^^^^^^^^^^^ AIR301 -119 | dataset_manager -120 | resolve_dataset_manager +100 | dataset_manager +101 | resolve_dataset_manager | = help: Use `airflow.assets.manager.AssetManager` instead -AIR301_names.py:119:1: AIR301 `airflow.datasets.manager.dataset_manager` is removed in Airflow 3.0 +AIR301_names.py:100:1: AIR301 `airflow.datasets.manager.dataset_manager` is removed in Airflow 3.0 | -117 | # airflow.datasets.manager -118 | DatasetManager() -119 | dataset_manager + 98 | # airflow.datasets.manager + 99 | DatasetManager() +100 | dataset_manager | ^^^^^^^^^^^^^^^ AIR301 -120 | resolve_dataset_manager +101 | resolve_dataset_manager | = help: Use `airflow.assets.manager.asset_manager` instead -AIR301_names.py:120:1: AIR301 `airflow.datasets.manager.resolve_dataset_manager` is removed in Airflow 3.0 +AIR301_names.py:101:1: AIR301 `airflow.datasets.manager.resolve_dataset_manager` is removed in Airflow 3.0 | -118 | DatasetManager() -119 | dataset_manager -120 | resolve_dataset_manager + 99 | DatasetManager() +100 | dataset_manager +101 | resolve_dataset_manager | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -121 | -122 | # airflow.hooks +102 | +103 | # airflow.hooks | = help: Use `airflow.assets.resolve_asset_manager` instead -AIR301_names.py:123:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 +AIR301_names.py:104:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0 | -122 | # airflow.hooks -123 | BaseHook() +103 | # airflow.hooks +104 | BaseHook() | ^^^^^^^^ AIR301 -124 | -125 | # airflow.lineage.hook +105 | +106 | # airflow.lineage.hook | = help: Use `airflow.hooks.base.BaseHook` instead -AIR301_names.py:126:1: AIR301 `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0 +AIR301_names.py:107:1: AIR301 `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0 | -125 | # airflow.lineage.hook -126 | DatasetLineageInfo() +106 | # airflow.lineage.hook +107 | DatasetLineageInfo() | ^^^^^^^^^^^^^^^^^^ AIR301 -127 | -128 | # airflow.listeners.spec.dataset +108 | +109 | # airflow.listeners.spec.dataset | = help: Use `airflow.lineage.hook.AssetLineageInfo` instead -AIR301_names.py:129:1: AIR301 `airflow.listeners.spec.dataset.on_dataset_changed` is removed in Airflow 3.0 +AIR301_names.py:110:1: AIR301 `airflow.listeners.spec.dataset.on_dataset_changed` is removed in Airflow 3.0 | -128 | # airflow.listeners.spec.dataset -129 | on_dataset_changed +109 | # airflow.listeners.spec.dataset +110 | on_dataset_changed | ^^^^^^^^^^^^^^^^^^ AIR301 -130 | on_dataset_created +111 | on_dataset_created | = help: Use `airflow.listeners.spec.asset.on_asset_changed` instead -AIR301_names.py:130:1: AIR301 `airflow.listeners.spec.dataset.on_dataset_created` is removed in Airflow 3.0 +AIR301_names.py:111:1: AIR301 `airflow.listeners.spec.dataset.on_dataset_created` is removed in Airflow 3.0 | -128 | # airflow.listeners.spec.dataset -129 | on_dataset_changed -130 | on_dataset_created +109 | # airflow.listeners.spec.dataset +110 | on_dataset_changed +111 | on_dataset_created | ^^^^^^^^^^^^^^^^^^ AIR301 -131 | -132 | # airflow.metrics.validators +112 | +113 | # airflow.metrics.validators | = help: Use `airflow.listeners.spec.asset.on_asset_created` instead -AIR301_names.py:133:1: AIR301 `airflow.metrics.validators.AllowListValidator` is removed in Airflow 3.0 +AIR301_names.py:114:1: AIR301 `airflow.metrics.validators.AllowListValidator` is removed in Airflow 3.0 | -132 | # airflow.metrics.validators -133 | AllowListValidator() +113 | # airflow.metrics.validators +114 | AllowListValidator() | ^^^^^^^^^^^^^^^^^^ AIR301 -134 | BlockListValidator() +115 | BlockListValidator() | = help: Use `airflow.metrics.validators.PatternAllowListValidator` instead -AIR301_names.py:134:1: AIR301 `airflow.metrics.validators.BlockListValidator` is removed in Airflow 3.0 +AIR301_names.py:115:1: AIR301 `airflow.metrics.validators.BlockListValidator` is removed in Airflow 3.0 | -132 | # airflow.metrics.validators -133 | AllowListValidator() -134 | BlockListValidator() +113 | # airflow.metrics.validators +114 | AllowListValidator() +115 | BlockListValidator() | ^^^^^^^^^^^^^^^^^^ AIR301 | = help: Use `airflow.metrics.validators.PatternBlockListValidator` instead -AIR301_names.py:138:1: AIR301 `airflow.models.baseoperator.chain` is removed in Airflow 3.0 +AIR301_names.py:138:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0; The whole `airflow.subdag` module has been removed. | -137 | # airflow.models.baseoperator -138 | chain, chain_linear, cross_downstream - | ^^^^^ AIR301 - | - = help: Use `airflow.sdk.chain` instead - -AIR301_names.py:138:8: AIR301 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0 - | -137 | # airflow.models.baseoperator -138 | chain, chain_linear, cross_downstream - | ^^^^^^^^^^^^ AIR301 - | - = help: Use `airflow.sdk.chain_linear` instead - -AIR301_names.py:138:22: AIR301 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0 - | -137 | # airflow.models.baseoperator -138 | chain, chain_linear, cross_downstream - | ^^^^^^^^^^^^^^^^ AIR301 - | - = help: Use `airflow.sdk.cross_downstream` instead - -AIR301_names.py:161:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0; The whole `airflow.subdag` module has been removed. - | -160 | # airflow.operators.subdag.* -161 | SubDagOperator() +137 | # airflow.operators.subdag.* +138 | SubDagOperator() | ^^^^^^^^^^^^^^ AIR301 -162 | -163 | # airflow.providers.amazon +139 | +140 | # airflow.providers.amazon | -AIR301_names.py:164:13: AIR301 `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.DATASET` is removed in Airflow 3.0 +AIR301_names.py:141:13: AIR301 `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.DATASET` is removed in Airflow 3.0 | -163 | # airflow.providers.amazon -164 | AvpEntities.DATASET +140 | # airflow.providers.amazon +141 | AvpEntities.DATASET | ^^^^^^^ AIR301 -165 | s3.create_dataset -166 | s3.convert_dataset_to_openlineage +142 | s3.create_dataset +143 | s3.convert_dataset_to_openlineage | = help: Use `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.ASSET` instead -AIR301_names.py:165:4: AIR301 `airflow.providers.amazon.aws.datasets.s3.create_dataset` is removed in Airflow 3.0 +AIR301_names.py:142:4: AIR301 `airflow.providers.amazon.aws.datasets.s3.create_dataset` is removed in Airflow 3.0 | -163 | # airflow.providers.amazon -164 | AvpEntities.DATASET -165 | s3.create_dataset +140 | # airflow.providers.amazon +141 | AvpEntities.DATASET +142 | s3.create_dataset | ^^^^^^^^^^^^^^ AIR301 -166 | s3.convert_dataset_to_openlineage -167 | s3.sanitize_uri +143 | s3.convert_dataset_to_openlineage +144 | s3.sanitize_uri | = help: Use `airflow.providers.amazon.aws.assets.s3.create_asset` instead -AIR301_names.py:166:4: AIR301 `airflow.providers.amazon.aws.datasets.s3.convert_dataset_to_openlineage` is removed in Airflow 3.0 +AIR301_names.py:143:4: AIR301 `airflow.providers.amazon.aws.datasets.s3.convert_dataset_to_openlineage` is removed in Airflow 3.0 | -164 | AvpEntities.DATASET -165 | s3.create_dataset -166 | s3.convert_dataset_to_openlineage +141 | AvpEntities.DATASET +142 | s3.create_dataset +143 | s3.convert_dataset_to_openlineage | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -167 | s3.sanitize_uri +144 | s3.sanitize_uri | = help: Use `airflow.providers.amazon.aws.assets.s3.convert_asset_to_openlineage` instead -AIR301_names.py:167:4: AIR301 `airflow.providers.amazon.aws.datasets.s3.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:144:4: AIR301 `airflow.providers.amazon.aws.datasets.s3.sanitize_uri` is removed in Airflow 3.0 | -165 | s3.create_dataset -166 | s3.convert_dataset_to_openlineage -167 | s3.sanitize_uri +142 | s3.create_dataset +143 | s3.convert_dataset_to_openlineage +144 | s3.sanitize_uri | ^^^^^^^^^^^^ AIR301 -168 | -169 | # airflow.providers.common.io +145 | +146 | # airflow.providers.common.io | = help: Use `airflow.providers.amazon.aws.assets.s3.sanitize_uri` instead -AIR301_names.py:170:16: AIR301 `airflow.providers.common.io.datasets.file.convert_dataset_to_openlineage` is removed in Airflow 3.0 +AIR301_names.py:147:16: AIR301 `airflow.providers.common.io.datasets.file.convert_dataset_to_openlineage` is removed in Airflow 3.0 | -169 | # airflow.providers.common.io -170 | common_io_file.convert_dataset_to_openlineage +146 | # airflow.providers.common.io +147 | common_io_file.convert_dataset_to_openlineage | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -171 | common_io_file.create_dataset -172 | common_io_file.sanitize_uri +148 | common_io_file.create_dataset +149 | common_io_file.sanitize_uri | = help: Use `airflow.providers.common.io.assets.file.convert_asset_to_openlineage` instead -AIR301_names.py:171:16: AIR301 `airflow.providers.common.io.datasets.file.create_dataset` is removed in Airflow 3.0 +AIR301_names.py:148:16: AIR301 `airflow.providers.common.io.datasets.file.create_dataset` is removed in Airflow 3.0 | -169 | # airflow.providers.common.io -170 | common_io_file.convert_dataset_to_openlineage -171 | common_io_file.create_dataset +146 | # airflow.providers.common.io +147 | common_io_file.convert_dataset_to_openlineage +148 | common_io_file.create_dataset | ^^^^^^^^^^^^^^ AIR301 -172 | common_io_file.sanitize_uri +149 | common_io_file.sanitize_uri | = help: Use `airflow.providers.common.io.assets.file.create_asset` instead -AIR301_names.py:172:16: AIR301 `airflow.providers.common.io.datasets.file.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:149:16: AIR301 `airflow.providers.common.io.datasets.file.sanitize_uri` is removed in Airflow 3.0 | -170 | common_io_file.convert_dataset_to_openlineage -171 | common_io_file.create_dataset -172 | common_io_file.sanitize_uri +147 | common_io_file.convert_dataset_to_openlineage +148 | common_io_file.create_dataset +149 | common_io_file.sanitize_uri | ^^^^^^^^^^^^ AIR301 -173 | -174 | # airflow.providers.fab +150 | +151 | # airflow.providers.fab | = help: Use `airflow.providers.common.io.assets.file.sanitize_uri` instead -AIR301_names.py:175:18: AIR301 `airflow.providers.fab.auth_manager.fab_auth_manager.is_authorized_dataset` is removed in Airflow 3.0 +AIR301_names.py:152:18: AIR301 `airflow.providers.fab.auth_manager.fab_auth_manager.is_authorized_dataset` is removed in Airflow 3.0 | -174 | # airflow.providers.fab -175 | fab_auth_manager.is_authorized_dataset +151 | # airflow.providers.fab +152 | fab_auth_manager.is_authorized_dataset | ^^^^^^^^^^^^^^^^^^^^^ AIR301 -176 | -177 | # airflow.providers.google +153 | +154 | # airflow.providers.google | = help: Use `airflow.providers.fab.auth_manager.fab_auth_manager.is_authorized_asset` instead -AIR301_names.py:180:5: AIR301 `airflow.providers.google.datasets.gcs.create_dataset` is removed in Airflow 3.0 +AIR301_names.py:157:5: AIR301 `airflow.providers.google.datasets.gcs.create_dataset` is removed in Airflow 3.0 | -178 | bigquery.sanitize_uri -179 | -180 | gcs.create_dataset +155 | bigquery.sanitize_uri +156 | +157 | gcs.create_dataset | ^^^^^^^^^^^^^^ AIR301 -181 | gcs.sanitize_uri -182 | gcs.convert_dataset_to_openlineage +158 | gcs.sanitize_uri +159 | gcs.convert_dataset_to_openlineage | = help: Use `airflow.providers.google.assets.gcs.create_asset` instead -AIR301_names.py:181:5: AIR301 `airflow.providers.google.datasets.gcs.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:158:5: AIR301 `airflow.providers.google.datasets.gcs.sanitize_uri` is removed in Airflow 3.0 | -180 | gcs.create_dataset -181 | gcs.sanitize_uri +157 | gcs.create_dataset +158 | gcs.sanitize_uri | ^^^^^^^^^^^^ AIR301 -182 | gcs.convert_dataset_to_openlineage +159 | gcs.convert_dataset_to_openlineage | = help: Use `airflow.providers.google.assets.gcs.sanitize_uri` instead -AIR301_names.py:182:5: AIR301 `airflow.providers.google.datasets.gcs.convert_dataset_to_openlineage` is removed in Airflow 3.0 +AIR301_names.py:159:5: AIR301 `airflow.providers.google.datasets.gcs.convert_dataset_to_openlineage` is removed in Airflow 3.0 | -180 | gcs.create_dataset -181 | gcs.sanitize_uri -182 | gcs.convert_dataset_to_openlineage +157 | gcs.create_dataset +158 | gcs.sanitize_uri +159 | gcs.convert_dataset_to_openlineage | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -183 | -184 | # airflow.providers.mysql +160 | +161 | # airflow.providers.mysql | = help: Use `airflow.providers.google.assets.gcs.convert_asset_to_openlineage` instead -AIR301_names.py:185:7: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:162:7: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0 | -184 | # airflow.providers.mysql -185 | mysql.sanitize_uri +161 | # airflow.providers.mysql +162 | mysql.sanitize_uri | ^^^^^^^^^^^^ AIR301 -186 | -187 | # airflow.providers.openlineage +163 | +164 | # airflow.providers.openlineage | = help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead -AIR301_names.py:188:1: AIR301 `airflow.providers.openlineage.utils.utils.DatasetInfo` is removed in Airflow 3.0 +AIR301_names.py:165:1: AIR301 `airflow.providers.openlineage.utils.utils.DatasetInfo` is removed in Airflow 3.0 | -187 | # airflow.providers.openlineage -188 | DatasetInfo() +164 | # airflow.providers.openlineage +165 | DatasetInfo() | ^^^^^^^^^^^ AIR301 -189 | translate_airflow_dataset +166 | translate_airflow_dataset | = help: Use `airflow.providers.openlineage.utils.utils.AssetInfo` instead -AIR301_names.py:189:1: AIR301 `airflow.providers.openlineage.utils.utils.translate_airflow_dataset` is removed in Airflow 3.0 +AIR301_names.py:166:1: AIR301 `airflow.providers.openlineage.utils.utils.translate_airflow_dataset` is removed in Airflow 3.0 | -187 | # airflow.providers.openlineage -188 | DatasetInfo() -189 | translate_airflow_dataset +164 | # airflow.providers.openlineage +165 | DatasetInfo() +166 | translate_airflow_dataset | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -190 | -191 | # airflow.providers.postgres +167 | +168 | # airflow.providers.postgres | = help: Use `airflow.providers.openlineage.utils.utils.translate_airflow_asset` instead -AIR301_names.py:192:10: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:169:10: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0 | -191 | # airflow.providers.postgres -192 | postgres.sanitize_uri +168 | # airflow.providers.postgres +169 | postgres.sanitize_uri | ^^^^^^^^^^^^ AIR301 -193 | -194 | # airflow.providers.trino +170 | +171 | # airflow.providers.trino | = help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead -AIR301_names.py:195:7: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 +AIR301_names.py:172:7: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0 | -194 | # airflow.providers.trino -195 | trino.sanitize_uri +171 | # airflow.providers.trino +172 | trino.sanitize_uri | ^^^^^^^^^^^^ AIR301 -196 | -197 | # airflow.secrets +173 | +174 | # airflow.secrets | = help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead -AIR301_names.py:200:1: AIR301 `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0 +AIR301_names.py:177:1: AIR301 `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0 | -198 | # get_connection -199 | LocalFilesystemBackend() -200 | load_connections +175 | # get_connection +176 | LocalFilesystemBackend() +177 | load_connections | ^^^^^^^^^^^^^^^^ AIR301 -201 | -202 | # airflow.security.permissions +178 | +179 | # airflow.security.permissions | = help: Use `airflow.secrets.local_filesystem.load_connections_dict` instead -AIR301_names.py:203:1: AIR301 `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0 +AIR301_names.py:180:1: AIR301 `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0 | -202 | # airflow.security.permissions -203 | RESOURCE_DATASET +179 | # airflow.security.permissions +180 | RESOURCE_DATASET | ^^^^^^^^^^^^^^^^ AIR301 -204 | -205 | # airflow.sensors.base_sensor_operator +181 | +182 | # airflow.sensors.base_sensor_operator | = help: Use `airflow.security.permissions.RESOURCE_ASSET` instead -AIR301_names.py:206:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0 +AIR301_names.py:183:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0 | -205 | # airflow.sensors.base_sensor_operator -206 | BaseSensorOperator() +182 | # airflow.sensors.base_sensor_operator +183 | BaseSensorOperator() | ^^^^^^^^^^^^^^^^^^ AIR301 | = help: Use `airflow.sdk.bases.sensor.BaseSensorOperator` instead -AIR301_names.py:210:1: AIR301 `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0 +AIR301_names.py:187:1: AIR301 `airflow.timetables.simple.DatasetTriggeredTimetable` is removed in Airflow 3.0 | -209 | # airflow.timetables -210 | DatasetOrTimeSchedule() - | ^^^^^^^^^^^^^^^^^^^^^ AIR301 -211 | DatasetTriggeredTimetable() - | - = help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead - -AIR301_names.py:211:1: AIR301 `airflow.timetables.simple.DatasetTriggeredTimetable` is removed in Airflow 3.0 - | -209 | # airflow.timetables -210 | DatasetOrTimeSchedule() -211 | DatasetTriggeredTimetable() +186 | # airflow.timetables +187 | DatasetTriggeredTimetable() | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -212 | -213 | # airflow.triggers.external_task +188 | +189 | # airflow.triggers.external_task | = help: Use `airflow.timetables.simple.AssetTriggeredTimetable` instead -AIR301_names.py:214:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 +AIR301_names.py:190:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0 | -213 | # airflow.triggers.external_task -214 | TaskStateTrigger() +189 | # airflow.triggers.external_task +190 | TaskStateTrigger() | ^^^^^^^^^^^^^^^^ AIR301 -215 | -216 | # airflow.utils.date +191 | +192 | # airflow.utils.date | -AIR301_names.py:217:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 +AIR301_names.py:193:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 | -216 | # airflow.utils.date -217 | dates.date_range +192 | # airflow.utils.date +193 | dates.date_range | ^^^^^^^^^^ AIR301 -218 | dates.days_ago +194 | dates.days_ago | -AIR301_names.py:218:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 +AIR301_names.py:194:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 | -216 | # airflow.utils.date -217 | dates.date_range -218 | dates.days_ago +192 | # airflow.utils.date +193 | dates.date_range +194 | dates.days_ago | ^^^^^^^^ AIR301 -219 | -220 | date_range +195 | +196 | date_range | = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:220:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 +AIR301_names.py:196:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0 | -218 | dates.days_ago -219 | -220 | date_range +194 | dates.days_ago +195 | +196 | date_range | ^^^^^^^^^^ AIR301 -221 | days_ago -222 | infer_time_unit +197 | days_ago +198 | infer_time_unit | -AIR301_names.py:221:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 +AIR301_names.py:197:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0 | -220 | date_range -221 | days_ago +196 | date_range +197 | days_ago | ^^^^^^^^ AIR301 -222 | infer_time_unit -223 | parse_execution_date +198 | infer_time_unit +199 | parse_execution_date | = help: Use `pendulum.today('UTC').add(days=-N, ...)` instead -AIR301_names.py:222:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0 +AIR301_names.py:198:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0 | -220 | date_range -221 | days_ago -222 | infer_time_unit +196 | date_range +197 | days_ago +198 | infer_time_unit | ^^^^^^^^^^^^^^^ AIR301 -223 | parse_execution_date -224 | round_time +199 | parse_execution_date +200 | round_time | -AIR301_names.py:223:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0 +AIR301_names.py:199:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0 | -221 | days_ago -222 | infer_time_unit -223 | parse_execution_date +197 | days_ago +198 | infer_time_unit +199 | parse_execution_date | ^^^^^^^^^^^^^^^^^^^^ AIR301 -224 | round_time -225 | scale_time_units +200 | round_time +201 | scale_time_units | -AIR301_names.py:224:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0 +AIR301_names.py:200:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0 | -222 | infer_time_unit -223 | parse_execution_date -224 | round_time +198 | infer_time_unit +199 | parse_execution_date +200 | round_time | ^^^^^^^^^^ AIR301 -225 | scale_time_units +201 | scale_time_units | -AIR301_names.py:225:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0 +AIR301_names.py:201:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0 | -223 | parse_execution_date -224 | round_time -225 | scale_time_units +199 | parse_execution_date +200 | round_time +201 | scale_time_units | ^^^^^^^^^^^^^^^^ AIR301 -226 | -227 | # This one was not deprecated. +202 | +203 | # This one was not deprecated. | -AIR301_names.py:232:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0 +AIR301_names.py:208:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0 | -231 | # airflow.utils.dag_cycle_tester -232 | test_cycle +207 | # airflow.utils.dag_cycle_tester +208 | test_cycle | ^^^^^^^^^^ AIR301 -233 | -234 | # airflow.utils.dag_parsing_context | -AIR301_names.py:235:1: AIR301 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0 +AIR301_names.py:212:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0 | -234 | # airflow.utils.dag_parsing_context -235 | get_parsing_context - | ^^^^^^^^^^^^^^^^^^^ AIR301 -236 | -237 | # airflow.utils.db - | - = help: Use `airflow.sdk.get_parsing_context` instead - -AIR301_names.py:238:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0 - | -237 | # airflow.utils.db -238 | create_session +211 | # airflow.utils.db +212 | create_session | ^^^^^^^^^^^^^^ AIR301 -239 | -240 | # airflow.utils.decorators +213 | +214 | # airflow.utils.decorators | -AIR301_names.py:241:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0; `apply_defaults` is now unconditionally done and can be safely removed. +AIR301_names.py:215:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0; `apply_defaults` is now unconditionally done and can be safely removed. | -240 | # airflow.utils.decorators -241 | apply_defaults +214 | # airflow.utils.decorators +215 | apply_defaults | ^^^^^^^^^^^^^^ AIR301 -242 | -243 | # airflow.utils.file +216 | +217 | # airflow.utils.file | -AIR301_names.py:244:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0 +AIR301_names.py:218:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0 | -243 | # airflow.utils.file -244 | TemporaryDirectory() +217 | # airflow.utils.file +218 | TemporaryDirectory() | ^^^^^^^^^^^^^^^^^^ AIR301 -245 | mkdirs +219 | mkdirs | = help: Use `tempfile.TemporaryDirectory` instead -AIR301_names.py:245:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 +AIR301_names.py:219:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0 | -243 | # airflow.utils.file -244 | TemporaryDirectory() -245 | mkdirs +217 | # airflow.utils.file +218 | TemporaryDirectory() +219 | mkdirs | ^^^^^^ AIR301 -246 | -247 | # airflow.utils.helpers +220 | +221 | # airflow.utils.helpers | = help: Use `pathlib.Path({path}).mkdir` instead -AIR301_names.py:248:1: AIR301 `airflow.utils.helpers.chain` is removed in Airflow 3.0 +AIR301_names.py:222:1: AIR301 `airflow.utils.helpers.chain` is removed in Airflow 3.0 | -247 | # airflow.utils.helpers -248 | helper_chain +221 | # airflow.utils.helpers +222 | helper_chain | ^^^^^^^^^^^^ AIR301 -249 | helper_cross_downstream +223 | helper_cross_downstream | = help: Use `airflow.sdk.chain` instead -AIR301_names.py:249:1: AIR301 `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0 +AIR301_names.py:223:1: AIR301 `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0 | -247 | # airflow.utils.helpers -248 | helper_chain -249 | helper_cross_downstream +221 | # airflow.utils.helpers +222 | helper_chain +223 | helper_cross_downstream | ^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -250 | -251 | # airflow.utils.log +224 | +225 | # airflow.utils.log | = help: Use `airflow.sdk.cross_downstream` instead -AIR301_names.py:252:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0 +AIR301_names.py:226:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0 | -251 | # airflow.utils.log -252 | secrets_masker +225 | # airflow.utils.log +226 | secrets_masker | ^^^^^^^^^^^^^^ AIR301 -253 | -254 | # airflow.utils.state +227 | +228 | # airflow.utils.state | = help: Use `airflow.sdk.execution_time.secrets_masker` instead -AIR301_names.py:255:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 +AIR301_names.py:229:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0 | -254 | # airflow.utils.state -255 | SHUTDOWN +228 | # airflow.utils.state +229 | SHUTDOWN | ^^^^^^^^ AIR301 -256 | terminating_states +230 | terminating_states | -AIR301_names.py:256:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0 +AIR301_names.py:230:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0 | -254 | # airflow.utils.state -255 | SHUTDOWN -256 | terminating_states +228 | # airflow.utils.state +229 | SHUTDOWN +230 | terminating_states | ^^^^^^^^^^^^^^^^^^ AIR301 -257 | -258 | # airflow.utils.trigger_rule +231 | +232 | # airflow.utils.trigger_rule | -AIR301_names.py:259:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0 +AIR301_names.py:233:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0 | -258 | # airflow.utils.trigger_rule -259 | TriggerRule.DUMMY +232 | # airflow.utils.trigger_rule +233 | TriggerRule.DUMMY | ^^^^^ AIR301 -260 | TriggerRule.NONE_FAILED_OR_SKIPPED +234 | TriggerRule.NONE_FAILED_OR_SKIPPED | -AIR301_names.py:260:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0 +AIR301_names.py:234:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0 | -258 | # airflow.utils.trigger_rule -259 | TriggerRule.DUMMY -260 | TriggerRule.NONE_FAILED_OR_SKIPPED +232 | # airflow.utils.trigger_rule +233 | TriggerRule.DUMMY +234 | TriggerRule.NONE_FAILED_OR_SKIPPED | ^^^^^^^^^^^^^^^^^^^^^^ AIR301 -261 | -262 | # airflow.www.auth +235 | +236 | # airflow.www.auth | -AIR301_names.py:263:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0 +AIR301_names.py:237:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0 | -262 | # airflow.www.auth -263 | has_access +236 | # airflow.www.auth +237 | has_access | ^^^^^^^^^^ AIR301 -264 | has_access_dataset +238 | has_access_dataset | = help: Use `airflow.www.auth.has_access_*` instead -AIR301_names.py:264:1: AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0 +AIR301_names.py:238:1: AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0 | -262 | # airflow.www.auth -263 | has_access -264 | has_access_dataset +236 | # airflow.www.auth +237 | has_access +238 | has_access_dataset | ^^^^^^^^^^^^^^^^^^ AIR301 -265 | -266 | # airflow.www.utils +239 | +240 | # airflow.www.utils | = help: Use `airflow.www.auth.has_access_dataset` instead -AIR301_names.py:267:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0 +AIR301_names.py:241:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0 | -266 | # airflow.www.utils -267 | get_sensitive_variables_fields +240 | # airflow.www.utils +241 | get_sensitive_variables_fields | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 -268 | should_hide_value_for_key +242 | should_hide_value_for_key | = help: Use `airflow.utils.log.secrets_masker.get_sensitive_variables_fields` instead -AIR301_names.py:268:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0 +AIR301_names.py:242:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0 | -266 | # airflow.www.utils -267 | get_sensitive_variables_fields -268 | should_hide_value_for_key +240 | # airflow.www.utils +241 | get_sensitive_variables_fields +242 | should_hide_value_for_key | ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301 | = help: Use `airflow.utils.log.secrets_masker.should_hide_value_for_key` instead diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_args.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_args.py.snap new file mode 100644 index 0000000000..2637a92e53 --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_args.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +--- +AIR311_args.py:13:34: AIR311 `sla_miss_callback` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +13 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback) + | ^^^^^^^^^^^^^^^^^ AIR311 + | + +AIR311_args.py:16:6: AIR311 `sla_miss_callback` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +16 | @dag(sla_miss_callback=sla_callback) + | ^^^^^^^^^^^^^^^^^ AIR311 +17 | def decorator_sla_callback(): +18 | pass + | + +AIR311_args.py:25:9: AIR311 `sla` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +23 | branch_dt_op2 = BranchDateTimeOperator( +24 | task_id="branch_dt_op2", +25 | sla=timedelta(seconds=10), + | ^^^ AIR311 +26 | ) + | diff --git a/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap new file mode 100644 index 0000000000..9b4597539e --- /dev/null +++ b/crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap @@ -0,0 +1,153 @@ +--- +source: crates/ruff_linter/src/rules/airflow/mod.rs +--- +AIR311_names.py:17:1: AIR311 [*] `airflow.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +15 | from airflow.utils.dag_parsing_context import get_parsing_context +16 | +17 | DatasetFromRoot() + | ^^^^^^^^^^^^^^^ AIR311 +18 | +19 | # airflow.datasets + | + = help: Use `airflow.sdk.Asset` instead + +ℹ Safe fix +13 13 | from airflow.models.baseoperatorlink import BaseOperatorLink +14 14 | from airflow.timetables.datasets import DatasetOrTimeSchedule +15 15 | from airflow.utils.dag_parsing_context import get_parsing_context + 16 |+from airflow.sdk import Asset +16 17 | +17 |-DatasetFromRoot() + 18 |+Asset() +18 19 | +19 20 | # airflow.datasets +20 21 | Dataset() + +AIR311_names.py:20:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +19 | # airflow.datasets +20 | Dataset() + | ^^^^^^^ AIR311 +21 | DatasetAlias() +22 | DatasetAll() + | + = help: Use `airflow.sdk.Asset` instead + +ℹ Safe fix +13 13 | from airflow.models.baseoperatorlink import BaseOperatorLink +14 14 | from airflow.timetables.datasets import DatasetOrTimeSchedule +15 15 | from airflow.utils.dag_parsing_context import get_parsing_context + 16 |+from airflow.sdk import Asset +16 17 | +17 18 | DatasetFromRoot() +18 19 | +19 20 | # airflow.datasets +20 |-Dataset() + 21 |+Asset() +21 22 | DatasetAlias() +22 23 | DatasetAll() +23 24 | DatasetAny() + +AIR311_names.py:21:1: AIR311 `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +19 | # airflow.datasets +20 | Dataset() +21 | DatasetAlias() + | ^^^^^^^^^^^^ AIR311 +22 | DatasetAll() +23 | DatasetAny() + | + = help: Use `airflow.sdk.AssetAlias` instead + +AIR311_names.py:22:1: AIR311 `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +20 | Dataset() +21 | DatasetAlias() +22 | DatasetAll() + | ^^^^^^^^^^ AIR311 +23 | DatasetAny() +24 | Metadata() + | + = help: Use `airflow.sdk.AssetAll` instead + +AIR311_names.py:23:1: AIR311 `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +21 | DatasetAlias() +22 | DatasetAll() +23 | DatasetAny() + | ^^^^^^^^^^ AIR311 +24 | Metadata() +25 | expand_alias_to_datasets() + | + = help: Use `airflow.sdk.AssetAny` instead + +AIR311_names.py:24:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +22 | DatasetAll() +23 | DatasetAny() +24 | Metadata() + | ^^^^^^^^ AIR311 +25 | expand_alias_to_datasets() + | + = help: Use `airflow.sdk.Metadata` instead + +AIR311_names.py:25:1: AIR311 `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +23 | DatasetAny() +24 | Metadata() +25 | expand_alias_to_datasets() + | ^^^^^^^^^^^^^^^^^^^^^^^^ AIR311 +26 | +27 | # airflow.models.baseoperator + | + = help: Use `airflow.sdk.expand_alias_to_assets` instead + +AIR311_names.py:28:1: AIR311 `airflow.models.baseoperator.chain` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +27 | # airflow.models.baseoperator +28 | chain() + | ^^^^^ AIR311 +29 | chain_linear() +30 | cross_downstream() + | + = help: Use `airflow.sdk.chain` instead + +AIR311_names.py:29:1: AIR311 `airflow.models.baseoperator.chain_linear` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +27 | # airflow.models.baseoperator +28 | chain() +29 | chain_linear() + | ^^^^^^^^^^^^ AIR311 +30 | cross_downstream() + | + = help: Use `airflow.sdk.chain_linear` instead + +AIR311_names.py:30:1: AIR311 `airflow.models.baseoperator.cross_downstream` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +28 | chain() +29 | chain_linear() +30 | cross_downstream() + | ^^^^^^^^^^^^^^^^ AIR311 +31 | +32 | # airflow.models.baseoperatolinker + | + = help: Use `airflow.sdk.cross_downstream` instead + +AIR311_names.py:36:1: AIR311 `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +35 | # airflow.timetables.datasets +36 | DatasetOrTimeSchedule() + | ^^^^^^^^^^^^^^^^^^^^^ AIR311 +37 | +38 | # airflow.utils.dag_parsing_context + | + = help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead + +AIR311_names.py:39:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version. + | +38 | # airflow.utils.dag_parsing_context +39 | get_parsing_context() + | ^^^^^^^^^^^^^^^^^^^ AIR311 + | + = help: Use `airflow.sdk.get_parsing_context` instead diff --git a/ruff.schema.json b/ruff.schema.json index 16d78a3789..cbe8fa22cb 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2949,6 +2949,7 @@ "AIR301", "AIR302", "AIR31", + "AIR311", "AIR312", "ALL", "ANN",