fix(airflow): необязательные поля формы generator_control стали необязательными
- Зачем:
- форма Trigger DAG требовала заполнить все поля: канонический запуск
«выбрать профиль, остальное пусто» через UI был невозможен (находка F1
ручного HITL); артефакты backfill (data/*.json, 49 МБ) рисковали
попасть в коммит.
- Что:
- пять необязательных Param переведены на Param(None, type=["null",
"string"]) — идиома необязательного поля, проверено по Context7
(Airflow 2.10.5); все места чтения уже None-безопасны (or "").
- контрактный тест дополнен: у необязательных Param есть "null" в type,
у operation/profile — нет.
- .gitignore: правило data/*.json (сиды data/*.jsonl остаются под git);
задача 21 закрыта в .scratch.
- Проверка:
- make test (205 + 31 passed) и make lint — зелёные.
This commit is contained in:
@@ -16,6 +16,32 @@ def _tree():
|
||||
return ast.parse(DAG_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _declared_param_types() -> dict[str, set[str]]:
|
||||
param_types = {}
|
||||
for node in ast.walk(_tree()):
|
||||
if not isinstance(node, ast.Dict):
|
||||
continue
|
||||
for key, value in zip(node.keys, node.values, strict=True):
|
||||
if not (
|
||||
isinstance(key, ast.Constant)
|
||||
and isinstance(key.value, str)
|
||||
and isinstance(value, ast.Call)
|
||||
and isinstance(value.func, ast.Name)
|
||||
and value.func.id == "Param"
|
||||
):
|
||||
continue
|
||||
type_keyword = next(
|
||||
keyword.value for keyword in value.keywords if keyword.arg == "type"
|
||||
)
|
||||
if isinstance(type_keyword, ast.List):
|
||||
param_types[key.value] = {
|
||||
item.value for item in type_keyword.elts if isinstance(item, ast.Constant)
|
||||
}
|
||||
else:
|
||||
param_types[key.value] = {type_keyword.value}
|
||||
return param_types
|
||||
|
||||
|
||||
def test_dag_file_exists_and_uses_dynamic_profiles():
|
||||
"""DAG берёт варианты профилей из PROFILES, а не из ручного списка."""
|
||||
text = DAG_PATH.read_text(encoding="utf-8")
|
||||
@@ -37,6 +63,22 @@ def test_trigger_form_has_expected_param_enums():
|
||||
assert '"expected_t_end": Param(' in text
|
||||
|
||||
|
||||
def test_trigger_form_marks_only_optional_params_as_nullable():
|
||||
"""Форма разрешает оставить необязательные поля пустыми."""
|
||||
param_types = _declared_param_types()
|
||||
|
||||
for name in {
|
||||
"duration",
|
||||
"seed",
|
||||
"model_time_speed",
|
||||
"artifact_path",
|
||||
"expected_t_end",
|
||||
}:
|
||||
assert "null" in param_types[name]
|
||||
for name in {"operation", "profile"}:
|
||||
assert "null" not in param_types[name]
|
||||
|
||||
|
||||
def test_dag_branches_and_waits_for_etl_completion():
|
||||
"""Backfill/import/next-day запускают ETL и ждут завершения перед check."""
|
||||
text = DAG_PATH.read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user