Skip to content

Commit 8e4b65e

Browse files
committed
feat(AIR303): initial air303
1 parent b56b3c8 commit 8e4b65e

File tree

8 files changed

+120
-0
lines changed

8 files changed

+120
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from airflow.www.security import FabAirflowSecurityManagerOverride
2+
3+
FabAirflowSecurityManagerOverride

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

+3
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
223223
if checker.enabled(Rule::Airflow3Removal) {
224224
airflow::rules::removed_in_3(checker, expr);
225225
}
226+
if checker.enabled(Rule::Airflow3MoveToProvider) {
227+
airflow::rules::moved_to_provider_in_3(checker, expr);
228+
}
226229

227230
// Ex) List[...]
228231
if checker.any_enabled(&[

crates/ruff_linter/src/codes.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
10441044
(Airflow, "001") => (RuleGroup::Stable, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
10451045
(Airflow, "301") => (RuleGroup::Preview, rules::airflow::rules::AirflowDagNoScheduleArgument),
10461046
(Airflow, "302") => (RuleGroup::Preview, rules::airflow::rules::Airflow3Removal),
1047+
(Airflow, "303") => (RuleGroup::Preview, rules::airflow::rules::Airflow3MoveToProvider),
10471048

10481049
// perflint
10491050
(Perflint, "101") => (RuleGroup::Stable, rules::perflint::rules::UnnecessaryListCast),

crates/ruff_linter/src/rules/airflow/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod tests {
1616
#[test_case(Rule::AirflowDagNoScheduleArgument, Path::new("AIR301.py"))]
1717
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_args.py"))]
1818
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_names.py"))]
19+
#[test_case(Rule::Airflow3MoveToProvider, Path::new("AIR303.py"))]
1920
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
2021
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
2122
let diagnostics = test_path(
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
pub(crate) use dag_schedule_argument::*;
2+
pub(crate) use moved_to_provider_in_3::*;
23
pub(crate) use removal_in_3::*;
34
pub(crate) use task_variable_name::*;
45

56
mod dag_schedule_argument;
7+
mod moved_to_provider_in_3;
68
mod removal_in_3;
79
mod task_variable_name;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use ruff_diagnostics::{Diagnostic, Violation};
2+
use ruff_macros::{derive_message_formats, ViolationMetadata};
3+
use ruff_python_ast::{Expr, ExprAttribute};
4+
use ruff_python_semantic::Modules;
5+
use ruff_text_size::Ranged;
6+
7+
use crate::checkers::ast::Checker;
8+
9+
#[derive(Debug, Eq, PartialEq)]
10+
enum Replacement {
11+
Message(String, String, String),
12+
}
13+
14+
/// ## What it does
15+
/// Checks for uses of deprecated Airflow functions and values.
16+
///
17+
/// ## Why is this bad?
18+
/// Airflow 3.0 removed various deprecated functions, members, and other
19+
/// values. Some have more modern replacements. Others are considered too niche
20+
/// and not worth to be maintained in Airflow.
21+
///
22+
/// ## Example
23+
/// ```python
24+
/// from airflow.utils.dates import days_ago
25+
///
26+
///
27+
/// yesterday = days_ago(today, 1)
28+
/// ```
29+
///
30+
/// Use instead:
31+
/// ```python
32+
/// from datetime import timedelta
33+
///
34+
///
35+
/// yesterday = today - timedelta(days=1)
36+
/// ```
37+
#[derive(ViolationMetadata)]
38+
pub(crate) struct Airflow3MoveToProvider {
39+
deprecated: String,
40+
replacement: Replacement,
41+
}
42+
43+
impl Violation for Airflow3MoveToProvider {
44+
#[derive_message_formats]
45+
fn message(&self) -> String {
46+
let Airflow3MoveToProvider {
47+
deprecated,
48+
replacement,
49+
} = self;
50+
match replacement {
51+
Replacement::Message(name, provider, provider_version) => {
52+
format!("`{deprecated}` is removed in Airflow 3.0; use `{name}` instead (in `{provider}=={provider_version}`)")
53+
}
54+
}
55+
}
56+
}
57+
58+
fn moved_to_provider(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
59+
let result =
60+
checker
61+
.semantic()
62+
.resolve_qualified_name(expr)
63+
.and_then(|qualname| match qualname.segments() {
64+
["airflow", "www", "security", "FabAirflowSecurityManagerOverride"] => Some((
65+
qualname.to_string(),
66+
Replacement::Message(
67+
"airflow.providers.fab.auth_manager.security_manager.override.FabAirflowSecurityManagerOverride".to_string(),
68+
"apache-airflow-providers-fab".to_string(),
69+
"1.0.0".to_string()
70+
),
71+
)),
72+
_ => None,
73+
});
74+
if let Some((deprecated, replacement)) = result {
75+
checker.diagnostics.push(Diagnostic::new(
76+
Airflow3MoveToProvider {
77+
deprecated,
78+
replacement,
79+
},
80+
ranged.range(),
81+
));
82+
}
83+
}
84+
85+
/// AIR303
86+
pub(crate) fn moved_to_provider_in_3(checker: &mut Checker, expr: &Expr) {
87+
if !checker.semantic().seen_module(Modules::AIRFLOW) {
88+
return;
89+
}
90+
91+
match expr {
92+
Expr::Attribute(ExprAttribute { attr: ranged, .. }) => {
93+
moved_to_provider(checker, expr, ranged);
94+
}
95+
ranged @ Expr::Name(_) => moved_to_provider(checker, expr, ranged),
96+
_ => {}
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: crates/ruff_linter/src/rules/airflow/mod.rs
3+
snapshot_kind: text
4+
---
5+
AIR303.py:3:1: AIR303 `airflow.www.security.FabAirflowSecurityManagerOverride` is removed in Airflow 3.0; use `airflow.providers.fab.auth_manager.security_manager.override.FabAirflowSecurityManagerOverride` instead (in `apache-airflow-providers-fab==1.0.0`)
6+
|
7+
1 | from airflow.www.security import FabAirflowSecurityManagerOverride
8+
2 |
9+
3 | FabAirflowSecurityManagerOverride
10+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR303
11+
|

ruff.schema.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)