Compare commits

..

2 Commits

Author SHA1 Message Date
David Peter
c366fb642a Docs 2025-01-24 21:52:06 +01:00
David Peter
a3d730bc7c [red-knot] Use T_declared | T_inferred for declared public symbols 2025-01-24 21:33:20 +01:00
13 changed files with 536 additions and 531 deletions

View File

@@ -11,7 +11,7 @@ x: Any = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
reveal_type(x) # revealed: Any | Literal["foo"]
```
## Aliased to a different name
@@ -25,7 +25,7 @@ x: RenamedAny = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
reveal_type(x) # revealed: Any | Literal["foo"]
```
## Shadowed class

View File

@@ -18,7 +18,7 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple
reveal_type(Alias) # revealed: @Todo(Unsupported or invalid type in a type expression)
reveal_type(Alias) # revealed: @Todo(Unsupported or invalid type in a type expression) | Literal[int]
def g() -> TypeGuard[int]: ...
def h() -> TypeIs[int]: ...

View File

@@ -49,12 +49,12 @@ reveal_type(c) # revealed: tuple[str, int]
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
# TODO: homogeneous tuples, PEP-646 tuples
reveal_type(e) # revealed: @Todo(full tuple[...] support)
reveal_type(f) # revealed: @Todo(full tuple[...] support)
reveal_type(g) # revealed: @Todo(full tuple[...] support)
reveal_type(e) # revealed: @Todo(full tuple[...] support) | tuple[()]
reveal_type(f) # revealed: @Todo(full tuple[...] support) | tuple[Literal["42"], Literal[b"42"]]
reveal_type(g) # revealed: @Todo(full tuple[...] support) | tuple[Literal["42"], Literal[b"42"]]
# TODO: support more kinds of type expressions in annotations
reveal_type(h) # revealed: @Todo(full tuple[...] support)
reveal_type(h) # revealed: @Todo(full tuple[...] support) | tuple[list, list]
reveal_type(i) # revealed: tuple[str | int, str | int]
reveal_type(j) # revealed: tuple[str | int]

View File

@@ -175,16 +175,14 @@ class C:
reveal_type(C.pure_class_variable1) # revealed: str
# TODO: Should be `Unknown | Literal[1]`.
reveal_type(C.pure_class_variable2) # revealed: Unknown
reveal_type(C.pure_class_variable2) # revealed: Unknown | Literal[1]
c_instance = C()
# It is okay to access a pure class variable on an instance.
reveal_type(c_instance.pure_class_variable1) # revealed: str
# TODO: Should be `Unknown | Literal[1]`.
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown | Literal[1]
# error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `C`"
c_instance.pure_class_variable1 = "value set on instance"

View File

@@ -1,9 +1,13 @@
# Boundness and declaredness: public uses
This document demonstrates how type-inference and diagnostics works for *public* uses of a symbol,
This document demonstrates how type-inference and diagnostics work for *public* uses of a symbol,
that is, a use of a symbol from another scope. If a symbol has a declared type in its local scope
(e.g. `int`), we use that as the symbol's "public type" (the type of the symbol from the perspective
of other scopes) even if there is a more precise local inferred type for the symbol (`Literal[1]`).
of other scopes). If there is an inferred type in addition (i.e. if we also see bindings for this
symbol, not just declarations), we use `T_decl | T_decl & T_inf` as the public type, which
simplifies to `T_decl` for `T_inf = Unknown` (the unbound case).
[TODO: more explanation]
If a symbol has no declared type, we use the union of `Unknown` with the inferred type as the public
type. If there is no declaration, then the symbol can be reassigned to any type from another scope;
@@ -17,11 +21,11 @@ this behavior is questionable and might change in the future. See the TODOs in `
In particular, we should raise errors in the "possibly-undeclared-and-unbound" as well as the
"undeclared-and-possibly-unbound" cases (marked with a "?").
| **Public type** | declared | possibly-undeclared | undeclared |
| ---------------- | ------------ | -------------------------- | ----------------------- |
| bound | `T_declared` | `T_declared \| T_inferred` | `Unknown \| T_inferred` |
| possibly-unbound | `T_declared` | `T_declared \| T_inferred` | `Unknown \| T_inferred` |
| unbound | `T_declared` | `T_declared` | `Unknown` |
| **Public type** | declared | possibly-undeclared | undeclared |
| ---------------- | -------------------------- | ------------------- | ------------------ |
| bound | `T_decl \| T_decl & T_inf` | `T_decl \| T_inf` | `Unknown \| T_inf` |
| possibly-unbound | `T_decl \| T_decl & T_inf` | `T_decl \| T_inf` | `Unknown \| T_inf` |
| unbound | `T_decl` | `T_decl` | `Unknown` |
| **Diagnostic** | declared | possibly-undeclared | undeclared |
| ---------------- | -------- | ------------------------- | ------------------- |
@@ -37,17 +41,27 @@ If a symbol has a declared type (`int`), we use that even if there is a more pre
(`Literal[1]`), or a conflicting inferred type (`Literal[2]`):
```py path=mod.py
x: int = 1
from typing import Any
def any() -> Any: ...
a: int = 1
# error: [invalid-assignment]
y: str = 2
b: str = 2
c: Any = 3
d: int = any()
```
```py
from mod import x, y
from mod import a, b, c, d
reveal_type(x) # revealed: int
reveal_type(y) # revealed: str
reveal_type(a) # revealed: int
reveal_type(b) # revealed: str
reveal_type(c) # revealed: Any | Literal[3]
reveal_type(d) # revealed: int
```
### Declared and possibly unbound
@@ -56,21 +70,31 @@ If a symbol is declared and *possibly* unbound, we trust that other module and u
without raising an error.
```py path=mod.py
from typing import Any
def any() -> Any: ...
def flag() -> bool: ...
x: int
y: str
a: int
b: str
c: Any
d: int
if flag:
x = 1
a = 1
# error: [invalid-assignment]
y = 2
b = 2
c = 3
d = any()
```
```py
from mod import x, y
from mod import a, b, c, d
reveal_type(x) # revealed: int
reveal_type(y) # revealed: str
reveal_type(a) # revealed: int
reveal_type(b) # revealed: str
reveal_type(c) # revealed: Any | Literal[3]
reveal_type(d) # revealed: int
```
### Declared and unbound
@@ -79,13 +103,17 @@ Similarly, if a symbol is declared but unbound, we do not raise an error. We tru
is available somehow and simply use the declared type.
```py path=mod.py
x: int
from typing import Any
a: int
b: Any
```
```py
from mod import x
from mod import a, b
reveal_type(x) # revealed: int
reveal_type(a) # revealed: int
reveal_type(b) # revealed: Any
```
## Possibly undeclared
@@ -100,56 +128,56 @@ from typing import Any
def flag() -> bool: ...
x = 1
y = 2
z = 3
a = 1
b = 2
c = 3
if flag():
x: int
y: Any
a: int
b: Any
# error: [invalid-declaration]
z: str
c: str
```
```py
from mod import x, y, z
from mod import a, b, c
reveal_type(x) # revealed: int
reveal_type(y) # revealed: Literal[2] | Any
reveal_type(z) # revealed: Literal[3] | Unknown
reveal_type(a) # revealed: int
reveal_type(b) # revealed: Literal[2] | Any
reveal_type(c) # revealed: Literal[3] | Unknown
# External modifications of `x` that violate the declared type are not allowed:
# External modifications of `a` that violate the declared type are not allowed:
# error: [invalid-assignment]
x = None
a = None
```
### Possibly undeclared and possibly unbound
If a symbol is possibly undeclared and possibly unbound, we also use the union of the declared and
inferred types. This case is interesting because the "possibly declared" definition might not be the
same as the "possibly bound" definition (symbol `y`). Note that we raise a `possibly-unbound-import`
error for both `x` and `y`:
same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-unbound-import`
error for both `a` and `b`:
```py path=mod.py
def flag() -> bool: ...
if flag():
x: Any = 1
y = 2
a: Any = 1
b = 2
else:
y: str
b: str
```
```py
# error: [possibly-unbound-import]
# error: [possibly-unbound-import]
from mod import x, y
from mod import a, b
reveal_type(x) # revealed: Literal[1] | Any
reveal_type(y) # revealed: Literal[2] | str
reveal_type(a) # revealed: Literal[1] | Any
reveal_type(b) # revealed: Literal[2] | str
# External modifications of `y` that violate the declared type are not allowed:
# External modifications of `b` that violate the declared type are not allowed:
# error: [invalid-assignment]
y = None
b = None
```
### Possibly undeclared and unbound
@@ -161,19 +189,19 @@ seems inconsistent when compared to the case just above.
def flag() -> bool: ...
if flag():
x: int
a: int
```
```py
# TODO: this should raise an error. Once we fix this, update the section description and the table
# on top of this document.
from mod import x
from mod import a
reveal_type(x) # revealed: int
reveal_type(a) # revealed: int
# External modifications to `x` that violate the declared type are not allowed:
# External modifications to `a` that violate the declared type are not allowed:
# error: [invalid-assignment]
x = None
a = None
```
## Undeclared
@@ -181,16 +209,16 @@ x = None
### Undeclared but bound
```py path=mod.py
x = 1
a = 1
```
```py
from mod import x
from mod import a
reveal_type(x) # revealed: Unknown | Literal[1]
reveal_type(a) # revealed: Unknown | Literal[1]
# All external modifications of `x` are allowed:
x = None
# All external modifications of `a` are allowed:
a = None
```
### Undeclared and possibly unbound
@@ -202,18 +230,18 @@ inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" cas
def flag() -> bool: ...
if flag:
x = 1
a = 1
```
```py
# TODO: this should raise an error. Once we fix this, update the section description and the table
# on top of this document.
from mod import x
from mod import a
reveal_type(x) # revealed: Unknown | Literal[1]
reveal_type(a) # revealed: Unknown | Literal[1]
# All external modifications of `x` are allowed:
x = None
# All external modifications of `a` are allowed:
a = None
```
### Undeclared and unbound
@@ -222,15 +250,15 @@ If a symbol is undeclared *and* unbound, we infer `Unknown` and raise an error.
```py path=mod.py
if False:
x: int = 1
a: int = 1
```
```py
# error: [unresolved-import]
from mod import x
from mod import a
reveal_type(x) # revealed: Unknown
reveal_type(a) # revealed: Unknown
# Modifications allowed in this case:
x = None
a = None
```

View File

@@ -21,8 +21,7 @@ class C:
reveal_type(C.a) # revealed: int
reveal_type(C.b) # revealed: int
reveal_type(C.c) # revealed: int
# TODO: should be Unknown | Literal[1]
reveal_type(C.d) # revealed: Unknown
reveal_type(C.d) # revealed: Unknown | Literal[1]
reveal_type(C.e) # revealed: int
c = C()

View File

@@ -103,6 +103,54 @@ fn widen_type_for_undeclared_public_symbol<'db>(
}
}
/// Computes a possibly more precise public type for a (possibly) declared symbol where
/// we also have an inferred type from visible bindings.
///
/// Below, we build the type `declared_ty | declared_ty & inferred_ty`. This represents
/// the fact that we want to return a type that is no larger than and no smaller than
/// `declared_ty`. If `declared_ty` is a fully static type, this means that we will
/// simply return `declared_ty`. But if `declared_ty` is an arbitrary gradual type, this
/// can make the public type more specific by including information about `inferred_ty`.
///
/// We have a special handling for the following cases, both for performance reasons
/// and to construct the easiest possible representation of a type:
///
/// ```txt
/// inferred_ty = Any/Unknown
///
/// => declared_ty | declared_ty & Any = declared_ty
///
/// declared_ty = Any/Unknown
///
/// => Any | Any & inferred_ty = Any | inferred_ty
/// ```
fn widen_type_for_declared_public_symbol<'db>(
db: &'db dyn Db,
declared_ty: Type<'db>,
inferred: &Symbol<'db>,
) -> Type<'db> {
match inferred.ignore_possibly_unbound() {
Some(inferred_ty) => {
if inferred_ty.is_dynamic() {
declared_ty
} else if declared_ty.is_dynamic() {
UnionType::from_elements(db, [declared_ty, inferred_ty])
} else if declared_ty.is_fully_static(db) {
declared_ty
} else {
IntersectionBuilder::new(db)
.add_positive(UnionType::from_elements(
db,
[declared_ty, inferred_ty].iter().copied(),
))
.add_positive(declared_ty)
.build()
}
}
None => declared_ty,
}
}
/// Infer the public type of a symbol (its type as seen from outside its scope).
fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> {
#[salsa::tracked]
@@ -122,14 +170,17 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db>
let is_final = declared.as_ref().is_ok_and(SymbolAndQualifiers::is_final);
let declared = declared.map(|SymbolAndQualifiers(symbol, _)| symbol);
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, bindings);
match declared {
// Symbol is declared, trust the declared type
Ok(symbol @ Symbol::Type(_, Boundness::Bound)) => symbol,
// Symbol is declared
Ok(Symbol::Type(declared_ty, Boundness::Bound)) => Symbol::Type(
widen_type_for_declared_public_symbol(db, declared_ty, &inferred),
Boundness::Bound,
),
// Symbol is possibly declared
Ok(Symbol::Type(declared_ty, Boundness::PossiblyUnbound)) => {
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, bindings);
match inferred {
// Symbol is possibly undeclared and definitely unbound
Symbol::Unbound => {
@@ -646,6 +697,10 @@ impl<'db> Type<'db> {
matches!(self, Type::Dynamic(DynamicType::Todo(_)))
}
pub const fn is_dynamic(&self) -> bool {
matches!(self, Type::Dynamic(_))
}
pub const fn class_literal(class: Class<'db>) -> Self {
Self::ClassLiteral(ClassLiteralType { class })
}
@@ -4125,8 +4180,12 @@ impl<'db> Class<'db> {
let use_def = use_def_map(db, body_scope);
let declarations = use_def.public_declarations(symbol_id);
let declared = symbol_from_declarations(db, declarations);
match symbol_from_declarations(db, declarations) {
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, bindings);
match declared {
Ok(SymbolAndQualifiers(Symbol::Type(declared_ty, _), qualifiers)) => {
if let Some(function) = declared_ty.into_function_literal() {
// TODO: Eventually, we are going to process all decorators correctly. This is
@@ -4138,13 +4197,16 @@ impl<'db> Class<'db> {
todo_type!("bound method").into()
}
} else {
SymbolAndQualifiers(Symbol::Type(declared_ty, Boundness::Bound), qualifiers)
SymbolAndQualifiers(
Symbol::Type(
widen_type_for_declared_public_symbol(db, declared_ty, &inferred),
Boundness::Bound,
),
qualifiers,
)
}
}
Ok(symbol @ SymbolAndQualifiers(Symbol::Unbound, qualifiers)) => {
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, bindings);
SymbolAndQualifiers(
widen_type_for_undeclared_public_symbol(db, inferred, symbol.is_final()),
qualifiers,

View File

@@ -1,8 +1,7 @@
from __future__ import annotations
from datetime import datetime
import pendulum
from airflow.decorators import dag, task
from airflow.models import DAG
from airflow.models.baseoperator import BaseOperator
@@ -14,22 +13,30 @@ from airflow.utils.context import get_current_context
def access_invalid_key_in_context(**context):
print("access invalid key", context["conf"])
print("access invalid key", context.get("conf"))
@task
def access_invalid_key_task_out_of_dag(**context):
print("access invalid key", context["conf"])
print("access invalid key", context.get("conf"))
@dag(
schedule=None,
start_date=pendulum.datetime(2021, 1, 1, tz="UTC"),
catchup=False,
tags=[""],
)
def invalid_dag():
@task()
def access_invalid_key_task(**context):
print("access invalid key", context.get("conf"))
@task
def access_invalid_argument_task_out_of_dag(
execution_date, tomorrow_ds, logical_date, **context
):
print("execution date", execution_date)
print("access invalid key", context.get("conf"))
task1 = PythonOperator(
task_id="task1",
python_callable=access_invalid_key_in_context,
)
access_invalid_key_task() >> task1
access_invalid_key_task_out_of_dag()
invalid_dag()
@task
def print_config(**context):
@@ -49,9 +56,31 @@ def print_config(**context):
yesterday_ds = context["yesterday_ds"]
yesterday_ds_nodash = context["yesterday_ds_nodash"]
with DAG(
dag_id="example_dag",
schedule_interval="@daily",
start_date=datetime(2023, 1, 1),
template_searchpath=["/templates"],
) as dag:
task1 = DummyOperator(
task_id="task1",
params={
# Removed variables in template
"execution_date": "{{ execution_date }}",
"next_ds": "{{ next_ds }}",
"prev_ds": "{{ prev_ds }}"
},
)
class CustomMacrosPlugin(AirflowPlugin):
name = "custom_macros"
macros = {
"execution_date_macro": lambda context: context["execution_date"],
"next_ds_macro": lambda context: context["next_ds"]
}
@task
def print_config_with_get_current_context():
def print_config():
context = get_current_context()
execution_date = context["execution_date"]
next_ds = context["next_ds"]
@@ -65,74 +94,8 @@ def print_config_with_get_current_context():
yesterday_ds = context["yesterday_ds"]
yesterday_ds_nodash = context["yesterday_ds_nodash"]
@task(task_id="print_the_context")
def print_context(ds=None, **kwargs):
"""Print the Airflow context and ds variable from the context."""
print(ds)
print(kwargs.get("tomorrow_ds"))
c = get_current_context()
c.get("execution_date")
@dag(
schedule=None,
start_date=pendulum.datetime(2021, 1, 1, tz="UTC"),
catchup=False,
tags=[""],
)
def invalid_dag():
@task()
def access_invalid_key_task(**context):
print("access invalid key", context.get("conf"))
@task()
def access_invalid_key_explicit_task(execution_date):
print(execution_date)
task1 = PythonOperator(
task_id="task1",
python_callable=access_invalid_key_in_context,
)
access_invalid_key_task() >> task1
access_invalid_key_explicit_task()
access_invalid_argument_task_out_of_dag()
access_invalid_key_task_out_of_dag()
print_config()
print_config_with_get_current_context()
print_context()
invalid_dag()
with DAG(
dag_id="example_dag",
schedule_interval="@daily",
start_date=datetime(2023, 1, 1),
template_searchpath=["/templates"],
) as dag:
task1 = DummyOperator(
task_id="task1",
params={
# Removed variables in template
"execution_date": "{{ execution_date }}",
"next_ds": "{{ next_ds }}",
"prev_ds": "{{ prev_ds }}",
},
)
class CustomMacrosPlugin(AirflowPlugin):
name = "custom_macros"
macros = {
"execution_date_macro": lambda context: context["execution_date"],
"next_ds_macro": lambda context: context["next_ds"],
}
class CustomOperator(BaseOperator):
def execute(self, next_ds, context):
def execute(self, context):
execution_date = context["execution_date"]
next_ds = context["next_ds"]
next_ds_nodash = context["next_ds_nodash"]
@@ -145,6 +108,18 @@ class CustomOperator(BaseOperator):
yesterday_ds = context["yesterday_ds"]
yesterday_ds_nodash = context["yesterday_ds_nodash"]
@task
def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context):
print("execution date", execution_date)
print("access invalid key", context.get("conf"))
@task(task_id="print_the_context")
def print_context(ds=None, **kwargs):
"""Print the Airflow context and ds variable from the context."""
print(ds)
print(kwargs.get("tomorrow_ds"))
c = get_current_context()
c.get("execution_date")
class CustomOperatorNew(BaseOperator):
def execute(self, context):

View File

@@ -176,7 +176,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::use_pep646_unpack(checker, subscript);
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::airflow_3_removal_expr(checker, expr);
airflow::rules::removed_in_3(checker, expr);
}
pandas_vet::rules::subscript(checker, value, expr);
}
@@ -227,7 +227,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
refurb::rules::regex_flag_alias(checker, expr);
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::airflow_3_removal_expr(checker, expr);
airflow::rules::removed_in_3(checker, expr);
}
if checker.enabled(Rule::Airflow3MovedToProvider) {
airflow::rules::moved_to_provider_in_3(checker, expr);
@@ -311,7 +311,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::airflow_3_removal_expr(checker, expr);
airflow::rules::removed_in_3(checker, expr);
}
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
@@ -449,7 +449,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_pyi::rules::bytestring_attribute(checker, expr);
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::airflow_3_removal_expr(checker, expr);
airflow::rules::removed_in_3(checker, expr);
}
}
Expr::Call(
@@ -1150,7 +1150,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
ruff::rules::unnecessary_regular_expression(checker, call);
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::airflow_3_removal_expr(checker, expr);
airflow::rules::removed_in_3(checker, expr);
}
if checker.enabled(Rule::UnnecessaryCastToInt) {
ruff::rules::unnecessary_cast_to_int(checker, call);

View File

@@ -377,7 +377,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_pytest_style::rules::parameter_with_default_argument(checker, function_def);
}
if checker.enabled(Rule::Airflow3Removal) {
airflow::rules::airflow_3_removal_function_def(checker, function_def);
airflow::rules::removed_in_3_function_def(checker, function_def);
}
if checker.enabled(Rule::NonPEP695GenericFunction) {
pyupgrade::rules::non_pep695_generic_function(checker, function_def);

View File

@@ -80,7 +80,7 @@ enum Replacement {
}
/// AIR302
pub(crate) fn airflow_3_removal_expr(checker: &mut Checker, expr: &Expr) {
pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -117,10 +117,7 @@ pub(crate) fn airflow_3_removal_expr(checker: &mut Checker, expr: &Expr) {
}
/// AIR302
pub(crate) fn airflow_3_removal_function_def(
checker: &mut Checker,
function_def: &StmtFunctionDef,
) {
pub(crate) fn removed_in_3_function_def(checker: &mut Checker, function_def: &StmtFunctionDef) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -157,9 +154,7 @@ const REMOVED_CONTEXT_KEYS: [&str; 12] = [
/// pass
/// ```
fn check_function_parameters(checker: &mut Checker, function_def: &StmtFunctionDef) {
if !is_airflow_task(function_def, checker.semantic())
&& !is_execute_method_inherits_from_airflow_operator(function_def, checker.semantic())
{
if !is_airflow_task(function_def, checker.semantic()) {
return;
}
@@ -1081,35 +1076,3 @@ fn is_airflow_task(function_def: &StmtFunctionDef, semantic: &SemanticModel) ->
})
})
}
/// Check it's "execute" method inherits from Airflow base operator
///
/// For example:
///
/// ```python
/// from airflow.models.baseoperator import BaseOperator
///
/// class CustomOperator(BaseOperator):
/// def execute(self):
/// pass
/// ```
fn is_execute_method_inherits_from_airflow_operator(
function_def: &StmtFunctionDef,
semantic: &SemanticModel,
) -> bool {
if function_def.name.as_str() != "execute" {
return false;
}
let ScopeKind::Class(class_def) = semantic.current_scope().kind else {
return false;
};
class_def.bases().iter().any(|class_base| {
semantic
.resolve_qualified_name(class_base)
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["airflow", .., "BaseOperator"])
})
})
}

View File

@@ -1,338 +1,319 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
snapshot_kind: text
---
AIR302_context.py:22:41: AIR302 `conf` is removed in Airflow 3.0
AIR302_context.py:19:45: AIR302 `conf` is removed in Airflow 3.0
|
20 | @task
21 | def access_invalid_key_task_out_of_dag(**context):
22 | print("access invalid key", context["conf"])
| ^^^^^^ AIR302
23 | print("access invalid key", context.get("conf"))
|
AIR302_context.py:23:45: AIR302 `conf` is removed in Airflow 3.0
|
21 | def access_invalid_key_task_out_of_dag(**context):
22 | print("access invalid key", context["conf"])
23 | print("access invalid key", context.get("conf"))
17 | @task
18 | def access_invalid_key_task_out_of_dag(**context):
19 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR302
20 |
21 | @dag(
|
AIR302_context.py:28:5: AIR302 `execution_date` is removed in Airflow 3.0
AIR302_context.py:30:49: AIR302 `conf` is removed in Airflow 3.0
|
26 | @task
27 | def access_invalid_argument_task_out_of_dag(
28 | execution_date, tomorrow_ds, logical_date, **context
| ^^^^^^^^^^^^^^ AIR302
29 | ):
30 | print("execution date", execution_date)
|
AIR302_context.py:28:21: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
26 | @task
27 | def access_invalid_argument_task_out_of_dag(
28 | execution_date, tomorrow_ds, logical_date, **context
| ^^^^^^^^^^^ AIR302
29 | ):
30 | print("execution date", execution_date)
|
AIR302_context.py:31:45: AIR302 `conf` is removed in Airflow 3.0
|
29 | ):
30 | print("execution date", execution_date)
31 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR302
|
AIR302_context.py:40:30: AIR302 `execution_date` is removed in Airflow 3.0
|
39 | # Removed usage - should trigger violations
40 | execution_date = context["execution_date"]
| ^^^^^^^^^^^^^^^^ AIR302
41 | next_ds = context["next_ds"]
42 | next_ds_nodash = context["next_ds_nodash"]
|
AIR302_context.py:41:23: AIR302 `next_ds` is removed in Airflow 3.0
|
39 | # Removed usage - should trigger violations
40 | execution_date = context["execution_date"]
41 | next_ds = context["next_ds"]
| ^^^^^^^^^ AIR302
42 | next_ds_nodash = context["next_ds_nodash"]
43 | next_execution_date = context["next_execution_date"]
|
AIR302_context.py:42:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0
|
40 | execution_date = context["execution_date"]
41 | next_ds = context["next_ds"]
42 | next_ds_nodash = context["next_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
43 | next_execution_date = context["next_execution_date"]
44 | prev_ds = context["prev_ds"]
|
AIR302_context.py:43:35: AIR302 `next_execution_date` is removed in Airflow 3.0
|
41 | next_ds = context["next_ds"]
42 | next_ds_nodash = context["next_ds_nodash"]
43 | next_execution_date = context["next_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
44 | prev_ds = context["prev_ds"]
45 | prev_ds_nodash = context["prev_ds_nodash"]
|
AIR302_context.py:44:23: AIR302 `prev_ds` is removed in Airflow 3.0
|
42 | next_ds_nodash = context["next_ds_nodash"]
43 | next_execution_date = context["next_execution_date"]
44 | prev_ds = context["prev_ds"]
| ^^^^^^^^^ AIR302
45 | prev_ds_nodash = context["prev_ds_nodash"]
46 | prev_execution_date = context["prev_execution_date"]
|
AIR302_context.py:45:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0
|
43 | next_execution_date = context["next_execution_date"]
44 | prev_ds = context["prev_ds"]
45 | prev_ds_nodash = context["prev_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
46 | prev_execution_date = context["prev_execution_date"]
47 | prev_execution_date_success = context["prev_execution_date_success"]
|
AIR302_context.py:46:35: AIR302 `prev_execution_date` is removed in Airflow 3.0
|
44 | prev_ds = context["prev_ds"]
45 | prev_ds_nodash = context["prev_ds_nodash"]
46 | prev_execution_date = context["prev_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
47 | prev_execution_date_success = context["prev_execution_date_success"]
48 | tomorrow_ds = context["tomorrow_ds"]
|
AIR302_context.py:47:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0
|
45 | prev_ds_nodash = context["prev_ds_nodash"]
46 | prev_execution_date = context["prev_execution_date"]
47 | prev_execution_date_success = context["prev_execution_date_success"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
48 | tomorrow_ds = context["tomorrow_ds"]
49 | yesterday_ds = context["yesterday_ds"]
|
AIR302_context.py:48:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
46 | prev_execution_date = context["prev_execution_date"]
47 | prev_execution_date_success = context["prev_execution_date_success"]
48 | tomorrow_ds = context["tomorrow_ds"]
| ^^^^^^^^^^^^^ AIR302
49 | yesterday_ds = context["yesterday_ds"]
50 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:49:28: AIR302 `yesterday_ds` is removed in Airflow 3.0
|
47 | prev_execution_date_success = context["prev_execution_date_success"]
48 | tomorrow_ds = context["tomorrow_ds"]
49 | yesterday_ds = context["yesterday_ds"]
| ^^^^^^^^^^^^^^ AIR302
50 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:50:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0
|
48 | tomorrow_ds = context["tomorrow_ds"]
49 | yesterday_ds = context["yesterday_ds"]
50 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
|
AIR302_context.py:56:30: AIR302 `execution_date` is removed in Airflow 3.0
|
54 | def print_config_with_get_current_context():
55 | context = get_current_context()
56 | execution_date = context["execution_date"]
| ^^^^^^^^^^^^^^^^ AIR302
57 | next_ds = context["next_ds"]
58 | next_ds_nodash = context["next_ds_nodash"]
|
AIR302_context.py:57:23: AIR302 `next_ds` is removed in Airflow 3.0
|
55 | context = get_current_context()
56 | execution_date = context["execution_date"]
57 | next_ds = context["next_ds"]
| ^^^^^^^^^ AIR302
58 | next_ds_nodash = context["next_ds_nodash"]
59 | next_execution_date = context["next_execution_date"]
|
AIR302_context.py:58:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0
|
56 | execution_date = context["execution_date"]
57 | next_ds = context["next_ds"]
58 | next_ds_nodash = context["next_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
59 | next_execution_date = context["next_execution_date"]
60 | prev_ds = context["prev_ds"]
|
AIR302_context.py:59:35: AIR302 `next_execution_date` is removed in Airflow 3.0
|
57 | next_ds = context["next_ds"]
58 | next_ds_nodash = context["next_ds_nodash"]
59 | next_execution_date = context["next_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
60 | prev_ds = context["prev_ds"]
61 | prev_ds_nodash = context["prev_ds_nodash"]
|
AIR302_context.py:60:23: AIR302 `prev_ds` is removed in Airflow 3.0
|
58 | next_ds_nodash = context["next_ds_nodash"]
59 | next_execution_date = context["next_execution_date"]
60 | prev_ds = context["prev_ds"]
| ^^^^^^^^^ AIR302
61 | prev_ds_nodash = context["prev_ds_nodash"]
62 | prev_execution_date = context["prev_execution_date"]
|
AIR302_context.py:61:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0
|
59 | next_execution_date = context["next_execution_date"]
60 | prev_ds = context["prev_ds"]
61 | prev_ds_nodash = context["prev_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
62 | prev_execution_date = context["prev_execution_date"]
63 | prev_execution_date_success = context["prev_execution_date_success"]
|
AIR302_context.py:62:35: AIR302 `prev_execution_date` is removed in Airflow 3.0
|
60 | prev_ds = context["prev_ds"]
61 | prev_ds_nodash = context["prev_ds_nodash"]
62 | prev_execution_date = context["prev_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
63 | prev_execution_date_success = context["prev_execution_date_success"]
64 | tomorrow_ds = context["tomorrow_ds"]
|
AIR302_context.py:63:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0
|
61 | prev_ds_nodash = context["prev_ds_nodash"]
62 | prev_execution_date = context["prev_execution_date"]
63 | prev_execution_date_success = context["prev_execution_date_success"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
64 | tomorrow_ds = context["tomorrow_ds"]
65 | yesterday_ds = context["yesterday_ds"]
|
AIR302_context.py:64:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
62 | prev_execution_date = context["prev_execution_date"]
63 | prev_execution_date_success = context["prev_execution_date_success"]
64 | tomorrow_ds = context["tomorrow_ds"]
| ^^^^^^^^^^^^^ AIR302
65 | yesterday_ds = context["yesterday_ds"]
66 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:65:28: AIR302 `yesterday_ds` is removed in Airflow 3.0
|
63 | prev_execution_date_success = context["prev_execution_date_success"]
64 | tomorrow_ds = context["tomorrow_ds"]
65 | yesterday_ds = context["yesterday_ds"]
| ^^^^^^^^^^^^^^ AIR302
66 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:66:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0
|
64 | tomorrow_ds = context["tomorrow_ds"]
65 | yesterday_ds = context["yesterday_ds"]
66 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
|
AIR302_context.py:73:22: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
71 | """Print the Airflow context and ds variable from the context."""
72 | print(ds)
73 | print(kwargs.get("tomorrow_ds"))
| ^^^^^^^^^^^^^ AIR302
74 | c = get_current_context()
75 | c.get("execution_date")
|
AIR302_context.py:75:11: AIR302 `execution_date` is removed in Airflow 3.0
|
73 | print(kwargs.get("tomorrow_ds"))
74 | c = get_current_context()
75 | c.get("execution_date")
| ^^^^^^^^^^^^^^^^ AIR302
|
AIR302_context.py:87:49: AIR302 `conf` is removed in Airflow 3.0
|
85 | @task()
86 | def access_invalid_key_task(**context):
87 | print("access invalid key", context.get("conf"))
28 | @task()
29 | def access_invalid_key_task(**context):
30 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR302
88 |
89 | @task()
31 |
32 | task1 = PythonOperator(
|
AIR302_context.py:90:42: AIR302 `execution_date` is removed in Airflow 3.0
AIR302_context.py:47:30: AIR302 `execution_date` is removed in Airflow 3.0
|
89 | @task()
90 | def access_invalid_key_explicit_task(execution_date):
| ^^^^^^^^^^^^^^ AIR302
91 | print(execution_date)
46 | # Removed usage - should trigger violations
47 | execution_date = context["execution_date"]
| ^^^^^^^^^^^^^^^^ AIR302
48 | next_ds = context["next_ds"]
49 | next_ds_nodash = context["next_ds_nodash"]
|
AIR302_context.py:111:5: AIR302 [*] `schedule_interval` is removed in Airflow 3.0
|
109 | with DAG(
110 | dag_id="example_dag",
111 | schedule_interval="@daily",
| ^^^^^^^^^^^^^^^^^ AIR302
112 | start_date=datetime(2023, 1, 1),
113 | template_searchpath=["/templates"],
|
= help: Use `schedule` instead
AIR302_context.py:48:23: AIR302 `next_ds` is removed in Airflow 3.0
|
46 | # Removed usage - should trigger violations
47 | execution_date = context["execution_date"]
48 | next_ds = context["next_ds"]
| ^^^^^^^^^ AIR302
49 | next_ds_nodash = context["next_ds_nodash"]
50 | next_execution_date = context["next_execution_date"]
|
AIR302_context.py:49:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0
|
47 | execution_date = context["execution_date"]
48 | next_ds = context["next_ds"]
49 | next_ds_nodash = context["next_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
50 | next_execution_date = context["next_execution_date"]
51 | prev_ds = context["prev_ds"]
|
AIR302_context.py:50:35: AIR302 `next_execution_date` is removed in Airflow 3.0
|
48 | next_ds = context["next_ds"]
49 | next_ds_nodash = context["next_ds_nodash"]
50 | next_execution_date = context["next_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
51 | prev_ds = context["prev_ds"]
52 | prev_ds_nodash = context["prev_ds_nodash"]
|
AIR302_context.py:51:23: AIR302 `prev_ds` is removed in Airflow 3.0
|
49 | next_ds_nodash = context["next_ds_nodash"]
50 | next_execution_date = context["next_execution_date"]
51 | prev_ds = context["prev_ds"]
| ^^^^^^^^^ AIR302
52 | prev_ds_nodash = context["prev_ds_nodash"]
53 | prev_execution_date = context["prev_execution_date"]
|
AIR302_context.py:52:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0
|
50 | next_execution_date = context["next_execution_date"]
51 | prev_ds = context["prev_ds"]
52 | prev_ds_nodash = context["prev_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
53 | prev_execution_date = context["prev_execution_date"]
54 | prev_execution_date_success = context["prev_execution_date_success"]
|
AIR302_context.py:53:35: AIR302 `prev_execution_date` is removed in Airflow 3.0
|
51 | prev_ds = context["prev_ds"]
52 | prev_ds_nodash = context["prev_ds_nodash"]
53 | prev_execution_date = context["prev_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
54 | prev_execution_date_success = context["prev_execution_date_success"]
55 | tomorrow_ds = context["tomorrow_ds"]
|
AIR302_context.py:54:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0
|
52 | prev_ds_nodash = context["prev_ds_nodash"]
53 | prev_execution_date = context["prev_execution_date"]
54 | prev_execution_date_success = context["prev_execution_date_success"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
55 | tomorrow_ds = context["tomorrow_ds"]
56 | yesterday_ds = context["yesterday_ds"]
|
AIR302_context.py:55:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
53 | prev_execution_date = context["prev_execution_date"]
54 | prev_execution_date_success = context["prev_execution_date_success"]
55 | tomorrow_ds = context["tomorrow_ds"]
| ^^^^^^^^^^^^^ AIR302
56 | yesterday_ds = context["yesterday_ds"]
57 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:56:28: AIR302 `yesterday_ds` is removed in Airflow 3.0
|
54 | prev_execution_date_success = context["prev_execution_date_success"]
55 | tomorrow_ds = context["tomorrow_ds"]
56 | yesterday_ds = context["yesterday_ds"]
| ^^^^^^^^^^^^^^ AIR302
57 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:57:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0
|
55 | tomorrow_ds = context["tomorrow_ds"]
56 | yesterday_ds = context["yesterday_ds"]
57 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
58 |
59 | with DAG(
|
AIR302_context.py:61:5: AIR302 [*] `schedule_interval` is removed in Airflow 3.0
|
59 | with DAG(
60 | dag_id="example_dag",
61 | schedule_interval="@daily",
| ^^^^^^^^^^^^^^^^^ AIR302
62 | start_date=datetime(2023, 1, 1),
63 | template_searchpath=["/templates"],
|
= help: Use `schedule` instead
Safe fix
108 108 |
109 109 | with DAG(
110 110 | dag_id="example_dag",
111 |- schedule_interval="@daily",
111 |+ schedule="@daily",
112 112 | start_date=datetime(2023, 1, 1),
113 113 | template_searchpath=["/templates"],
114 114 | ) as dag:
58 58 |
59 59 | with DAG(
60 60 | dag_id="example_dag",
61 |- schedule_interval="@daily",
61 |+ schedule="@daily",
62 62 | start_date=datetime(2023, 1, 1),
63 63 | template_searchpath=["/templates"],
64 64 | ) as dag:
AIR302_context.py:115:13: AIR302 `airflow.operators.dummy.DummyOperator` is removed in Airflow 3.0
|
113 | template_searchpath=["/templates"],
114 | ) as dag:
115 | task1 = DummyOperator(
| ^^^^^^^^^^^^^ AIR302
116 | task_id="task1",
117 | params={
|
= help: Use `airflow.operators.empty.EmptyOperator` instead
AIR302_context.py:65:13: AIR302 `airflow.operators.dummy.DummyOperator` is removed in Airflow 3.0
|
63 | template_searchpath=["/templates"],
64 | ) as dag:
65 | task1 = DummyOperator(
| ^^^^^^^^^^^^^ AIR302
66 | task_id="task1",
67 | params={
|
= help: Use `airflow.operators.empty.EmptyOperator` instead
AIR302_context.py:135:23: AIR302 `next_ds` is removed in Airflow 3.0
AIR302_context.py:85:30: AIR302 `execution_date` is removed in Airflow 3.0
|
83 | def print_config():
84 | context = get_current_context()
85 | execution_date = context["execution_date"]
| ^^^^^^^^^^^^^^^^ AIR302
86 | next_ds = context["next_ds"]
87 | next_ds_nodash = context["next_ds_nodash"]
|
AIR302_context.py:86:23: AIR302 `next_ds` is removed in Airflow 3.0
|
84 | context = get_current_context()
85 | execution_date = context["execution_date"]
86 | next_ds = context["next_ds"]
| ^^^^^^^^^ AIR302
87 | next_ds_nodash = context["next_ds_nodash"]
88 | next_execution_date = context["next_execution_date"]
|
AIR302_context.py:87:30: AIR302 `next_ds_nodash` is removed in Airflow 3.0
|
85 | execution_date = context["execution_date"]
86 | next_ds = context["next_ds"]
87 | next_ds_nodash = context["next_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
88 | next_execution_date = context["next_execution_date"]
89 | prev_ds = context["prev_ds"]
|
AIR302_context.py:88:35: AIR302 `next_execution_date` is removed in Airflow 3.0
|
86 | next_ds = context["next_ds"]
87 | next_ds_nodash = context["next_ds_nodash"]
88 | next_execution_date = context["next_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
89 | prev_ds = context["prev_ds"]
90 | prev_ds_nodash = context["prev_ds_nodash"]
|
AIR302_context.py:89:23: AIR302 `prev_ds` is removed in Airflow 3.0
|
87 | next_ds_nodash = context["next_ds_nodash"]
88 | next_execution_date = context["next_execution_date"]
89 | prev_ds = context["prev_ds"]
| ^^^^^^^^^ AIR302
90 | prev_ds_nodash = context["prev_ds_nodash"]
91 | prev_execution_date = context["prev_execution_date"]
|
AIR302_context.py:90:30: AIR302 `prev_ds_nodash` is removed in Airflow 3.0
|
88 | next_execution_date = context["next_execution_date"]
89 | prev_ds = context["prev_ds"]
90 | prev_ds_nodash = context["prev_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR302
91 | prev_execution_date = context["prev_execution_date"]
92 | prev_execution_date_success = context["prev_execution_date_success"]
|
AIR302_context.py:91:35: AIR302 `prev_execution_date` is removed in Airflow 3.0
|
89 | prev_ds = context["prev_ds"]
90 | prev_ds_nodash = context["prev_ds_nodash"]
91 | prev_execution_date = context["prev_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
92 | prev_execution_date_success = context["prev_execution_date_success"]
93 | tomorrow_ds = context["tomorrow_ds"]
|
AIR302_context.py:92:43: AIR302 `prev_execution_date_success` is removed in Airflow 3.0
|
90 | prev_ds_nodash = context["prev_ds_nodash"]
91 | prev_execution_date = context["prev_execution_date"]
92 | prev_execution_date_success = context["prev_execution_date_success"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
93 | tomorrow_ds = context["tomorrow_ds"]
94 | yesterday_ds = context["yesterday_ds"]
|
AIR302_context.py:93:27: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
91 | prev_execution_date = context["prev_execution_date"]
92 | prev_execution_date_success = context["prev_execution_date_success"]
93 | tomorrow_ds = context["tomorrow_ds"]
| ^^^^^^^^^^^^^ AIR302
94 | yesterday_ds = context["yesterday_ds"]
95 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:94:28: AIR302 `yesterday_ds` is removed in Airflow 3.0
|
92 | prev_execution_date_success = context["prev_execution_date_success"]
93 | tomorrow_ds = context["tomorrow_ds"]
94 | yesterday_ds = context["yesterday_ds"]
| ^^^^^^^^^^^^^^ AIR302
95 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR302_context.py:95:35: AIR302 `yesterday_ds_nodash` is removed in Airflow 3.0
|
93 | tomorrow_ds = context["tomorrow_ds"]
94 | yesterday_ds = context["yesterday_ds"]
95 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
96 |
97 | class CustomOperator(BaseOperator):
|
AIR302_context.py:112:45: AIR302 `execution_date` is removed in Airflow 3.0
|
134 | class CustomOperator(BaseOperator):
135 | def execute(self, next_ds, context):
| ^^^^^^^ AIR302
136 | execution_date = context["execution_date"]
137 | next_ds = context["next_ds"]
111 | @task
112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context):
| ^^^^^^^^^^^^^^ AIR302
113 | print("execution date", execution_date)
114 | print("access invalid key", context.get("conf"))
|
AIR302_context.py:112:61: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
111 | @task
112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context):
| ^^^^^^^^^^^ AIR302
113 | print("execution date", execution_date)
114 | print("access invalid key", context.get("conf"))
|
AIR302_context.py:114:45: AIR302 `conf` is removed in Airflow 3.0
|
112 | def access_invalid_argument_task_out_of_dag(execution_date, tomorrow_ds, logical_date, **context):
113 | print("execution date", execution_date)
114 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR302
115 |
116 | @task(task_id="print_the_context")
|
AIR302_context.py:120:22: AIR302 `tomorrow_ds` is removed in Airflow 3.0
|
118 | """Print the Airflow context and ds variable from the context."""
119 | print(ds)
120 | print(kwargs.get("tomorrow_ds"))
| ^^^^^^^^^^^^^ AIR302
121 | c = get_current_context()
122 | c.get("execution_date")
|
AIR302_context.py:122:11: AIR302 `execution_date` is removed in Airflow 3.0
|
120 | print(kwargs.get("tomorrow_ds"))
121 | c = get_current_context()
122 | c.get("execution_date")
| ^^^^^^^^^^^^^^^^ AIR302
123 |
124 | class CustomOperatorNew(BaseOperator):
|

View File

@@ -8,14 +8,13 @@ your project. For a more detailed overview, see [_Configuring Ruff_](configurati
To start, we'll initialize a project using [uv](https://docs.astral.sh/uv/):
```console
$ uv init --lib numbers
$ uv init numbers
```
This command creates a Python project with the following structure:
```text
numbers
├── README.md
├── pyproject.toml
└── src
└── numbers