Skip to content

Commit

Permalink
Merge branch 'main' into PYI034
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo authored Dec 6, 2024
2 parents da69f1a + 2119dca commit 7032218
Show file tree
Hide file tree
Showing 9 changed files with 769 additions and 139 deletions.
55 changes: 55 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/airflow/AIR302_args.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
from airflow import DAG, dag
from airflow.timetables.simple import NullTimetable

from airflow.operators.trigger_dagrun import TriggerDagRunOperator
from airflow.providers.standard.operators import trigger_dagrun

from airflow.operators.datetime import BranchDateTimeOperator
from airflow.providers.standard.operators import datetime

from airflow.sensors.weekday import DayOfWeekSensor, BranchDayOfWeekOperator
from airflow.providers.standard.sensors import weekday

DAG(dag_id="class_schedule", schedule="@hourly")

DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")

DAG(dag_id="class_timetable", timetable=NullTimetable())


def sla_callback(*arg, **kwargs):
pass


DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback)


@dag(schedule="0 * * * *")
def decorator_schedule():
pass
Expand All @@ -21,3 +37,42 @@ def decorator_schedule_interval():
@dag(timetable=NullTimetable())
def decorator_timetable():
pass


@dag(sla_miss_callback=sla_callback)
def decorator_sla_callback():
pass


@dag()
def decorator_deprecated_operator_args():
trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
task_id="trigger_dagrun_op1", execution_date="2024-12-04"
)
trigger_dagrun_op2 = TriggerDagRunOperator(
task_id="trigger_dagrun_op2", execution_date="2024-12-04"
)

branch_dt_op = datetime.BranchDateTimeOperator(
task_id="branch_dt_op", use_task_execution_day=True
)
branch_dt_op2 = BranchDateTimeOperator(
task_id="branch_dt_op2", use_task_execution_day=True
)

dof_task_sensor = weekday.DayOfWeekSensor(
task_id="dof_task_sensor", use_task_execution_day=True
)
dof_task_sensor2 = DayOfWeekSensor(
task_id="dof_task_sensor2", use_task_execution_day=True
)

bdow_op = weekday.BranchDayOfWeekOperator(
task_id="bdow_op", use_task_execution_day=True
)
bdow_op2 = BranchDayOfWeekOperator(task_id="bdow_op2", use_task_execution_day=True)

trigger_dagrun_op >> trigger_dagrun_op2
branch_dt_op >> branch_dt_op2
dof_task_sensor >> dof_task_sensor2
bdow_op >> bdow_op2
18 changes: 17 additions & 1 deletion crates/ruff_linter/resources/test/fixtures/ruff/RUF052.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def fun(self):
_var = "method variable" # [RUF052]
return _var

def fun(_var): # [RUF052]
def fun(_var): # parameters are ignored
return _var

def fun():
Expand Down Expand Up @@ -129,3 +129,19 @@ def nested():
# unfixable because the rename would shadow a variable from the outer function
_local = "local4"
print(_local)

def special_calls():
from typing import TypeVar, ParamSpec, NamedTuple
from enum import Enum
from collections import namedtuple

_P = ParamSpec("_P")
_T = TypeVar(name="_T", covariant=True, bound=int|str)
_NT = NamedTuple("_NT", [("foo", int)])
_E = Enum("_E", ["a", "b", "c"])
_NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
_NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
_DynamicClass = type("_DynamicClass", (), {})
_NotADynamicClass = type("_NotADynamicClass")

print(_T, _P, _NT, _E, _NT2, _NT3, _DynamicClass, _NotADynamicClass)
92 changes: 92 additions & 0 deletions crates/ruff_linter/src/renamer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use anyhow::{anyhow, Result};
use itertools::Itertools;

use ruff_diagnostics::Edit;
use ruff_python_ast as ast;
use ruff_python_codegen::Stylist;
use ruff_python_semantic::{Binding, BindingKind, Scope, ScopeId, SemanticModel};
use ruff_text_size::Ranged;
Expand Down Expand Up @@ -69,6 +70,11 @@ impl Renamer {
/// example, to rename `pandas` to `pd`, we may need to rewrite `import pandas` to
/// `import pandas as pd`, rather than `import pd`.
///
/// 1. Check to see if the binding is assigned to a known special call where the first argument
/// must be a string that is the same as the binding's name. For example,
/// `T = TypeVar("_T")` will be rejected by a type checker; only `T = TypeVar("T")` will do.
/// If it *is* one of these calls, we rename the relevant argument as well.
///
/// 1. Rename every reference to the [`Binding`]. For example, renaming the references to the
/// `x = 1` binding above would give us:
///
Expand Down Expand Up @@ -198,6 +204,12 @@ impl Renamer {
if let Some(edit) = Renamer::rename_binding(binding, name, target) {
edits.push(edit);

if let Some(edit) =
Renamer::fixup_assigned_value(binding, semantic, stylist, name, target)
{
edits.push(edit);
}

// Rename any delayed annotations.
if let Some(annotations) = semantic.delayed_annotations(binding_id) {
edits.extend(annotations.iter().filter_map(|annotation_id| {
Expand Down Expand Up @@ -231,6 +243,86 @@ impl Renamer {
edits
}

/// If the r.h.s. of a call expression is a call expression,
/// we may need to fixup some arguments passed to that call expression.
///
/// It's impossible to do this entirely rigorously;
/// we only special-case some common standard-library constructors here.
///
/// For example, in this `TypeVar` definition:
/// ```py
/// from typing import TypeVar
///
/// _T = TypeVar("_T")
/// ```
///
/// If we're renaming it from `_T` to `T`, we want this to be the end result:
/// ```py
/// from typing import TypeVar
///
/// T = TypeVar("T")
/// ```
///
/// Not this, which a type checker will reject:
/// ```py
/// from typing import TypeVar
///
/// T = TypeVar("_T")
/// ```
fn fixup_assigned_value(
binding: &Binding,
semantic: &SemanticModel,
stylist: &Stylist,
name: &str,
target: &str,
) -> Option<Edit> {
let statement = binding.statement(semantic)?;

let (ast::Stmt::Assign(ast::StmtAssign { value, .. })
| ast::Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), ..
})) = statement
else {
return None;
};

let ast::ExprCall {
func, arguments, ..
} = value.as_call_expr()?;

let qualified_name = semantic.resolve_qualified_name(func)?;

let name_argument = match qualified_name.segments() {
["collections", "namedtuple"] => arguments.find_argument("typename", 0),

["typing" | "typing_extensions", "TypeVar" | "ParamSpec" | "TypeVarTuple" | "NewType" | "TypeAliasType"] => {
arguments.find_argument("name", 0)
}

["enum", "Enum" | "IntEnum" | "StrEnum" | "ReprEnum" | "Flag" | "IntFlag"]
| ["typing" | "typing_extensions", "NamedTuple" | "TypedDict"] => {
arguments.find_positional(0)
}

["builtins" | "", "type"] if arguments.len() == 3 => arguments.find_positional(0),

_ => None,
}?;

let name_argument = name_argument.as_string_literal_expr()?;

if name_argument.value.to_str() != name {
return None;
}

let quote = stylist.quote();

Some(Edit::range_replacement(
format!("{quote}{target}{quote}"),
name_argument.range(),
))
}

/// Rename a [`Binding`] reference.
fn rename_binding(binding: &Binding, name: &str, target: &str) -> Option<Edit> {
match &binding.kind {
Expand Down
33 changes: 33 additions & 0 deletions crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,39 @@ fn removed_argument(checker: &mut Checker, qualname: &QualifiedName, arguments:
"timetable",
Some("schedule"),
));
checker.diagnostics.extend(diagnostic_for_argument(
arguments,
"sla_miss_callback",
None::<&str>,
));
}
["airflow", .., "operators", "trigger_dagrun", "TriggerDagRunOperator"] => {
checker.diagnostics.extend(diagnostic_for_argument(
arguments,
"execution_date",
Some("logical_date"),
));
}
["airflow", .., "operators", "datetime", "BranchDateTimeOperator"] => {
checker.diagnostics.extend(diagnostic_for_argument(
arguments,
"use_task_execution_day",
Some("use_task_logical_date"),
));
}
["airflow", .., "operators", "weekday", "DayOfWeekSensor"] => {
checker.diagnostics.extend(diagnostic_for_argument(
arguments,
"use_task_execution_day",
Some("use_task_logical_date"),
));
}
["airflow", .., "operators", "weekday", "BranchDayOfWeekOperator"] => {
checker.diagnostics.extend(diagnostic_for_argument(
arguments,
"use_task_execution_day",
Some("use_task_logical_date"),
));
}
_ => {}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,87 @@
source: crates/ruff_linter/src/rules/airflow/mod.rs
snapshot_kind: text
---
AIR302_args.py:6:39: AIR302 `schedule_interval` is removed in Airflow 3.0; use `schedule` instead
|
4 | DAG(dag_id="class_schedule", schedule="@hourly")
5 |
6 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
| ^^^^^^^^^^^^^^^^^ AIR302
7 |
8 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|

AIR302_args.py:8:31: AIR302 `timetable` is removed in Airflow 3.0; use `schedule` instead
|
6 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
7 |
8 | DAG(dag_id="class_timetable", timetable=NullTimetable())
| ^^^^^^^^^ AIR302
|

AIR302_args.py:16:6: AIR302 `schedule_interval` is removed in Airflow 3.0; use `schedule` instead
|
16 | @dag(schedule_interval="0 * * * *")
AIR302_args.py:15:39: AIR302 `schedule_interval` is removed in Airflow 3.0; use `schedule` instead
|
13 | DAG(dag_id="class_schedule", schedule="@hourly")
14 |
15 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
| ^^^^^^^^^^^^^^^^^ AIR302
16 |
17 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|

AIR302_args.py:17:31: AIR302 `timetable` is removed in Airflow 3.0; use `schedule` instead
|
15 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
16 |
17 | DAG(dag_id="class_timetable", timetable=NullTimetable())
| ^^^^^^^^^ AIR302
|

AIR302_args.py:24:34: AIR302 `sla_miss_callback` is removed in Airflow 3.0
|
24 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback)
| ^^^^^^^^^^^^^^^^^ AIR302
|

AIR302_args.py:32:6: AIR302 `schedule_interval` is removed in Airflow 3.0; use `schedule` instead
|
32 | @dag(schedule_interval="0 * * * *")
| ^^^^^^^^^^^^^^^^^ AIR302
17 | def decorator_schedule_interval():
18 | pass
33 | def decorator_schedule_interval():
34 | pass
|

AIR302_args.py:21:6: AIR302 `timetable` is removed in Airflow 3.0; use `schedule` instead
AIR302_args.py:37:6: AIR302 `timetable` is removed in Airflow 3.0; use `schedule` instead
|
21 | @dag(timetable=NullTimetable())
37 | @dag(timetable=NullTimetable())
| ^^^^^^^^^ AIR302
22 | def decorator_timetable():
23 | pass
38 | def decorator_timetable():
39 | pass
|

AIR302_args.py:42:6: AIR302 `sla_miss_callback` is removed in Airflow 3.0
|
42 | @dag(sla_miss_callback=sla_callback)
| ^^^^^^^^^^^^^^^^^ AIR302
43 | def decorator_sla_callback():
44 | pass
|

AIR302_args.py:50:39: AIR302 `execution_date` is removed in Airflow 3.0; use `logical_date` instead
|
48 | def decorator_deprecated_operator_args():
49 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
50 | task_id="trigger_dagrun_op1", execution_date="2024-12-04"
| ^^^^^^^^^^^^^^ AIR302
51 | )
52 | trigger_dagrun_op2 = TriggerDagRunOperator(
|

AIR302_args.py:53:39: AIR302 `execution_date` is removed in Airflow 3.0; use `logical_date` instead
|
51 | )
52 | trigger_dagrun_op2 = TriggerDagRunOperator(
53 | task_id="trigger_dagrun_op2", execution_date="2024-12-04"
| ^^^^^^^^^^^^^^ AIR302
54 | )
|

AIR302_args.py:57:33: AIR302 `use_task_execution_day` is removed in Airflow 3.0; use `use_task_logical_date` instead
|
56 | branch_dt_op = datetime.BranchDateTimeOperator(
57 | task_id="branch_dt_op", use_task_execution_day=True
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
58 | )
59 | branch_dt_op2 = BranchDateTimeOperator(
|

AIR302_args.py:60:34: AIR302 `use_task_execution_day` is removed in Airflow 3.0; use `use_task_logical_date` instead
|
58 | )
59 | branch_dt_op2 = BranchDateTimeOperator(
60 | task_id="branch_dt_op2", use_task_execution_day=True
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
61 | )
|
Loading

0 comments on commit 7032218

Please sign in to comment.