Skip to content

Commit 5611425

Browse files
dheerajturagajason810496
authored andcommitted
Default logical_date to now in airflowctl dagrun trigger to match UI behavior (apache#61047)
* Default logical_date to now in airflowctl dagrun trigger to match UI behavior When triggering DAG runs via airflowctl without specifying --logical-date, the parameter now defaults to the current timestamp instead of None. This aligns with the Airflow UI behavior where the trigger form pre-populates logical_date with the current time, providing a more intuitive user experience. * Add tests
1 parent 393b26a commit 5611425

3 files changed

Lines changed: 60 additions & 0 deletions

File tree

airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def date_param():
8686
"dags list-warning",
8787
# Order of trigger and pause/unpause is important for test stability because state checked
8888
f"dags trigger --dag-id=example_bash_operator --logical-date={ONE_DATE_PARAM} --run-after={ONE_DATE_PARAM}",
89+
# Test trigger without logical-date (should default to now)
90+
"dags trigger --dag-id=example_bash_operator",
8991
"dags pause example_bash_operator",
9092
"dags unpause example_bash_operator",
9193
# DAG Run commands

airflow-ctl/src/airflowctl/ctl/cli_config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,16 @@ def _get_func(args: Namespace, api_operation: dict, api_client: Client = NEW_API
621621

622622
if datamodel:
623623
if datamodel_param_name:
624+
# Special handling for TriggerDAGRunPostBody: default logical_date to now
625+
# This matches the Airflow UI behavior where the form pre-fills with current time
626+
if (
627+
datamodel.__name__ == "TriggerDAGRunPostBody"
628+
and "logical_date" in method_params[datamodel_param_name]
629+
and method_params[datamodel_param_name]["logical_date"] is None
630+
):
631+
method_params[datamodel_param_name]["logical_date"] = datetime.datetime.now(
632+
datetime.timezone.utc
633+
)
624634
method_params[datamodel_param_name] = datamodel.model_validate(
625635
method_params[datamodel_param_name]
626636
)

airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,51 @@ def test_merge_commands(self, no_op_method):
355355
assert "subcommand2" in sub_command_names
356356
assert "subcommand3" in sub_command_names
357357
assert "subcommand4" in sub_command_names
358+
359+
def test_trigger_dag_run_defaults_logical_date_to_now(self):
360+
"""Test that trigger command defaults logical_date to now when not provided."""
361+
from datetime import datetime, timezone
362+
363+
from airflowctl.api.datamodels.generated import TriggerDAGRunPostBody
364+
365+
# Simulate the logic in _get_func from cli_config.py
366+
# This is the actual code path that runs when user doesn't provide --logical-date
367+
368+
# Step 1: Simulate CLI args being parsed (logical_date=None)
369+
method_params = {
370+
"trigger_dag_run": {
371+
"dag_run_id": None,
372+
"data_interval_start": None,
373+
"data_interval_end": None,
374+
"logical_date": None, # User did not provide --logical-date
375+
"run_after": None,
376+
"conf": None,
377+
"note": None,
378+
"partition_key": None,
379+
}
380+
}
381+
382+
# Step 2: Apply the defaulting logic (from cli_config.py lines 622-630)
383+
datamodel = TriggerDAGRunPostBody
384+
datamodel_param_name = "trigger_dag_run"
385+
386+
if (
387+
datamodel.__name__ == "TriggerDAGRunPostBody"
388+
and "logical_date" in method_params[datamodel_param_name]
389+
and method_params[datamodel_param_name]["logical_date"] is None
390+
):
391+
method_params[datamodel_param_name]["logical_date"] = datetime.now(timezone.utc)
392+
393+
# Step 3: Create the Pydantic model (what happens in the actual code)
394+
trigger_body = datamodel.model_validate(method_params[datamodel_param_name])
395+
396+
# Step 4: Verify logical_date was set to now
397+
assert trigger_body.logical_date is not None, "logical_date should be defaulted to now"
398+
assert isinstance(trigger_body.logical_date, datetime)
399+
400+
# Verify it's close to current time (within 5 seconds)
401+
time_diff = abs((datetime.now(timezone.utc) - trigger_body.logical_date).total_seconds())
402+
assert time_diff < 5, f"logical_date should be close to now, but diff is {time_diff} seconds"
403+
404+
# Also verify timezone is UTC
405+
assert trigger_body.logical_date.tzinfo is not None, "logical_date should have timezone info"

0 commit comments

Comments
 (0)