[airflow] Flag runtime-varying values in DAG/task constructor arguments (AIR304) (#23631)

## Summary

Add rule AIR304 that flags runtime-varying function calls (e.g.,
`datetime.now()`, `pendulum.now()`, `random.randint()`, `uuid.uuid4()`)
used as arguments in Airflow DAG/task constructors. These calls cause
the serialized DAG hash to change on every parse, creating infinite DAG
versions in the `dag_version` and `serialized_dag` tables, leading to
unbounded database growth and eventual OOM conditions.

Reference: https://github.com/apache/airflow/pull/59430

The rule checks:
- `DAG(...)` constructors and `@dag(...)` decorator calls
- Operator and sensor constructors (via
`is_airflow_builtin_or_provider`)
- `@task(...)` decorator calls

It recursively inspects keyword argument values through binary/unary
ops, dicts, lists, sets, tuples, and f-strings to find calls to known
runtime-varying functions from `datetime`, `pendulum`, `time`, `uuid`,
and `random`.

## Test Plan

`cargo test -p ruff_linter -- airflow::tests` — all 43 tests pass,
including the new AIR304 test case with 17 violation and 6 non-violation
scenarios covering direct calls, binary ops, `default_args` dicts,
f-strings, operators, sensors, decorators, and non-airflow calls.

---
Related: https://github.com/apache/airflow/issues/43176
CC: @Lee-W @sjyangkevin @wjddn279

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dev-iL
2026-03-09 19:59:59 +02:00
committed by GitHub
parent 2e227cdd5b
commit b82853fb12
10 changed files with 623 additions and 4 deletions
@@ -0,0 +1,84 @@
import random
import time
import uuid
from datetime import date, datetime, timedelta
import pendulum
from airflow import DAG, dag
from airflow.decorators import task
from airflow.operators.bash import BashOperator
from airflow.providers.standard.sensors.python import PythonSensor
# Violations
DAG(dag_id="a", start_date=datetime.now())
DAG(dag_id="b", start_date=datetime.now() - timedelta(days=1))
DAG(dag_id="c", default_args={"start_date": datetime.now()})
DAG(dag_id="d", start_date=datetime.utcnow())
DAG(dag_id="e", start_date=datetime.today())
DAG(dag_id="f", start_date=date.today())
DAG(dag_id="g", tags=[f"v{random.randint(1, 9)}"])
DAG(dag_id="h", start_date=pendulum.now())
DAG(dag_id="i", start_date=pendulum.yesterday())
DAG(dag_id="j", start_date=pendulum.tomorrow())
DAG(dag_id="k", start_date=pendulum.today())
DAG(dag_id="l", owner=f"team-{uuid.uuid4()}")
DAG(dag_id="m", description=f"built at {time.time()}")
# Wrapped calls
DAG(dag_id="n", start_date=str(datetime.now()))
DAG(dag_id="o", start_date=int(time.time()))
# Method chain
DAG(dag_id="p", start_date=datetime.now().isoformat())
# Ternary
DAG(dag_id="q", start_date=datetime.now() if True else None)
# BoolOp
DAG(dag_id="r", start_date=None or datetime.now())
# Walrus operator
DAG(dag_id="s", start_date=(x := datetime.now()))
@dag(start_date=pendulum.now())
def my_dag():
pass
BashOperator(task_id="t", bash_command="echo hi", start_date=datetime.today())
PythonSensor(task_id="s", start_date=datetime.now())
@task(start_date=datetime.utcnow())
def my_task():
pass
# Non-violations
DAG(dag_id="ok_a", start_date=datetime(2024, 1, 1))
DAG(dag_id="ok_b", start_date=pendulum.datetime(2024, 1, 1))
DAG(dag_id="ok_c", schedule=timedelta(hours=1))
DAG(dag_id="ok_d", default_args={"retries": 3})
BashOperator(task_id="t_ok", bash_command="echo hello")
@task(retries=3)
def my_static_task():
pass
# Non-airflow function call with dynamic arg — should not trigger
def not_airflow(start_date):
pass
not_airflow(start_date=datetime.now())
# Lambda defers execution — not a violation
DAG(dag_id="ok_e", on_failure_callback=lambda ctx: datetime.now())
@@ -1287,6 +1287,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::Airflow3IncompatibleFunctionSignature) {
airflow::rules::airflow_3_incompatible_function_signature(checker, expr);
}
if checker.is_rule_enabled(Rule::Airflow3DagDynamicValue) {
airflow::rules::airflow_3_dag_dynamic_value(checker, call);
}
if checker.is_rule_enabled(Rule::UnnecessaryCastToInt) {
ruff::rules::unnecessary_cast_to_int(checker, call);
}
+1
View File
@@ -1132,6 +1132,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Airflow, "301") => rules::airflow::rules::Airflow3Removal,
(Airflow, "302") => rules::airflow::rules::Airflow3MovedToProvider,
(Airflow, "303") => rules::airflow::rules::Airflow3IncompatibleFunctionSignature,
(Airflow, "304") => rules::airflow::rules::Airflow3DagDynamicValue,
(Airflow, "311") => rules::airflow::rules::Airflow3SuggestedUpdate,
(Airflow, "312") => rules::airflow::rules::Airflow3SuggestedToMoveToProvider,
(Airflow, "321") => rules::airflow::rules::Airflow31Moved,
@@ -50,6 +50,7 @@ mod tests {
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_standard.py"))]
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_try.py"))]
#[test_case(Rule::Airflow3IncompatibleFunctionSignature, Path::new("AIR303.py"))]
#[test_case(Rule::Airflow3DagDynamicValue, Path::new("AIR304.py"))]
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_args.py"))]
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_names.py"))]
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_try.py"))]
@@ -15,11 +15,11 @@ use crate::checkers::ast::Checker;
/// The default value of the `schedule` parameter on Airflow 2 and
/// `schedule_interval` on Airflow 1 is `timedelta(days=1)`, which is almost
/// never what a user is looking for. Airflow 3 changed the default value to `None`,
/// and would break existing dags using the implicit default.
/// and would break existing Dags using the implicit default.
///
/// If your dag does not have an explicit `schedule` / `schedule_interval` argument,
/// If your Dag does not have an explicit `schedule` / `schedule_interval` argument,
/// Airflow 2 schedules a run for it every day (at the time determined by `start_date`).
/// Such a dag will no longer be scheduled on Airflow 3 at all, without any
/// Such a Dag will no longer be scheduled on Airflow 3 at all, without any
/// exceptions or other messages visible to the user.
///
/// ## Example
@@ -3,6 +3,7 @@ pub(crate) use function_signature_change_in_3::*;
pub(crate) use moved_in_3_1::*;
pub(crate) use moved_to_provider_in_3::*;
pub(crate) use removal_in_3::*;
pub(crate) use runtime_value_in_dag_or_task::*;
pub(crate) use suggested_to_move_to_provider_in_3::*;
pub(crate) use suggested_to_update_3_0::*;
pub(crate) use task_variable_name::*;
@@ -12,6 +13,7 @@ mod function_signature_change_in_3;
mod moved_in_3_1;
mod moved_to_provider_in_3;
mod removal_in_3;
mod runtime_value_in_dag_or_task;
mod suggested_to_move_to_provider_in_3;
mod suggested_to_update_3_0;
mod task_variable_name;
@@ -0,0 +1,290 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr, ExprCall, InterpolatedStringElement};
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::airflow::helpers::is_airflow_builtin_or_provider;
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for calls to runtime-varying functions (such as `datetime.now()`)
/// used as arguments in Airflow Dag or task constructors.
///
/// ## Why is this bad?
/// Using runtime-varying values as arguments to Dag or task constructors
/// causes the serialized Dag hash to change on every parse, creating
/// infinite Dag versions in the `dag_version` and `serialized_dag` tables.
/// This leads to unbounded database growth and can eventually cause
/// out-of-memory conditions.
///
/// ## Example
/// ```python
/// from datetime import datetime
///
/// from airflow import DAG
///
/// dag = DAG(dag_id="my_dag", start_date=datetime.now())
/// ```
///
/// Use instead:
/// ```python
/// from datetime import datetime
///
/// from airflow import DAG
///
/// dag = DAG(dag_id="my_dag", start_date=datetime(2024, 1, 1))
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "NEXT_RUFF_VERSION")]
pub(crate) struct Airflow3DagDynamicValue {
function_name: String,
}
impl Violation for Airflow3DagDynamicValue {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
#[derive_message_formats]
fn message(&self) -> String {
let Airflow3DagDynamicValue { function_name } = self;
format!(
"`{function_name}()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation"
)
}
}
/// AIR304
pub(crate) fn airflow_3_dag_dynamic_value(checker: &Checker, call: &ExprCall) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else {
return;
};
if !is_dag_or_task_constructor(&qualified_name) {
return;
}
for keyword in &call.arguments.keywords {
if let Some((expr, name)) = find_runtime_varying_call(&keyword.value, checker.semantic()) {
checker.report_diagnostic(
Airflow3DagDynamicValue {
function_name: name.to_string(),
},
expr.range(),
);
}
}
}
/// Check if the qualified name refers to a Dag constructor, `@dag` decorator,
/// operator, sensor, or `@task` decorator.
fn is_dag_or_task_constructor(qualified_name: &QualifiedName) -> bool {
let segments = qualified_name.segments();
matches!(segments, ["airflow", .., "DAG" | "dag"])
|| matches!(segments, ["airflow", "decorators" | "sdk", "task"])
|| is_airflow_builtin_or_provider(segments, "operators", "Operator")
|| is_airflow_builtin_or_provider(segments, "sensors", "Sensor")
}
/// Recursively check an expression for calls to known runtime-varying functions.
/// Returns the call expression and a display name (e.g., `"datetime.now"`) if found.
///
/// The set of traversed expression variants mirrors [`any_over_expr`] in
/// `ruff_python_ast::helpers` so that no nested sub-expression is silently skipped.
fn find_runtime_varying_call<'a>(
expr: &'a Expr,
semantic: &SemanticModel,
) -> Option<(&'a Expr, &'static str)> {
match expr {
Expr::Call(ExprCall {
func, arguments, ..
}) => {
if let Some(qualified_name) = semantic.resolve_qualified_name(func) {
let name = match qualified_name.segments() {
["datetime", "datetime", "now"] => Some("datetime.now"),
["datetime", "datetime", "utcnow"] => Some("datetime.utcnow"),
["datetime", "datetime", "today"] => Some("datetime.today"),
["datetime", "date", "today"] => Some("date.today"),
["pendulum", "now"] => Some("pendulum.now"),
["pendulum", "today"] => Some("pendulum.today"),
["pendulum", "yesterday"] => Some("pendulum.yesterday"),
["pendulum", "tomorrow"] => Some("pendulum.tomorrow"),
["time", "time"] => Some("time.time"),
["uuid", "uuid1"] => Some("uuid.uuid1"),
["uuid", "uuid4"] => Some("uuid.uuid4"),
["random", "random"] => Some("random.random"),
["random", "randint"] => Some("random.randint"),
["random", "choice"] => Some("random.choice"),
["random", "uniform"] => Some("random.uniform"),
["random", "randrange"] => Some("random.randrange"),
["random", "sample"] => Some("random.sample"),
["random", "getrandbits"] => Some("random.getrandbits"),
_ => None,
};
if let Some(name) = name {
return Some((expr, name));
}
}
// Recurse into the callee (catches method chains like `datetime.now().isoformat()`)
// and into arguments (catches wrappers like `str(datetime.now())`).
find_runtime_varying_call(func, semantic)
.or_else(|| {
arguments
.args
.iter()
.find_map(|arg| find_runtime_varying_call(arg, semantic))
})
.or_else(|| {
arguments
.keywords
.iter()
.find_map(|kw| find_runtime_varying_call(&kw.value, semantic))
})
}
Expr::BoolOp(ast::ExprBoolOp { values, .. }) => values
.iter()
.find_map(|value| find_runtime_varying_call(value, semantic)),
Expr::Named(ast::ExprNamed { target, value, .. }) => {
find_runtime_varying_call(target, semantic)
.or_else(|| find_runtime_varying_call(value, semantic))
}
Expr::BinOp(ast::ExprBinOp { left, right, .. }) => {
find_runtime_varying_call(left, semantic)
.or_else(|| find_runtime_varying_call(right, semantic))
}
Expr::UnaryOp(ast::ExprUnaryOp { operand, .. }) => {
find_runtime_varying_call(operand, semantic)
}
// Lambda defers execution — `lambda: datetime.now()` does not call
// `now()` at DAG parse time, so traversing into the body would be a
// false positive.
Expr::Lambda(_) => None,
Expr::If(ast::ExprIf {
test, body, orelse, ..
}) => find_runtime_varying_call(test, semantic)
.or_else(|| find_runtime_varying_call(body, semantic))
.or_else(|| find_runtime_varying_call(orelse, semantic)),
Expr::Dict(ast::ExprDict { items, .. }) => items.iter().find_map(|item| {
item.key
.as_ref()
.and_then(|k| find_runtime_varying_call(k, semantic))
.or_else(|| find_runtime_varying_call(&item.value, semantic))
}),
Expr::List(ast::ExprList { elts, .. })
| Expr::Tuple(ast::ExprTuple { elts, .. })
| Expr::Set(ast::ExprSet { elts, .. }) => elts
.iter()
.find_map(|elt| find_runtime_varying_call(elt, semantic)),
Expr::ListComp(ast::ExprListComp {
elt, generators, ..
})
| Expr::SetComp(ast::ExprSetComp {
elt, generators, ..
})
| Expr::Generator(ast::ExprGenerator {
elt, generators, ..
}) => find_runtime_varying_call(elt, semantic).or_else(|| {
generators.iter().find_map(|generator| {
find_runtime_varying_call(&generator.target, semantic)
.or_else(|| find_runtime_varying_call(&generator.iter, semantic))
.or_else(|| {
generator
.ifs
.iter()
.find_map(|e| find_runtime_varying_call(e, semantic))
})
})
}),
Expr::DictComp(ast::ExprDictComp {
key,
value,
generators,
..
}) => find_runtime_varying_call(key, semantic)
.or_else(|| find_runtime_varying_call(value, semantic))
.or_else(|| {
generators.iter().find_map(|generator| {
find_runtime_varying_call(&generator.target, semantic)
.or_else(|| find_runtime_varying_call(&generator.iter, semantic))
.or_else(|| {
generator
.ifs
.iter()
.find_map(|e| find_runtime_varying_call(e, semantic))
})
})
}),
Expr::Await(ast::ExprAwait { value, .. })
| Expr::YieldFrom(ast::ExprYieldFrom { value, .. })
| Expr::Attribute(ast::ExprAttribute { value, .. })
| Expr::Starred(ast::ExprStarred { value, .. }) => {
find_runtime_varying_call(value, semantic)
}
Expr::Yield(ast::ExprYield { value, .. }) => value
.as_ref()
.and_then(|v| find_runtime_varying_call(v, semantic)),
Expr::Compare(ast::ExprCompare {
left, comparators, ..
}) => find_runtime_varying_call(left, semantic).or_else(|| {
comparators
.iter()
.find_map(|c| find_runtime_varying_call(c, semantic))
}),
Expr::FString(ast::ExprFString { value, .. }) => value
.elements()
.find_map(|element| find_runtime_in_interpolated_element(element, semantic)),
Expr::TString(ast::ExprTString { value, .. }) => value
.elements()
.find_map(|element| find_runtime_in_interpolated_element(element, semantic)),
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
find_runtime_varying_call(value, semantic)
.or_else(|| find_runtime_varying_call(slice, semantic))
}
Expr::Slice(ast::ExprSlice {
lower, upper, step, ..
}) => lower
.as_ref()
.and_then(|v| find_runtime_varying_call(v, semantic))
.or_else(|| {
upper
.as_ref()
.and_then(|v| find_runtime_varying_call(v, semantic))
})
.or_else(|| {
step.as_ref()
.and_then(|v| find_runtime_varying_call(v, semantic))
}),
Expr::Name(_)
| Expr::StringLiteral(_)
| Expr::BytesLiteral(_)
| Expr::NumberLiteral(_)
| Expr::BooleanLiteral(_)
| Expr::NoneLiteral(_)
| Expr::EllipsisLiteral(_)
| Expr::IpyEscapeCommand(_) => None,
}
}
/// Check an interpolated string element (f-string or t-string) for runtime-varying calls,
/// including format specifications.
fn find_runtime_in_interpolated_element<'a>(
element: &'a InterpolatedStringElement,
semantic: &SemanticModel,
) -> Option<(&'a Expr, &'static str)> {
match element {
InterpolatedStringElement::Literal(_) => None,
InterpolatedStringElement::Interpolation(interpolation) => {
find_runtime_varying_call(&interpolation.expression, semantic).or_else(|| {
interpolation.format_spec.as_ref().and_then(|spec| {
spec.elements
.iter()
.find_map(|el| find_runtime_in_interpolated_element(el, semantic))
})
})
}
}
}
@@ -14,7 +14,7 @@ use crate::checkers::ast::Checker;
/// ## Why is this bad?
/// When initializing an Airflow Operator, for consistency, the variable
/// name should match the `task_id` value. This makes it easier to
/// follow the flow of the DAG.
/// follow the flow of the Dag.
///
/// ## Example
/// ```python
@@ -0,0 +1,237 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:15:28
|
13 | # Violations
14 |
15 | DAG(dag_id="a", start_date=datetime.now())
| ^^^^^^^^^^^^^^
16 | DAG(dag_id="b", start_date=datetime.now() - timedelta(days=1))
17 | DAG(dag_id="c", default_args={"start_date": datetime.now()})
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:16:28
|
15 | DAG(dag_id="a", start_date=datetime.now())
16 | DAG(dag_id="b", start_date=datetime.now() - timedelta(days=1))
| ^^^^^^^^^^^^^^
17 | DAG(dag_id="c", default_args={"start_date": datetime.now()})
18 | DAG(dag_id="d", start_date=datetime.utcnow())
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:17:45
|
15 | DAG(dag_id="a", start_date=datetime.now())
16 | DAG(dag_id="b", start_date=datetime.now() - timedelta(days=1))
17 | DAG(dag_id="c", default_args={"start_date": datetime.now()})
| ^^^^^^^^^^^^^^
18 | DAG(dag_id="d", start_date=datetime.utcnow())
19 | DAG(dag_id="e", start_date=datetime.today())
|
AIR304 `datetime.utcnow()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:18:28
|
16 | DAG(dag_id="b", start_date=datetime.now() - timedelta(days=1))
17 | DAG(dag_id="c", default_args={"start_date": datetime.now()})
18 | DAG(dag_id="d", start_date=datetime.utcnow())
| ^^^^^^^^^^^^^^^^^
19 | DAG(dag_id="e", start_date=datetime.today())
20 | DAG(dag_id="f", start_date=date.today())
|
AIR304 `datetime.today()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:19:28
|
17 | DAG(dag_id="c", default_args={"start_date": datetime.now()})
18 | DAG(dag_id="d", start_date=datetime.utcnow())
19 | DAG(dag_id="e", start_date=datetime.today())
| ^^^^^^^^^^^^^^^^
20 | DAG(dag_id="f", start_date=date.today())
21 | DAG(dag_id="g", tags=[f"v{random.randint(1, 9)}"])
|
AIR304 `date.today()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:20:28
|
18 | DAG(dag_id="d", start_date=datetime.utcnow())
19 | DAG(dag_id="e", start_date=datetime.today())
20 | DAG(dag_id="f", start_date=date.today())
| ^^^^^^^^^^^^
21 | DAG(dag_id="g", tags=[f"v{random.randint(1, 9)}"])
22 | DAG(dag_id="h", start_date=pendulum.now())
|
AIR304 `random.randint()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:21:27
|
19 | DAG(dag_id="e", start_date=datetime.today())
20 | DAG(dag_id="f", start_date=date.today())
21 | DAG(dag_id="g", tags=[f"v{random.randint(1, 9)}"])
| ^^^^^^^^^^^^^^^^^^^^
22 | DAG(dag_id="h", start_date=pendulum.now())
23 | DAG(dag_id="i", start_date=pendulum.yesterday())
|
AIR304 `pendulum.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:22:28
|
20 | DAG(dag_id="f", start_date=date.today())
21 | DAG(dag_id="g", tags=[f"v{random.randint(1, 9)}"])
22 | DAG(dag_id="h", start_date=pendulum.now())
| ^^^^^^^^^^^^^^
23 | DAG(dag_id="i", start_date=pendulum.yesterday())
24 | DAG(dag_id="j", start_date=pendulum.tomorrow())
|
AIR304 `pendulum.yesterday()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:23:28
|
21 | DAG(dag_id="g", tags=[f"v{random.randint(1, 9)}"])
22 | DAG(dag_id="h", start_date=pendulum.now())
23 | DAG(dag_id="i", start_date=pendulum.yesterday())
| ^^^^^^^^^^^^^^^^^^^^
24 | DAG(dag_id="j", start_date=pendulum.tomorrow())
25 | DAG(dag_id="k", start_date=pendulum.today())
|
AIR304 `pendulum.tomorrow()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:24:28
|
22 | DAG(dag_id="h", start_date=pendulum.now())
23 | DAG(dag_id="i", start_date=pendulum.yesterday())
24 | DAG(dag_id="j", start_date=pendulum.tomorrow())
| ^^^^^^^^^^^^^^^^^^^
25 | DAG(dag_id="k", start_date=pendulum.today())
26 | DAG(dag_id="l", owner=f"team-{uuid.uuid4()}")
|
AIR304 `pendulum.today()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:25:28
|
23 | DAG(dag_id="i", start_date=pendulum.yesterday())
24 | DAG(dag_id="j", start_date=pendulum.tomorrow())
25 | DAG(dag_id="k", start_date=pendulum.today())
| ^^^^^^^^^^^^^^^^
26 | DAG(dag_id="l", owner=f"team-{uuid.uuid4()}")
27 | DAG(dag_id="m", description=f"built at {time.time()}")
|
AIR304 `uuid.uuid4()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:26:31
|
24 | DAG(dag_id="j", start_date=pendulum.tomorrow())
25 | DAG(dag_id="k", start_date=pendulum.today())
26 | DAG(dag_id="l", owner=f"team-{uuid.uuid4()}")
| ^^^^^^^^^^^^
27 | DAG(dag_id="m", description=f"built at {time.time()}")
|
AIR304 `time.time()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:27:41
|
25 | DAG(dag_id="k", start_date=pendulum.today())
26 | DAG(dag_id="l", owner=f"team-{uuid.uuid4()}")
27 | DAG(dag_id="m", description=f"built at {time.time()}")
| ^^^^^^^^^^^
28 |
29 | # Wrapped calls
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:30:32
|
29 | # Wrapped calls
30 | DAG(dag_id="n", start_date=str(datetime.now()))
| ^^^^^^^^^^^^^^
31 | DAG(dag_id="o", start_date=int(time.time()))
|
AIR304 `time.time()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:31:32
|
29 | # Wrapped calls
30 | DAG(dag_id="n", start_date=str(datetime.now()))
31 | DAG(dag_id="o", start_date=int(time.time()))
| ^^^^^^^^^^^
32 |
33 | # Method chain
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:34:28
|
33 | # Method chain
34 | DAG(dag_id="p", start_date=datetime.now().isoformat())
| ^^^^^^^^^^^^^^
35 |
36 | # Ternary
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:37:28
|
36 | # Ternary
37 | DAG(dag_id="q", start_date=datetime.now() if True else None)
| ^^^^^^^^^^^^^^
38 |
39 | # BoolOp
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:40:36
|
39 | # BoolOp
40 | DAG(dag_id="r", start_date=None or datetime.now())
| ^^^^^^^^^^^^^^
41 |
42 | # Walrus operator
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:43:34
|
42 | # Walrus operator
43 | DAG(dag_id="s", start_date=(x := datetime.now()))
| ^^^^^^^^^^^^^^
|
AIR304 `pendulum.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:46:17
|
46 | @dag(start_date=pendulum.now())
| ^^^^^^^^^^^^^^
47 | def my_dag():
48 | pass
|
AIR304 `datetime.today()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:51:62
|
51 | BashOperator(task_id="t", bash_command="echo hi", start_date=datetime.today())
| ^^^^^^^^^^^^^^^^
52 |
53 | PythonSensor(task_id="s", start_date=datetime.now())
|
AIR304 `datetime.now()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:53:38
|
51 | BashOperator(task_id="t", bash_command="echo hi", start_date=datetime.today())
52 |
53 | PythonSensor(task_id="s", start_date=datetime.now())
| ^^^^^^^^^^^^^^
|
AIR304 `datetime.utcnow()` produces a value that changes at runtime; using it in a Dag or task argument causes infinite Dag version creation
--> AIR304.py:56:18
|
56 | @task(start_date=datetime.utcnow())
| ^^^^^^^^^^^^^^^^^
57 | def my_task():
58 | pass
|
+1
View File
@@ -3068,6 +3068,7 @@
"AIR301",
"AIR302",
"AIR303",
"AIR304",
"AIR31",
"AIR311",
"AIR312",