Skip to content

Commit

Permalink
Merge branch 'main' into dcreager/typing-any
Browse files Browse the repository at this point in the history
* main:
  [red-knot] Test: Hashable/Sized => A/B (#14769)
  [`flake8-type-checking`] Expands TC006 docs to better explain itself (#14749)
  [`pycodestyle`] Handle f-strings properly for `invalid-escape-sequence (W605)` (#14748)
  [red-knot] Add fuzzer to catch panics for invalid syntax (#14678)
  Check `AIR001` from builtin or providers `operators` module (#14631)
  [airflow]: extend removed names (AIR302) (#14734)
  • Loading branch information
dcreager committed Dec 4, 2024
2 parents 0ed09b6 + 948549f commit e47e3f4
Show file tree
Hide file tree
Showing 19 changed files with 722 additions and 167 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
# Flag that is raised when any code is changed
# This is superset of the linter and formatter
code: ${{ steps.changed.outputs.code_any_changed }}
# Flag that is raised when any code that affects the fuzzer is changed
fuzz: ${{ steps.changed.outputs.fuzz_any_changed }}
steps:
- uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -79,6 +81,11 @@ jobs:
- python/**
- .github/workflows/ci.yaml
fuzz:
- fuzz/Cargo.toml
- fuzz/Cargo.lock
- fuzz/fuzz_targets/**
code:
- "**/*"
- "!**/*.md"
Expand Down Expand Up @@ -288,7 +295,7 @@ jobs:
name: "cargo fuzz build"
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ github.ref == 'refs/heads/main' }}
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
Expand Down
99 changes: 62 additions & 37 deletions crates/red_knot_python_semantic/src/types/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ mod tests {
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::stdlib::typing_symbol;
use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder};
use crate::ProgramSettings;
use ruff_db::files::system_path_to_file;
Expand Down Expand Up @@ -626,59 +625,85 @@ mod tests {

#[test]
fn intersection_negation_distributes_over_union() {
let db = setup_db();
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
let ht = typing_symbol(&db, "Hashable")
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
r#"
class A: ...
class B: ...
"#,
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();

let a = global_symbol(&db, module, "A")
.expect_type()
.to_instance(&db);
let b = global_symbol(&db, module, "B")
.expect_type()
.to_instance(&db);
// sh_t: Sized & Hashable
let sh_t = IntersectionBuilder::new(&db)
.add_positive(st)
.add_positive(ht)

// intersection: A & B
let intersection = IntersectionBuilder::new(&db)
.add_positive(a)
.add_positive(b)
.build()
.expect_intersection();
assert_eq!(sh_t.pos_vec(&db), &[st, ht]);
assert_eq!(sh_t.neg_vec(&db), &[]);
assert_eq!(intersection.pos_vec(&db), &[a, b]);
assert_eq!(intersection.neg_vec(&db), &[]);

// ~sh_t => ~Sized | ~Hashable
let not_s_h_t = IntersectionBuilder::new(&db)
.add_negative(Type::Intersection(sh_t))
// ~intersection => ~A | ~B
let negated_intersection = IntersectionBuilder::new(&db)
.add_negative(Type::Intersection(intersection))
.build()
.expect_union();

// should have as elements: (~Sized),(~Hashable)
let not_st = st.negate(&db);
let not_ht = ht.negate(&db);
assert_eq!(not_s_h_t.elements(&db), &[not_st, not_ht]);
// should have as elements ~A and ~B
let not_a = a.negate(&db);
let not_b = b.negate(&db);
assert_eq!(negated_intersection.elements(&db), &[not_a, not_b]);
}

#[test]
fn mixed_intersection_negation_distributes_over_union() {
let db = setup_db();
let it = KnownClass::Int.to_instance(&db);
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
let ht = typing_symbol(&db, "Hashable")
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
r#"
class A: ...
class B: ...
"#,
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();

let a = global_symbol(&db, module, "A")
.expect_type()
.to_instance(&db);
let b = global_symbol(&db, module, "B")
.expect_type()
.to_instance(&db);
// s_not_h_t: Sized & ~Hashable
let s_not_h_t = IntersectionBuilder::new(&db)
.add_positive(st)
.add_negative(ht)
let int = KnownClass::Int.to_instance(&db);

// a_not_b: A & ~B
let a_not_b = IntersectionBuilder::new(&db)
.add_positive(a)
.add_negative(b)
.build()
.expect_intersection();
assert_eq!(s_not_h_t.pos_vec(&db), &[st]);
assert_eq!(s_not_h_t.neg_vec(&db), &[ht]);

// let's build int & ~(Sized & ~Hashable)
let tt = IntersectionBuilder::new(&db)
.add_positive(it)
.add_negative(Type::Intersection(s_not_h_t))
assert_eq!(a_not_b.pos_vec(&db), &[a]);
assert_eq!(a_not_b.neg_vec(&db), &[b]);

// let's build
// int & ~(A & ~B)
// = int & ~(A & ~B)
// = int & (~A | B)
// = (int & ~A) | (int & B)
let t = IntersectionBuilder::new(&db)
.add_positive(int)
.add_negative(Type::Intersection(a_not_b))
.build();

// int & ~(Sized & ~Hashable)
// -> int & (~Sized | Hashable)
// -> (int & ~Sized) | (int & Hashable)
assert_eq!(tt.display(&db).to_string(), "int & ~Sized | int & Hashable");
assert_eq!(t.display(&db).to_string(), "int & ~A | int & B");
}

#[test]
Expand Down
16 changes: 11 additions & 5 deletions crates/ruff_linter/resources/test/fixtures/airflow/AIR001.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
from airflow.operators import PythonOperator
from airflow.providers.airbyte.operators.airbyte import AirbyteTriggerSyncOperator
from airflow.providers.amazon.aws.operators.appflow import AppflowFlowRunOperator


def my_callable():
pass


my_task = PythonOperator(task_id="my_task", callable=my_callable)
my_task_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
incorrect_name = PythonOperator(task_id="my_task") # AIR001

incorrect_name = PythonOperator(task_id="my_task")
incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
my_task = AirbyteTriggerSyncOperator(task_id="my_task", callable=my_callable)
incorrect_name = AirbyteTriggerSyncOperator(task_id="my_task") # AIR001

from my_module import MyClass
my_task = AppflowFlowRunOperator(task_id="my_task", callable=my_callable)
incorrect_name = AppflowFlowRunOperator(task_id="my_task") # AIR001

incorrect_name = MyClass(task_id="my_task")
# Consider only from the `airflow.operators` (or providers operators) module
from airflow import MyOperator

incorrect_name = MyOperator(task_id="my_task")
46 changes: 43 additions & 3 deletions crates/ruff_linter/resources/test/fixtures/airflow/AIR302_names.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
from airflow.triggers.external_task import TaskStateTrigger
from airflow.www.auth import has_access
from airflow.api_connexion.security import requires_access
from airflow.metrics.validators import AllowListValidator
from airflow.metrics.validators import BlockListValidator
from airflow.utils import dates
from airflow.utils.dates import date_range, datetime_to_nano, days_ago
from airflow.utils.dates import (
date_range,
datetime_to_nano,
days_ago,
infer_time_unit,
parse_execution_date,
round_time,
scale_time_units,
)
from airflow.utils.file import TemporaryDirectory, mkdirs
from airflow.utils.state import SHUTDOWN, terminating_states
from airflow.utils.dag_cycle_tester import test_cycle


TaskStateTrigger

date_range
days_ago

has_access
requires_access

AllowListValidator
BlockListValidator

dates.date_range
dates.days_ago

date_range
days_ago
parse_execution_date
round_time
scale_time_units
infer_time_unit


# This one was not deprecated.
datetime_to_nano
dates.datetime_to_nano

TemporaryDirectory
mkdirs

SHUTDOWN
terminating_states


test_cycle
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@
f"{{}}+-\d"
f"\n{{}}+-\d+"
f"\n{{}}�+-\d+"

# See https://github.com/astral-sh/ruff/issues/11491
total = 10
ok = 7
incomplete = 3
s = f"TOTAL: {total}\nOK: {ok}\INCOMPLETE: {incomplete}\n"

# Debug text (should trigger)
t = f"{'\InHere'=}"
6 changes: 1 addition & 5 deletions crates/ruff_linter/src/checkers/ast/analyze/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,11 +1554,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
.rules
.enabled(Rule::AirflowVariableNameTaskIdMismatch)
{
if let Some(diagnostic) =
airflow::rules::variable_name_task_id(checker, targets, value)
{
checker.diagnostics.push(diagnostic);
}
airflow::rules::variable_name_task_id(checker, targets, value);
}
if checker.settings.rules.enabled(Rule::SelfAssigningVariable) {
pylint::rules::self_assignment(checker, assign);
Expand Down
68 changes: 65 additions & 3 deletions crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl Violation for Airflow3Removal {
match replacement {
Replacement::None => format!("`{deprecated}` is removed in Airflow 3.0"),
Replacement::Name(name) => {
format!("`{deprecated}` is removed in Airflow 3.0; use {name} instead")
format!("`{deprecated}` is removed in Airflow 3.0; use `{name}` instead")
}
}
}
Expand Down Expand Up @@ -103,13 +103,75 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
.semantic()
.resolve_qualified_name(expr)
.and_then(|qualname| match qualname.segments() {
["airflow", "utils", "dates", "date_range"] => {
["airflow", "triggers", "external_task", "TaskStateTrigger"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "www", "auth", "has_access"] => Some((
qualname.to_string(),
Replacement::Name("airflow.www.auth.has_access_*".to_string()),
)),
["airflow", "api_connexion", "security", "requires_access"] => Some((
qualname.to_string(),
Replacement::Name(
"airflow.api_connexion.security.requires_access_*".to_string(),
),
)),
// airflow.metrics.validators
["airflow", "metrics", "validators", "AllowListValidator"] => Some((
qualname.to_string(),
Replacement::Name(
"airflow.metrics.validators.PatternAllowListValidator".to_string(),
),
)),
["airflow", "metrics", "validators", "BlockListValidator"] => Some((
qualname.to_string(),
Replacement::Name(
"airflow.metrics.validators.PatternBlockListValidator".to_string(),
),
)),
// airflow.utils.dates
["airflow", "utils", "dates", "date_range"] => Some((
qualname.to_string(),
Replacement::Name("airflow.timetables.".to_string()),
)),
["airflow", "utils", "dates", "days_ago"] => Some((
qualname.to_string(),
Replacement::Name("datetime.timedelta()".to_string()),
Replacement::Name("pendulum.today('UTC').add(days=-N, ...)".to_string()),
)),
["airflow", "utils", "dates", "parse_execution_date"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "dates", "round_time"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "dates", "scale_time_units"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "dates", "infer_time_unit"] => {
Some((qualname.to_string(), Replacement::None))
}
// airflow.utils.file
["airflow", "utils", "file", "TemporaryDirectory"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "file", "mkdirs"] => Some((
qualname.to_string(),
Replacement::Name("pendulum.today('UTC').add(days=-N, ...)".to_string()),
)),
// airflow.utils.state
["airflow", "utils", "state", "SHUTDOWN"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "state", "terminating_states"] => {
Some((qualname.to_string(), Replacement::None))
}
// airflow.uilts
["airflow", "utils", "dag_cycle_tester", "test_cycle"] => {
Some((qualname.to_string(), Replacement::None))
}
["airflow", "utils", "decorators", "apply_defaults"] => {
Some((qualname.to_string(), Replacement::None))
}
_ => None,
});
if let Some((deprecated, replacement)) = result {
Expand Down
Loading

0 comments on commit e47e3f4

Please sign in to comment.