Skip to content

Commit aa56272

Browse files
committed
Remove custom yml constructor and consolidate boolean implementation
Following up on PR #487, this PR demonstrates and replaces the usages of tasks and task groups across all example dags, tests and static yamls in docs to be a list. closes: #491 Fix yml
1 parent d270ff0 commit aa56272

61 files changed

Lines changed: 538 additions & 571 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dagfactory/_yaml.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

3+
import copy
34
import os
5+
from functools import reduce
46

57
import yaml
68

@@ -12,25 +14,40 @@ def load_yaml_file(file_path: str) -> dict[str, any]:
1214
Load a YAML file into a dictionary.
1315
"""
1416

15-
def __join(loader: yaml.FullLoader, node: yaml.Node) -> str:
16-
seq = loader.construct_sequence(node)
17-
return "".join([str(i) for i in seq])
18-
19-
def __or(loader: yaml.FullLoader, node: yaml.Node) -> str:
20-
seq = loader.construct_sequence(node)
21-
return " | ".join([f"({str(i)})" for i in seq])
22-
23-
def __and(loader: yaml.FullLoader, node: yaml.Node) -> str:
24-
seq = loader.construct_sequence(node)
25-
return " & ".join([f"({str(i)})" for i in seq])
26-
27-
yaml.add_constructor("!join", __join, yaml.FullLoader)
28-
yaml.add_constructor("!or", __or, yaml.FullLoader)
29-
yaml.add_constructor("!and", __and, yaml.FullLoader)
17+
def _flatten_logical_expressions_helper(data):
18+
if isinstance(data, str):
19+
return data
20+
if isinstance(data, dict):
21+
if "__and__" in data:
22+
processed_items = [_flatten_logical_expressions_helper(item) for item in data["__and__"]]
23+
try:
24+
return reduce(lambda a, b: a & b, processed_items)
25+
except TypeError:
26+
return f"({' & '.join(processed_items)})"
27+
if "__or__" in data:
28+
processed_items = [_flatten_logical_expressions_helper(item) for item in data["__or__"]]
29+
try:
30+
return reduce(lambda a, b: a | b, processed_items)
31+
except TypeError:
32+
return f"({' | '.join(processed_items)})"
33+
if "__join__" in data:
34+
processed_items = [_flatten_logical_expressions_helper(item) for item in data["__join__"]]
35+
return "".join(processed_items)
36+
new_dict = {}
37+
for key, value in data.items():
38+
new_dict[key] = _flatten_logical_expressions_helper(value)
39+
return new_dict
40+
if isinstance(data, list):
41+
return [_flatten_logical_expressions_helper(item) for item in data]
42+
return data
43+
44+
def _flatten_logical_expressions(data):
45+
return _flatten_logical_expressions_helper(copy.deepcopy(data))
3046

3147
with open(file_path, "r", encoding="utf-8") as fp:
3248
config_with_env = os.path.expandvars(fp.read())
3349
config: dict[str, any] = yaml.load(stream=config_with_env, Loader=yaml.FullLoader)
3450
config = cast_with_type(config)
51+
config = _flatten_logical_expressions(config)
3552

3653
return config

dagfactory/dagbuilder.py

Lines changed: 8 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import warnings
1111
from copy import deepcopy
1212
from datetime import datetime, timedelta
13-
from functools import partial, reduce
13+
from functools import partial
1414
from typing import Any, Callable, Dict, List, Tuple, Union
1515

1616
from airflow import configuration
@@ -756,66 +756,6 @@ def process_file_with_datasets(file: str, datasets_conditions: str) -> Any:
756756
datasets_uri = utils.get_datasets_uri_yaml_file(file, list(dataset_map.keys()))
757757
return [Dataset(uri) for uri in datasets_uri]
758758

759-
@staticmethod
760-
def _init_watchers(watchers_data):
761-
"""Initialize watcher objects from configuration."""
762-
from dagfactory.utils import _import_from_string
763-
764-
watchers = []
765-
for watcher in watchers_data:
766-
watcher_class = _import_from_string(watcher["callable"])
767-
trigger_data = watcher.get("trigger", {})
768-
trigger_class = _import_from_string(trigger_data.get("callable"))
769-
trigger_params = trigger_data.get("params", {})
770-
watchers.append(watcher_class(name=watcher.get("name"), trigger=trigger_class(**trigger_params)))
771-
return watchers
772-
773-
@staticmethod
774-
def _combine_assets(assets, op: str):
775-
"""Combine a list of Asset objects using logical operators."""
776-
if op == "or":
777-
return reduce(lambda a, b: a | b, assets)
778-
elif op == "and":
779-
return reduce(lambda a, b: a & b, assets)
780-
else:
781-
raise ValueError(f"Unknown operator: {op}")
782-
783-
@staticmethod
784-
def _is_asset(d):
785-
from airflow.sdk import Asset
786-
787-
if not isinstance(d, dict):
788-
return False
789-
for key, value in d.items():
790-
if isinstance(value, Asset):
791-
return True
792-
elif isinstance(value, list):
793-
if any(isinstance(item, Asset) for item in value):
794-
return True
795-
elif isinstance(value, dict):
796-
if DagBuilder._is_asset(value):
797-
return True
798-
return False
799-
800-
@staticmethod
801-
def _asset_schedule(value):
802-
"""Recursively parse and construct assets or combinations of assets."""
803-
from airflow.sdk import Asset
804-
805-
if isinstance(value, dict):
806-
if "or" in value:
807-
assets = [DagBuilder._asset_schedule(item) for item in value["or"]]
808-
return DagBuilder._combine_assets(assets, "or")
809-
elif "and" in value:
810-
assets = [DagBuilder._asset_schedule(item) for item in value["and"]]
811-
return DagBuilder._combine_assets(assets, "and")
812-
elif isinstance(value, list):
813-
return [asset for asset in value]
814-
elif isinstance(value, Asset):
815-
return value
816-
else:
817-
raise TypeError(f"Unexpected data type: {type(value)}")
818-
819759
@staticmethod
820760
def configure_schedule(dag_params: Dict[str, Any], dag_kwargs: Dict[str, Any]) -> None:
821761
"""
@@ -862,17 +802,14 @@ def configure_schedule(dag_params: Dict[str, Any], dag_kwargs: Dict[str, Any]) -
862802
schedule.pop("datasets")
863803
else:
864804
schedule = dag_params.get("schedule")
865-
if DagBuilder._is_asset(schedule):
866-
dag_kwargs["schedule"] = DagBuilder._asset_schedule(schedule)
805+
if (
806+
utils.check_dict_key(dag_params, "schedule")
807+
and isinstance(dag_params["schedule"], str)
808+
and dag_params["schedule"].strip().lower() == "none"
809+
):
810+
dag_kwargs["schedule"] = None
867811
else:
868-
if (
869-
utils.check_dict_key(dag_params, "schedule")
870-
and isinstance(dag_params["schedule"], str)
871-
and dag_params["schedule"].strip().lower() == "none"
872-
):
873-
dag_kwargs["schedule"] = None
874-
else:
875-
dag_kwargs["schedule"] = schedule
812+
dag_kwargs["schedule"] = schedule
876813

877814
@staticmethod
878815
def _normalise_tasks_config(tasks_cfg: Any) -> Dict[str, Dict[str, Any]]:

dagfactory/dagfactory.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ def __init__(
5555
DagFactory._validate_config_filepath(config_filepath=config_filepath)
5656
self.config: Dict[str, Any] = self._load_dag_config(config_filepath=config_filepath)
5757
if config:
58-
self.config: Dict[str, Any] = config
58+
self.config = config
59+
# This will only invoke in the CI
60+
# Make yaml DAG compatible for Airflow 3
61+
if version.parse(AIRFLOW_VERSION) >= version.parse("3.0.0") and os.getenv("AUTO_CONVERT_TO_AF3"):
62+
self.config = update_yaml_structure(config)
5963

6064
# These default args are a bit different; these are not the "default" structure that is applied to certain DAGs.
6165
# These are in-fact the "default" default_args
@@ -208,7 +212,7 @@ def _load_dag_config(self, config_filepath: str) -> Dict[str, Any]:
208212
config = update_yaml_structure(config)
209213

210214
except Exception as err:
211-
raise DagFactoryConfigException("Invalid DAG Factory config file") from err
215+
raise DagFactoryConfigException(f"Invalid DAG Factory config file: {err}")
212216
return config
213217

214218
def get_dag_configs(self) -> Dict[str, Dict[str, Any]]:

dev/dags/airflow2/example_custom_py_object_dag.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ custom_example_dag:
44
catchup: false
55
render_template_as_native_obj: True
66
tasks:
7-
task_1:
7+
- task_id: "task_1"
88
operator: airflow.operators.bash.BashOperator
99
bash_command: "echo 1"
1010
executor_config:
@@ -25,11 +25,11 @@ custom_example_dag:
2525
requests:
2626
cpu: "0.5"
2727
memory: "512Mi"
28-
task_2:
28+
- task_id: "task_2"
2929
operator: airflow.operators.bash.BashOperator
3030
bash_command: "echo 2"
3131
dependencies: [task_1]
32-
task_3:
32+
- task_id: "task_3"
3333
operator: airflow.operators.bash.BashOperator
3434
bash_command: "echo 2"
3535
dependencies: [task_1]

dev/dags/airflow2/example_customize_operator.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ example_breadfast:
1212
schedule_interval: "0 3 * * *"
1313
catchup: false
1414
tasks:
15-
begin:
15+
- task_id: "begin"
1616
operator: airflow.operators.empty.EmptyOperator
17-
make_bread_1:
17+
- task_id: "make_bread_1"
1818
operator: customized.operators.breakfast_operators.MakeBreadOperator
1919
bread_type: 'Sourdough'
2020
dependencies:
2121
- begin
22-
make_bread_2:
22+
- task_id: "make_bread_2"
2323
operator: customized.operators.breakfast_operators.MakeBreadOperator
2424
bread_type: 'Multigrain'
2525
dependencies:
2626
- begin
27-
make_coffee_1:
27+
- task_id: "make_coffee_1"
2828
operator: customized.operators.breakfast_operators.MakeCoffeeOperator
2929
coffee_type: 'Black'
3030
dependencies:
3131
- begin
3232
- make_bread_1
3333
- make_bread_2
34-
end:
34+
- task_id: "end"
3535
operator: airflow.operators.empty.EmptyOperator
3636
dependencies:
3737
- begin

dev/dags/airflow2/example_dag_factory_default_args.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ default:
88

99
etl:
1010
tasks:
11-
extract:
11+
- task_id: "extract"
1212
operator: airflow.operators.bash.BashOperator
1313
bash_command: "echo extract"
14-
transform:
14+
- task_id: "transform"
1515
operator: airflow.operators.bash.BashOperator
1616
bash_command: "echo transform"
1717
dependencies:
1818
- extract
19-
load:
19+
- task_id: "load"
2020
operator: airflow.operators.bash.BashOperator
2121
bash_command: "echo load"
2222
dependencies:

dev/dags/airflow2/example_dag_factory_default_config.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,31 @@ default:
66
tags:
77
- dynamic
88
tasks:
9-
extract:
9+
- task_id: "extract"
1010
operator: airflow.operators.bash.BashOperator
1111
bash_command: "echo extract"
12-
transform:
12+
- task_id: "transform"
1313
operator: airflow.operators.bash.BashOperator
1414
bash_command: "echo transform"
1515
dependencies:
1616
- extract
17-
load:
17+
- task_id: "load"
1818
operator: airflow.operators.bash.BashOperator
1919
dependencies:
2020
- transform
2121

2222

2323
machine_learning:
2424
tasks:
25-
load:
25+
- task_id: "load"
2626
bash_command: "echo machine_larning"
2727

2828
data_science:
2929
tasks:
30-
load:
30+
- task_id: "load"
3131
bash_command: "echo data_science"
3232

3333
artificial_intelligence:
3434
tasks:
35-
load:
35+
- task_id: "load"
3636
bash_command: "echo artificial_intelligence"

dev/dags/airflow2/example_dag_factory_multiple_config.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ example_dag:
1919
render_template_as_native_obj: True
2020
dag_display_name: "Pretty Example DAG"
2121
tasks:
22-
task_1:
22+
- task_id: "task_1"
2323
operator: airflow.operators.bash.BashOperator
2424
bash_command: "echo 1"
25-
task_2:
25+
- task_id: "task_2"
2626
operator: airflow.operators.bash.BashOperator
2727
bash_command: "echo 2"
2828
dependencies: [task_1]
29-
task_3:
29+
- task_id: "task_3"
3030
operator: airflow.operators.python.PythonOperator
3131
python_callable_name: print_hello
3232
python_callable_file: $CONFIG_ROOT_DIR/print_hello.py
@@ -37,53 +37,53 @@ example_dag2:
3737
default_args:
3838
timezone: Europe/Amsterdam
3939
tasks:
40-
task_1:
40+
- task_id: "task_1"
4141
operator: airflow.operators.bash.BashOperator
4242
bash_command: "echo 1"
43-
task_2:
43+
- task_id: "task_2"
4444
operator: airflow.operators.bash.BashOperator
4545
bash_command: "echo 2"
4646
dependencies: [task_1]
47-
task_3:
47+
- task_id: "task_3"
4848
operator: airflow.operators.bash.BashOperator
4949
bash_command: "echo 3"
5050
dependencies: [task_1]
5151

5252
example_dag3:
5353
tasks:
54-
task_1:
54+
- task_id: "task_1"
5555
operator: airflow.operators.bash.BashOperator
5656
bash_command: "echo 1"
57-
task_2:
57+
- task_id: "task_2"
5858
operator: airflow.operators.bash.BashOperator
5959
bash_command: "echo 2"
6060
dependencies: [task_1]
61-
task_3:
61+
- task_id: "task_3"
6262
operator: airflow.operators.bash.BashOperator
6363
bash_command: "echo 3"
6464
dependencies: [task_1]
6565

6666
example_dag4:
6767
description: "this dag uses task groups"
6868
task_groups:
69-
task_group_1:
69+
- group_name: "task_group_1"
7070
tooltip: "this is a task group"
7171
dependencies: [task_1]
7272
tasks:
73-
task_1:
73+
- task_id: "task_1"
7474
operator: airflow.operators.bash.BashOperator
7575
bash_command: "echo 1"
76-
task_2:
76+
- task_id: "task_2"
7777
operator: airflow.operators.bash.BashOperator
7878
bash_command: "echo 2"
7979
task_group_name: task_group_1
80-
task_3:
80+
- task_id: "task_3"
8181
operator: airflow.operators.python.PythonOperator
8282
python_callable_name: print_hello
8383
python_callable_file: $CONFIG_ROOT_DIR/print_hello.py
8484
task_group_name: task_group_1
8585
dependencies: [task_2]
86-
task_4:
86+
- task_id: "task_4"
8787
operator: airflow.operators.bash.BashOperator
8888
bash_command: "echo 1"
8989
dependencies: [task_group_1]

0 commit comments

Comments
 (0)