Skip to content

Commit

Permalink
feat(AIR302): extension "executors", "operators", "sensors", "hooks" …
Browse files Browse the repository at this point in the history
…have been removed in AirflowPlugin
  • Loading branch information
Lee-W committed Dec 26, 2024
1 parent 1af0c75 commit d1c9770
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from airflow.plugins_manager import AirflowPlugin


class AirflowTestPlugin(AirflowPlugin):
name = "test_plugin"
# --- Invalid extensions start
operators = [PluginOperator]
sensors = [PluginSensorOperator]
hooks = [PluginHook]
executors = [PluginExecutor]
# --- Invalid extensions end
macros = [plugin_macro]
flask_blueprints = [bp]
appbuilder_views = [v_appbuilder_package]
appbuilder_menu_items = [appbuilder_mitem, appbuilder_mitem_toplevel]
global_operator_extra_links = [
AirflowLink(),
GithubLink(),
]
operator_extra_links = [
GoogleLink(),
AirflowLink2(),
CustomOpLink(),
CustomBaseIndexOpLink(1),
]
timetables = [CustomCronDataIntervalTimetable]
listeners = [empty_listener, ClassBasedListener()]
ti_deps = [CustomTestTriggerRule()]
priority_weight_strategies = [CustomPriorityWeightStrategy]
3 changes: 3 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
);
}
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::removed_in_3(checker, expr);
}
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
pep8_naming::rules::mixed_case_variable_in_global_scope(
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 @@ -17,6 +17,7 @@ mod tests {
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_args.py"))]
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_names.py"))]
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_class_attribute.py"))]
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_airflow_plugin.py"))]
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR303.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
Expand Down
46 changes: 44 additions & 2 deletions crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
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};
use ruff_python_ast::{
name::QualifiedName, Arguments, Expr, ExprAttribute, ExprCall, ExprContext, ExprName,
StmtClassDef,
};
use ruff_python_semantic::analyze::typing;
use ruff_python_semantic::Modules;
use ruff_python_semantic::ScopeKind;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -806,6 +810,37 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
}
}

fn removed_airflow_plugin_extension(
checker: &mut Checker,
expr: &Expr,
name: &str,
class_def: &StmtClassDef,
) {
if matches!(name, "executors" | "operators" | "sensors" | "hooks") {
if class_def.bases().iter().any(|expr| {
checker
.semantic()
.resolve_qualified_name(expr)
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["airflow", "plugins_manager", "AirflowPlugin"]
)
})
}) {
checker.diagnostics.push(Diagnostic::new(
Airflow3Removal {
deprecated: name.to_string(),
replacement: Replacement::Message(
"This extension should just be imported as a regular python module.",
),
},
expr.range(),
));
}
}
}

/// AIR302
pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
Expand All @@ -826,7 +861,14 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
removed_name(checker, expr, ranged);
removed_class_attribute(checker, expr);
}
ranged @ Expr::Name(_) => removed_name(checker, expr, ranged),
ranged @ Expr::Name(ExprName { id, ctx, .. }) => {
removed_name(checker, expr, ranged);
if ctx == &ExprContext::Store {
if let ScopeKind::Class(class_def) = &checker.semantic().current_scope().kind {
removed_airflow_plugin_extension(checker, expr, id, class_def);
}
}
}
_ => {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
snapshot_kind: text
---
AIR302_airflow_plugin.py:7:5: AIR302 `operators` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
5 | name = "test_plugin"
6 | # --- Invalid extensions start
7 | operators = [PluginOperator]
| ^^^^^^^^^ AIR302
8 | sensors = [PluginSensorOperator]
9 | hooks = [PluginHook]
|

AIR302_airflow_plugin.py:8:5: AIR302 `sensors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
6 | # --- Invalid extensions start
7 | operators = [PluginOperator]
8 | sensors = [PluginSensorOperator]
| ^^^^^^^ AIR302
9 | hooks = [PluginHook]
10 | executors = [PluginExecutor]
|

AIR302_airflow_plugin.py:9:5: AIR302 `hooks` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
7 | operators = [PluginOperator]
8 | sensors = [PluginSensorOperator]
9 | hooks = [PluginHook]
| ^^^^^ AIR302
10 | executors = [PluginExecutor]
11 | # --- Invalid extensions end
|

AIR302_airflow_plugin.py:10:5: AIR302 `executors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
8 | sensors = [PluginSensorOperator]
9 | hooks = [PluginHook]
10 | executors = [PluginExecutor]
| ^^^^^^^^^ AIR302
11 | # --- Invalid extensions end
12 | macros = [plugin_macro]
|

0 comments on commit d1c9770

Please sign in to comment.