Skip to content

Commit 47a4f1a

Browse files
Refactor datamodel defaulting logic into dedicated method (#61236)
Extract TriggerDAGRunPostBody logical_date defaulting from _get_func into a new _apply_datamodel_defaults method. This addresses review feedback to reduce cyclomatic complexity in the already-complex _get_func method and provides a centralized location for managing datamodel-specific defaults.
1 parent 2ebec16 commit 47a4f1a

File tree

2 files changed

+93
-10
lines changed

2 files changed

+93
-10
lines changed

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

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,31 @@ def _create_args_map_from_operation(self):
586586

587587
self.args_map[(operation.get("name"), operation.get("parent").name)] = args
588588

589+
def _apply_datamodel_defaults(self, datamodel: type, params: dict) -> dict:
590+
"""
591+
Apply datamodel-specific default values.
592+
593+
Centralizes special handling for different datamodels to keep
594+
CLI parameter processing logic clean and maintainable.
595+
596+
Args:
597+
datamodel: The Pydantic datamodel class
598+
params: Dictionary of parameters for the datamodel
599+
600+
Returns:
601+
Updated params dictionary with defaults applied
602+
"""
603+
# Handle TriggerDAGRunPostBody: default logical_date to now
604+
# This matches the Airflow UI behavior where the form pre-fills with current time
605+
if (
606+
datamodel.__name__ == "TriggerDAGRunPostBody"
607+
and "logical_date" in params
608+
and params["logical_date"] is None
609+
):
610+
params["logical_date"] = datetime.datetime.now(datetime.timezone.utc)
611+
612+
return params
613+
589614
def _create_func_map_from_operation(self):
590615
"""Create function map from Operation Method checking for parameters and return types."""
591616

@@ -624,16 +649,10 @@ def _get_func(args: Namespace, api_operation: dict, api_client: Client = NEW_API
624649

625650
if datamodel:
626651
if datamodel_param_name:
627-
# Special handling for TriggerDAGRunPostBody: default logical_date to now
628-
# This matches the Airflow UI behavior where the form pre-fills with current time
629-
if (
630-
datamodel.__name__ == "TriggerDAGRunPostBody"
631-
and "logical_date" in method_params[datamodel_param_name]
632-
and method_params[datamodel_param_name]["logical_date"] is None
633-
):
634-
method_params[datamodel_param_name]["logical_date"] = datetime.datetime.now(
635-
datetime.timezone.utc
636-
)
652+
# Apply datamodel-specific defaults (e.g., logical_date for TriggerDAGRunPostBody)
653+
method_params[datamodel_param_name] = self._apply_datamodel_defaults(
654+
datamodel, method_params[datamodel_param_name]
655+
)
637656
method_params[datamodel_param_name] = datamodel.model_validate(
638657
method_params[datamodel_param_name]
639658
)

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,67 @@ def test_trigger_dag_run_defaults_logical_date_to_now(self):
403403

404404
# Also verify timezone is UTC
405405
assert trigger_body.logical_date.tzinfo is not None, "logical_date should have timezone info"
406+
407+
def test_apply_datamodel_defaults_trigger_dag_run_with_none(self):
408+
"""Test _apply_datamodel_defaults sets logical_date to now when None for TriggerDAGRunPostBody."""
409+
from datetime import datetime, timezone
410+
411+
from airflowctl.api.datamodels.generated import TriggerDAGRunPostBody
412+
413+
command_factory = CommandFactory()
414+
415+
# Test with logical_date=None
416+
params = {"logical_date": None, "conf": {}}
417+
result = command_factory._apply_datamodel_defaults(TriggerDAGRunPostBody, params)
418+
419+
assert result["logical_date"] is not None, "logical_date should be defaulted to now"
420+
assert isinstance(result["logical_date"], datetime)
421+
422+
# Verify it's close to current time (within 5 seconds)
423+
time_diff = abs((datetime.now(timezone.utc) - result["logical_date"]).total_seconds())
424+
assert time_diff < 5, f"logical_date should be close to now, but diff is {time_diff} seconds"
425+
426+
# Verify timezone is UTC
427+
assert result["logical_date"].tzinfo is not None, "logical_date should have timezone info"
428+
429+
def test_apply_datamodel_defaults_trigger_dag_run_with_value(self):
430+
"""Test _apply_datamodel_defaults preserves existing logical_date for TriggerDAGRunPostBody."""
431+
from datetime import datetime, timezone
432+
433+
from airflowctl.api.datamodels.generated import TriggerDAGRunPostBody
434+
435+
command_factory = CommandFactory()
436+
437+
# Test with an existing logical_date value
438+
specific_date = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
439+
params = {"logical_date": specific_date, "conf": {}}
440+
result = command_factory._apply_datamodel_defaults(TriggerDAGRunPostBody, params)
441+
442+
# Should preserve the provided value, not override it
443+
assert result["logical_date"] == specific_date, "logical_date should not be changed when already set"
444+
445+
def test_apply_datamodel_defaults_trigger_dag_run_without_logical_date(self):
446+
"""Test _apply_datamodel_defaults doesn't add logical_date if not present."""
447+
from airflowctl.api.datamodels.generated import TriggerDAGRunPostBody
448+
449+
command_factory = CommandFactory()
450+
451+
# Test without logical_date key
452+
params = {"conf": {}}
453+
result = command_factory._apply_datamodel_defaults(TriggerDAGRunPostBody, params)
454+
455+
# Should not add logical_date if it wasn't in params
456+
assert "logical_date" not in result, "logical_date should not be added if not originally present"
457+
458+
def test_apply_datamodel_defaults_other_datamodel(self):
459+
"""Test _apply_datamodel_defaults doesn't modify params for other datamodels."""
460+
from airflowctl.api.datamodels.generated import BackfillPostBody
461+
462+
command_factory = CommandFactory()
463+
464+
# Test with a different datamodel (BackfillPostBody)
465+
params = {"dag_id": "test_dag", "from_date": None}
466+
result = command_factory._apply_datamodel_defaults(BackfillPostBody, params)
467+
468+
# Should return params unchanged for other datamodels
469+
assert result == params, "Params should be unchanged for non-TriggerDAGRunPostBody datamodels"

0 commit comments

Comments
 (0)