Skip to content

Commit

Permalink
Implement Airflow rule for 3.0 removals
Browse files Browse the repository at this point in the history
Airflow 3.0 removes various deprecated functions, members, modules, and
other values. They have been deprecated in 2.x, but the removal causes
incompatibilities that we want to detect.
  • Loading branch information
uranusjr committed Nov 25, 2024
1 parent a90e404 commit 111ee16
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 2 deletions.
12 changes: 12 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/airflow/AIR302.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from airflow.utils import dates
from airflow.utils.dates import date_range, datetime_to_nano, days_ago

date_range
days_ago

dates.date_range
dates.days_ago

# This one was not deprecated.
datetime_to_nano
dates.datetime_to_nano
10 changes: 8 additions & 2 deletions crates/ruff_linter/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
use crate::rules::{
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
airflow, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear,
flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_use_pathlib, flynt, numpy,
Expand Down Expand Up @@ -216,6 +216,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::RegexFlagAlias) {
refurb::rules::regex_flag_alias(checker, expr);
}
if checker.enabled(Rule::AirflowDeprecatedMembers) {
airflow::rules::deprecated_members(checker, expr);
}

// Ex) List[...]
if checker.any_enabled(&[
Expand Down Expand Up @@ -376,6 +379,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ByteStringUsage) {
flake8_pyi::rules::bytestring_attribute(checker, expr);
}
if checker.enabled(Rule::AirflowDeprecatedMembers) {
airflow::rules::deprecated_members(checker, expr);
}
}
Expr::Call(
call @ ast::ExprCall {
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {

// airflow
(Airflow, "001") => (RuleGroup::Stable, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
(Airflow, "302") => (RuleGroup::Preview, rules::airflow::rules::AirflowDeprecatedMembers),

// perflint
(Perflint, "101") => (RuleGroup::Stable, rules::perflint::rules::UnnecessaryListCast),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/airflow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod tests {
use crate::{assert_messages, settings};

#[test_case(Rule::AirflowVariableNameTaskIdMismatch, Path::new("AIR001.py"))]
#[test_case(Rule::AirflowDeprecatedMembers, Path::new("AIR302.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
92 changes: 92 additions & 0 deletions crates/ruff_linter/src/rules/airflow/rules/deprecated_members.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Expr;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;

#[derive(Debug, Eq, PartialEq)]
enum Replacement {
None,
Name(String),
}

impl Replacement {
fn name(name: impl Into<String>) -> Self {
Self::Name(name.into())
}
}

/// ## What it does
/// Checks for uses of deprecated Airflow functions and values.
///
/// ## 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.
///
/// ## Example
/// ```python
/// from airflow.utils.dates import days_ago
///
///
/// yesterday = days_ago(today, 1)
/// ```
///
/// Use instead:
/// ```python
/// from datetime import timedelta
///
///
/// yesterday = today - timedelta(days=1)
/// ```
#[violation]
pub struct AirflowDeprecatedMembers {
deprecated: String,
replacement: Replacement,
}

impl Violation for AirflowDeprecatedMembers {
#[derive_message_formats]
fn message(&self) -> String {
let AirflowDeprecatedMembers {
deprecated,
replacement,
} = self;
match replacement {
Replacement::None => format!("`{deprecated}` is removed in Airflow 3.0"),
Replacement::Name(name) => {
format!("`{deprecated}` is removed in Airflow 3.0; use {name} instead")
}
}
}
}

/// AIR302
pub(crate) fn deprecated_members(checker: &mut Checker, expr: &Expr) {
let Some((deprecated, replacement)) =
checker
.semantic()
.resolve_qualified_name(expr)
.and_then(|qualname| match qualname.segments() {
["airflow", "utils", "dates", "date_range"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "dates", "days_ago"] => Some((
qualname.to_string(),
Replacement::name("datetime.timedelta()"),
)),
_ => None,
})
else {
return;
};

checker.diagnostics.push(Diagnostic::new(
AirflowDeprecatedMembers {
deprecated,
replacement,
},
expr.range(),
));
}
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/airflow/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub(crate) use deprecated_members::*;
pub(crate) use task_variable_name::*;

mod deprecated_members;
mod task_variable_name;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302.py:4:1: AIR302 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
2 | from airflow.utils.dates import date_range, datetime_to_nano, days_ago
3 |
4 | date_range
| ^^^^^^^^^^ AIR302
5 | days_ago
|

AIR302.py:5:1: AIR302 `airflow.utils.dates.days_ago` is removed in Airflow 3.0; use datetime.timedelta() instead
|
4 | date_range
5 | days_ago
| ^^^^^^^^ AIR302
6 |
7 | dates.date_range
|

AIR302.py:7:1: AIR302 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
5 | days_ago
6 |
7 | dates.date_range
| ^^^^^^^^^^^^^^^^ AIR302
8 | dates.days_ago
|

AIR302.py:8:1: AIR302 `airflow.utils.dates.days_ago` is removed in Airflow 3.0; use datetime.timedelta() instead
|
7 | dates.date_range
8 | dates.days_ago
| ^^^^^^^^^^^^^^ AIR302
9 |
10 | # This one was not deprecated.
|

0 comments on commit 111ee16

Please sign in to comment.