Compare commits
2 Commits
extend-air
...
david/unio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c366fb642a | ||
|
|
a3d730bc7c |
@@ -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
|
||||
|
||||
@@ -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]: ...
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user