Skip to content

Commit c0a34d3

Browse files
committed
Add more checks for removed context variables
add lint rule to show error for removed context variables in airflow
1 parent f8c9665 commit c0a34d3

File tree

5 files changed

+519
-2
lines changed

5 files changed

+519
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from airflow.models import DAG
2+
from airflow.operators.dummy import DummyOperator
3+
from datetime import datetime
4+
from airflow.plugins_manager import AirflowPlugin
5+
from airflow.decorators import task, get_current_context
6+
from airflow.models.baseoperator import BaseOperator
7+
8+
@task
9+
def print_config(**context):
10+
# This should not throw an error as logical_date is part of airflow context.
11+
logical_date = context["logical_date"]
12+
13+
# Removed usage - should trigger violations
14+
execution_date = context["execution_date"]
15+
next_ds = context["next_ds"]
16+
next_ds_nodash = context["next_ds_nodash"]
17+
next_execution_date = context["next_execution_date"]
18+
prev_ds = context["prev_ds"]
19+
prev_ds_nodash = context["prev_ds_nodash"]
20+
prev_execution_date = context["prev_execution_date"]
21+
prev_execution_date_success = context["prev_execution_date_success"]
22+
tomorrow_ds = context["tomorrow_ds"]
23+
yesterday_ds = context["yesterday_ds"]
24+
yesterday_ds_nodash = context["yesterday_ds_nodash"]
25+
26+
with DAG(
27+
dag_id="example_dag",
28+
schedule_interval="@daily",
29+
start_date=datetime(2023, 1, 1),
30+
template_searchpath=["/templates"],
31+
) as dag:
32+
task1 = DummyOperator(
33+
task_id="task1",
34+
params={
35+
# Removed variables in template
36+
"execution_date": "{{ execution_date }}",
37+
"next_ds": "{{ next_ds }}",
38+
"prev_ds": "{{ prev_ds }}"
39+
},
40+
)
41+
42+
class CustomMacrosPlugin(AirflowPlugin):
43+
name = "custom_macros"
44+
macros = {
45+
"execution_date_macro": lambda context: context["execution_date"],
46+
"next_ds_macro": lambda context: context["next_ds"]
47+
}
48+
49+
@task
50+
def print_config():
51+
context = get_current_context()
52+
execution_date = context["execution_date"]
53+
next_ds = context["next_ds"]
54+
next_ds_nodash = context["next_ds_nodash"]
55+
next_execution_date = context["next_execution_date"]
56+
prev_ds = context["prev_ds"]
57+
prev_ds_nodash = context["prev_ds_nodash"]
58+
prev_execution_date = context["prev_execution_date"]
59+
prev_execution_date_success = context["prev_execution_date_success"]
60+
tomorrow_ds = context["tomorrow_ds"]
61+
yesterday_ds = context["yesterday_ds"]
62+
yesterday_ds_nodash = context["yesterday_ds_nodash"]
63+
64+
class CustomOperator(BaseOperator):
65+
def execute(self, context):
66+
execution_date = context["execution_date"]
67+
next_ds = context["next_ds"]
68+
next_ds_nodash = context["next_ds_nodash"]
69+
next_execution_date = context["next_execution_date"]
70+
prev_ds = context["prev_ds"]
71+
prev_ds_nodash = context["prev_ds_nodash"]
72+
prev_execution_date = context["prev_execution_date"]
73+
prev_execution_date_success = context["prev_execution_date_success"]
74+
tomorrow_ds = context["tomorrow_ds"]
75+
yesterday_ds = context["yesterday_ds"]
76+
yesterday_ds_nodash = context["yesterday_ds_nodash"]

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
171171
if checker.enabled(Rule::NonPEP646Unpack) {
172172
pyupgrade::rules::use_pep646_unpack(checker, subscript);
173173
}
174-
174+
if checker.enabled(Rule::Airflow3Removal) {
175+
airflow::rules::removed_in_3(checker, expr);
176+
}
175177
pandas_vet::rules::subscript(checker, value, expr);
176178
}
177179
Expr::Tuple(ast::ExprTuple {

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

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod tests {
1818
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_names.py"))]
1919
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_class_attribute.py"))]
2020
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_airflow_plugin.py"))]
21+
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_context.py"))]
2122
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR303.py"))]
2223
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
2324
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());

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

+61-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
22
use ruff_macros::{derive_message_formats, ViolationMetadata};
33
use ruff_python_ast::{
44
name::QualifiedName, Arguments, Expr, ExprAttribute, ExprCall, ExprContext, ExprName,
5-
StmtClassDef,
5+
ExprStringLiteral, ExprSubscript, StmtClassDef,
66
};
77
use ruff_python_semantic::analyze::typing;
88
use ruff_python_semantic::Modules;
@@ -71,6 +71,63 @@ impl Violation for Airflow3Removal {
7171
}
7272
}
7373

74+
fn extract_name_from_slice(slice: &Expr) -> Option<String> {
75+
match slice {
76+
Expr::StringLiteral(ExprStringLiteral { value, .. }) => Some(value.to_string()),
77+
_ => None,
78+
}
79+
}
80+
81+
pub(crate) fn removed_context_variable(checker: &mut Checker, expr: &Expr) {
82+
const REMOVED_CONTEXT_KEYS: [&str; 12] = [
83+
"conf",
84+
"execution_date",
85+
"next_ds",
86+
"next_ds_nodash",
87+
"next_execution_date",
88+
"prev_ds",
89+
"prev_ds_nodash",
90+
"prev_execution_date",
91+
"prev_execution_date_success",
92+
"tomorrow_ds",
93+
"yesterday_ds",
94+
"yesterday_ds_nodash",
95+
];
96+
97+
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = expr {
98+
if let Expr::Name(ExprName { id, .. }) = &**value {
99+
if id.as_str() == "context" {
100+
if let Some(key) = extract_name_from_slice(slice) {
101+
if REMOVED_CONTEXT_KEYS.contains(&key.as_str()) {
102+
checker.diagnostics.push(Diagnostic::new(
103+
Airflow3Removal {
104+
deprecated: key,
105+
replacement: Replacement::None,
106+
},
107+
slice.range(),
108+
));
109+
}
110+
}
111+
}
112+
}
113+
}
114+
115+
if let Expr::StringLiteral(ExprStringLiteral { value, .. }) = expr {
116+
let value_str = value.to_string();
117+
for key in REMOVED_CONTEXT_KEYS {
118+
if value_str.contains(&format!("{{{{ {key} }}}}")) {
119+
checker.diagnostics.push(Diagnostic::new(
120+
Airflow3Removal {
121+
deprecated: key.to_string(),
122+
replacement: Replacement::None,
123+
},
124+
expr.range(),
125+
));
126+
}
127+
}
128+
}
129+
}
130+
74131
/// AIR302
75132
pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
76133
if !checker.semantic().seen_module(Modules::AIRFLOW) {
@@ -100,6 +157,9 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
100157
}
101158
}
102159
}
160+
Expr::Subscript(_) => {
161+
removed_context_variable(checker, expr);
162+
}
103163
_ => {}
104164
}
105165
}

0 commit comments

Comments
 (0)