This is an automated email from the ASF dual-hosted git repository.
turaga pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 9d61f742746 Default logical_date to now in airflowctl dagrun trigger
to match UI behavior (#61047)
9d61f742746 is described below
commit 9d61f7427460f1a7f866dc6b0011bdf8011b1e4e
Author: Dheeraj Turaga <[email protected]>
AuthorDate: Thu Jan 29 22:12:42 2026 -0600
Default logical_date to now in airflowctl dagrun trigger to match UI
behavior (#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
---
.../airflowctl_tests/test_airflowctl_commands.py | 2 +
airflow-ctl/src/airflowctl/ctl/cli_config.py | 10 +++++
.../tests/airflow_ctl/ctl/test_cli_config.py | 48 ++++++++++++++++++++++
3 files changed, 60 insertions(+)
diff --git
a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
index b6e537af97d..4e85b45be52 100644
--- a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
+++ b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
@@ -86,6 +86,8 @@ TEST_COMMANDS = [
"dags list-warning",
# Order of trigger and pause/unpause is important for test stability
because state checked
f"dags trigger --dag-id=example_bash_operator
--logical-date={ONE_DATE_PARAM} --run-after={ONE_DATE_PARAM}",
+ # Test trigger without logical-date (should default to now)
+ "dags trigger --dag-id=example_bash_operator",
"dags pause example_bash_operator",
"dags unpause example_bash_operator",
# DAG Run commands
diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index a578396acd4..6c455693b49 100644
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -621,6 +621,16 @@ class CommandFactory:
if datamodel:
if datamodel_param_name:
+ # Special handling for TriggerDAGRunPostBody: default
logical_date to now
+ # This matches the Airflow UI behavior where the form
pre-fills with current time
+ if (
+ datamodel.__name__ == "TriggerDAGRunPostBody"
+ and "logical_date" in
method_params[datamodel_param_name]
+ and
method_params[datamodel_param_name]["logical_date"] is None
+ ):
+ method_params[datamodel_param_name]["logical_date"] =
datetime.datetime.now(
+ datetime.timezone.utc
+ )
method_params[datamodel_param_name] =
datamodel.model_validate(
method_params[datamodel_param_name]
)
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
index c0ac022a256..ddf3909339b 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
@@ -355,3 +355,51 @@ class TestCliConfigMethods:
assert "subcommand2" in sub_command_names
assert "subcommand3" in sub_command_names
assert "subcommand4" in sub_command_names
+
+ def test_trigger_dag_run_defaults_logical_date_to_now(self):
+ """Test that trigger command defaults logical_date to now when not
provided."""
+ from datetime import datetime, timezone
+
+ from airflowctl.api.datamodels.generated import TriggerDAGRunPostBody
+
+ # Simulate the logic in _get_func from cli_config.py
+ # This is the actual code path that runs when user doesn't provide
--logical-date
+
+ # Step 1: Simulate CLI args being parsed (logical_date=None)
+ method_params = {
+ "trigger_dag_run": {
+ "dag_run_id": None,
+ "data_interval_start": None,
+ "data_interval_end": None,
+ "logical_date": None, # User did not provide --logical-date
+ "run_after": None,
+ "conf": None,
+ "note": None,
+ "partition_key": None,
+ }
+ }
+
+ # Step 2: Apply the defaulting logic (from cli_config.py lines 622-630)
+ datamodel = TriggerDAGRunPostBody
+ datamodel_param_name = "trigger_dag_run"
+
+ if (
+ datamodel.__name__ == "TriggerDAGRunPostBody"
+ and "logical_date" in method_params[datamodel_param_name]
+ and method_params[datamodel_param_name]["logical_date"] is None
+ ):
+ method_params[datamodel_param_name]["logical_date"] =
datetime.now(timezone.utc)
+
+ # Step 3: Create the Pydantic model (what happens in the actual code)
+ trigger_body =
datamodel.model_validate(method_params[datamodel_param_name])
+
+ # Step 4: Verify logical_date was set to now
+ assert trigger_body.logical_date is not None, "logical_date should be
defaulted to now"
+ assert isinstance(trigger_body.logical_date, datetime)
+
+ # Verify it's close to current time (within 5 seconds)
+ time_diff = abs((datetime.now(timezone.utc) -
trigger_body.logical_date).total_seconds())
+ assert time_diff < 5, f"logical_date should be close to now, but diff
is {time_diff} seconds"
+
+ # Also verify timezone is UTC
+ assert trigger_body.logical_date.tzinfo is not None, "logical_date
should have timezone info"