From e6a2de3ac64b4eba481775fb2da161f1ac024147 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Wed, 16 Apr 2025 23:06:57 +0800 Subject: [PATCH] [`airflow`] Extract `AIR311` from `AIR301` rules (`AIR301`, `AIR311`) (#17310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary As discussed in https://github.com/astral-sh/ruff/issues/14626#issuecomment-2766146129, we're to separate suggested changes from required changes. The following symbols have been moved to AIR311 from AIR301. They still work in Airflow 3.0, but they're suggested to be changed as they're expected to be removed in a future version. * arguments * `airflow..DAG | dag` * `sla_miss_callback` * operators * `sla` * name * `airflow.Dataset] | [airflow.datasets.Dataset` → `airflow.sdk.Asset` * `airflow.datasets, rest @ ..` * `DatasetAlias` → `airflow.sdk.AssetAlias` * `DatasetAll` → `airflow.sdk.AssetAll` * `DatasetAny` → `airflow.sdk.AssetAny` * `expand_alias_to_datasets` → `airflow.sdk.expand_alias_to_assets` * `metadata.Metadata` → `airflow.sdk.Metadata` * `airflow.models.baseoperator.chain` → `airflow.sdk.chain` * `airflow.models.baseoperator.chain_linear` → `airflow.sdk.chain_linear` * `airflow.models.baseoperator.cross_downstream` → `airflow.sdk.cross_downstream` * `airflow.models.baseoperatorlink.BaseOperatorLink` → `airflow.sdk.definitions.baseoperatorlink.BaseOperatorLink` * `airflow.timetables, rest @ ..` * `datasets.DatasetOrTimeSchedule` → * `airflow.timetables.assets.AssetOrTimeSchedule` * `airflow.utils, rest @ ..` * `dag_parsing_context.get_parsing_context` → `airflow.sdk.get_parsing_context` ## Test Plan The test fixture has been updated acccordingly --- .../test/fixtures/airflow/AIR301_args.py | 11 - .../test/fixtures/airflow/AIR301_names.py | 30 +- .../test/fixtures/airflow/AIR311_args.py | 28 + .../test/fixtures/airflow/AIR311_names.py | 39 + .../src/checkers/ast/analyze/expression.rs | 9 + crates/ruff_linter/src/codes.rs | 1 + .../ruff_linter/src/rules/airflow/helpers.rs | 42 + crates/ruff_linter/src/rules/airflow/mod.rs | 2 + .../src/rules/airflow/rules/mod.rs | 2 + .../src/rules/airflow/rules/removal_in_3.rs | 104 +- .../airflow/rules/suggested_to_update_3_0.rs | 261 +++++ ...airflow__tests__AIR301_AIR301_args.py.snap | 335 +++--- ...sts__AIR301_AIR301_class_attribute.py.snap | 73 -- ...irflow__tests__AIR301_AIR301_names.py.snap | 1008 +++++++---------- ...airflow__tests__AIR311_AIR311_args.py.snap | 25 + ...irflow__tests__AIR311_AIR311_names.py.snap | 153 +++ ruff.schema.json | 1 + 17 files changed, 1157 insertions(+), 967 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/airflow/AIR311_args.py create mode 100644 crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py create mode 100644 crates/ruff_linter/src/rules/airflow/rules/suggested_to_update_3_0.rs create mode 100644 crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_args.py.snap create mode 100644 crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap 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",