Skip to content

Commit 2eb2d53

Browse files
authored
[airflow] Apply try-catch guard to all AIR3 rules (AIR3) (#17887)
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> If a try-catch block guards the names, we don't raise warnings. During this change, I discovered that some of the replacement types were missed. Thus, I extend the fix to types other than AutoImport as well ## Test Plan <!-- How was it tested? --> Test fixtures are added and updated.
1 parent f549dfe commit 2eb2d53

14 files changed

+129
-53
lines changed
Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
from __future__ import annotations
22

33
try:
4-
from airflow.sdk import Asset
4+
from airflow.assets.manager import AssetManager
55
except ModuleNotFoundError:
6-
from airflow.datasets import Dataset as Asset
6+
from airflow.datasets.manager import DatasetManager as AssetManager
77

8-
Asset
9-
10-
try:
11-
from airflow.sdk import Asset
12-
except ModuleNotFoundError:
13-
from airflow import datasets
14-
15-
Asset = datasets.Dataset
16-
17-
asset = Asset()
8+
AssetManager()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from __future__ import annotations
2+
3+
try:
4+
from airflow.providers.http.operators.http import HttpOperator
5+
except ModuleNotFoundError:
6+
from airflow.operators.http_operator import SimpleHttpOperator as HttpOperator
7+
8+
HttpOperator()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from __future__ import annotations
2+
3+
try:
4+
from airflow.sdk import Asset
5+
except ModuleNotFoundError:
6+
from airflow.datasets import Dataset as Asset
7+
8+
Asset()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from __future__ import annotations
2+
3+
try:
4+
from airflow.providers.standard.triggers.file import FileTrigger
5+
except ModuleNotFoundError:
6+
from airflow.triggers.file import FileTrigger
7+
8+
FileTrigger()

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

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ pub(crate) enum ProviderReplacement {
4545

4646
pub(crate) fn is_guarded_by_try_except(
4747
expr: &Expr,
48-
replacement: &Replacement,
48+
module: &str,
49+
name: &str,
4950
semantic: &SemanticModel,
5051
) -> bool {
5152
match expr {
@@ -63,7 +64,7 @@ pub(crate) fn is_guarded_by_try_except(
6364
if !suspended_exceptions.contains(Exceptions::ATTRIBUTE_ERROR) {
6465
return false;
6566
}
66-
try_block_contains_undeprecated_attribute(try_node, replacement, semantic)
67+
try_block_contains_undeprecated_attribute(try_node, module, name, semantic)
6768
}
6869
Expr::Name(ExprName { id, .. }) => {
6970
let Some(binding_id) = semantic.lookup_symbol(id.as_str()) else {
@@ -89,7 +90,7 @@ pub(crate) fn is_guarded_by_try_except(
8990
{
9091
return false;
9192
}
92-
try_block_contains_undeprecated_import(try_node, replacement)
93+
try_block_contains_undeprecated_import(try_node, module, name)
9394
}
9495
_ => false,
9596
}
@@ -100,12 +101,10 @@ pub(crate) fn is_guarded_by_try_except(
100101
/// member is being accessed from the non-deprecated location?
101102
fn try_block_contains_undeprecated_attribute(
102103
try_node: &StmtTry,
103-
replacement: &Replacement,
104+
module: &str,
105+
name: &str,
104106
semantic: &SemanticModel,
105107
) -> bool {
106-
let Replacement::AutoImport { module, name } = replacement else {
107-
return false;
108-
};
109108
let undeprecated_qualified_name = {
110109
let mut builder = QualifiedNameBuilder::default();
111110
for part in module.split('.') {
@@ -122,10 +121,7 @@ fn try_block_contains_undeprecated_attribute(
122121
/// Given an [`ast::StmtTry`] node, does the `try` branch of that node
123122
/// contain any [`ast::StmtImportFrom`] nodes that indicate the airflow
124123
/// member is being imported from the non-deprecated location?
125-
fn try_block_contains_undeprecated_import(try_node: &StmtTry, replacement: &Replacement) -> bool {
126-
let Replacement::AutoImport { module, name } = replacement else {
127-
return false;
128-
};
124+
fn try_block_contains_undeprecated_import(try_node: &StmtTry, module: &str, name: &str) -> bool {
129125
let mut import_searcher = ImportSearcher::new(module, name);
130126
import_searcher.visit_body(&try_node.body);
131127
import_searcher.found_import

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@ mod tests {
4646
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_sqlite.py"))]
4747
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_zendesk.py"))]
4848
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_standard.py"))]
49+
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_try.py"))]
4950
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_args.py"))]
5051
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_names.py"))]
52+
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_try.py"))]
5153
#[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312.py"))]
54+
#[test_case(Rule::Airflow3SuggestedToMoveToProvider, Path::new("AIR312_try.py"))]
5255
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
5356
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
5457
let diagnostics = test_path(

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::importer::ImportRequest;
2-
use crate::rules::airflow::helpers::ProviderReplacement;
2+
use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement};
33
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
44
use ruff_macros::{derive_message_formats, ViolationMetadata};
55
use ruff_python_ast::{Expr, ExprAttribute};
@@ -937,13 +937,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
937937
ranged.range(),
938938
);
939939

940-
if let ProviderReplacement::AutoImport {
941-
module,
942-
name,
943-
provider: _,
944-
version: _,
945-
} = replacement
946-
{
940+
let semantic = checker.semantic();
941+
if let Some((module, name)) = match &replacement {
942+
ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)),
943+
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
944+
Some((module, name.as_str()))
945+
}
946+
_ => None,
947+
} {
948+
if is_guarded_by_try_except(expr, module, name, semantic) {
949+
return;
950+
}
947951
diagnostic.try_set_fix(|| {
948952
let (import_edit, binding) = checker.importer().get_or_import_symbol(
949953
&ImportRequest::import_from(module, name),
@@ -954,6 +958,5 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
954958
Ok(Fix::safe_edits(import_edit, [replacement_edit]))
955959
});
956960
}
957-
958961
checker.report_diagnostic(diagnostic);
959962
}

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -865,19 +865,22 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
865865
_ => return,
866866
};
867867

868-
if is_guarded_by_try_except(expr, &replacement, semantic) {
869-
return;
870-
}
871-
872868
let mut diagnostic = Diagnostic::new(
873869
Airflow3Removal {
874870
deprecated: qualified_name.to_string(),
875871
replacement: replacement.clone(),
876872
},
877873
range,
878874
);
879-
880-
if let Replacement::AutoImport { module, name } = replacement {
875+
let semantic = checker.semantic();
876+
if let Some((module, name)) = match &replacement {
877+
Replacement::AutoImport { module, name } => Some((module, *name)),
878+
Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())),
879+
_ => None,
880+
} {
881+
if is_guarded_by_try_except(expr, module, name, semantic) {
882+
return;
883+
}
881884
diagnostic.try_set_fix(|| {
882885
let (import_edit, binding) = checker.importer().get_or_import_symbol(
883886
&ImportRequest::import_from(module, name),

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::importer::ImportRequest;
22

3-
use crate::rules::airflow::helpers::ProviderReplacement;
3+
use crate::rules::airflow::helpers::{is_guarded_by_try_except, ProviderReplacement};
44
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
55
use ruff_macros::{derive_message_formats, ViolationMetadata};
66
use ruff_python_ast::{Expr, ExprAttribute};
@@ -279,13 +279,17 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
279279
ranged.range(),
280280
);
281281

282-
if let ProviderReplacement::AutoImport {
283-
module,
284-
name,
285-
provider: _,
286-
version: _,
287-
} = replacement
288-
{
282+
let semantic = checker.semantic();
283+
if let Some((module, name)) = match &replacement {
284+
ProviderReplacement::AutoImport { module, name, .. } => Some((module, *name)),
285+
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
286+
Some((module, name.as_str()))
287+
}
288+
_ => None,
289+
} {
290+
if is_guarded_by_try_except(expr, module, name, semantic) {
291+
return;
292+
}
289293
diagnostic.try_set_fix(|| {
290294
let (import_edit, binding) = checker.importer().get_or_import_symbol(
291295
&ImportRequest::import_from(module, name),

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
283283
_ => return,
284284
};
285285

286-
if is_guarded_by_try_except(expr, &replacement, semantic) {
287-
return;
288-
}
289-
290286
let mut diagnostic = Diagnostic::new(
291287
Airflow3SuggestedUpdate {
292288
deprecated: qualified_name.to_string(),
@@ -295,7 +291,15 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
295291
range,
296292
);
297293

298-
if let Replacement::AutoImport { module, name } = replacement {
294+
let semantic = checker.semantic();
295+
if let Some((module, name)) = match &replacement {
296+
Replacement::AutoImport { module, name } => Some((module, *name)),
297+
Replacement::SourceModuleMoved { module, name } => Some((module, name.as_str())),
298+
_ => None,
299+
} {
300+
if is_guarded_by_try_except(expr, module, name, semantic) {
301+
return;
302+
}
299303
diagnostic.try_set_fix(|| {
300304
let (import_edit, binding) = checker.importer().get_or_import_symbol(
301305
&ImportRequest::import_from(module, name),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
source: crates/ruff_linter/src/rules/airflow/mod.rs
3+
---
4+

crates/ruff_linter/src/rules/airflow/snapshots/ruff_linter__rules__airflow__tests__AIR311_AIR311_names.py.snap

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ AIR311_names.py:49:1: AIR311 `airflow.models.Connection` is removed in Airflow 3
258258
|
259259
= help: Use `airflow.sdk.Connection` instead
260260

261-
AIR311_names.py:50:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
261+
AIR311_names.py:50:1: AIR311 [*] `airflow.models.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
262262
|
263263
48 | # airflow.models
264264
49 | Connection()
@@ -268,6 +268,24 @@ AIR311_names.py:50:1: AIR311 `airflow.models.DAG` is removed in Airflow 3.0; It
268268
|
269269
= help: Use `airflow.sdk.DAG` instead
270270

271+
Safe fix
272+
22 22 | from airflow.models.dag import DAG as DAGFromDag
273+
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
274+
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
275+
25 |+from airflow.sdk import DAG
276+
25 26 |
277+
26 27 | # airflow
278+
27 28 | DatasetFromRoot()
279+
--------------------------------------------------------------------------------
280+
47 48 |
281+
48 49 | # airflow.models
282+
49 50 | Connection()
283+
50 |-DAGFromModel()
284+
51 |+DAG()
285+
51 52 | Variable()
286+
52 53 |
287+
53 54 | # airflow.models.baseoperator
288+
271289
AIR311_names.py:51:1: AIR311 `airflow.models.Variable` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
272290
|
273291
49 | Connection()
@@ -310,7 +328,7 @@ AIR311_names.py:56:1: AIR311 `airflow.models.baseoperator.cross_downstream` is r
310328
|
311329
= help: Use `airflow.sdk.cross_downstream` instead
312330

313-
AIR311_names.py:62:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
331+
AIR311_names.py:62:1: AIR311 [*] `airflow.models.dag.DAG` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
314332
|
315333
61 | # airflow.models.dag
316334
62 | DAGFromDag()
@@ -320,6 +338,24 @@ AIR311_names.py:62:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0;
320338
|
321339
= help: Use `airflow.sdk.DAG` instead
322340

341+
Safe fix
342+
22 22 | from airflow.models.dag import DAG as DAGFromDag
343+
23 23 | from airflow.timetables.datasets import DatasetOrTimeSchedule
344+
24 24 | from airflow.utils.dag_parsing_context import get_parsing_context
345+
25 |+from airflow.sdk import DAG
346+
25 26 |
347+
26 27 | # airflow
348+
27 28 | DatasetFromRoot()
349+
--------------------------------------------------------------------------------
350+
59 60 | BaseOperatorLink()
351+
60 61 |
352+
61 62 | # airflow.models.dag
353+
62 |-DAGFromDag()
354+
63 |+DAG()
355+
63 64 | # airflow.timetables.datasets
356+
64 65 | DatasetOrTimeSchedule()
357+
65 66 |
358+
323359
AIR311_names.py:64: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.
324360
|
325361
62 | DAGFromDag()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
source: crates/ruff_linter/src/rules/airflow/mod.rs
3+
---
4+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
source: crates/ruff_linter/src/rules/airflow/mod.rs
3+
---
4+

0 commit comments

Comments
 (0)