#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""DAG demonstrating various options for a trigger form generated by DAG params.
The DAG attribute `params` is used to define a default dictionary of parameters which are usually passed
to the DAG and which are used to render a trigger form.
"""
from __future__ import annotations
import datetime
import json
from pathlib import Path
from airflow.decorators import task
from airflow.models.dag import DAG
from airflow.models.param import Param, ParamsDict
with DAG(
dag_id=Path(__file__).stem,
dag_display_name="Params UI tutorial",
description=__doc__.partition(".")[0],
doc_md=__doc__,
schedule=None,
start_date=datetime.datetime(2022, 3, 4),
catchup=False,
tags=["example", "params", "ui"],
params={
# Let's start simple: Standard dict values are detected from type and offered as entry form fields.
# Detected types are numbers, text, boolean, lists and dicts.
# Note that such auto-detected parameters are treated as optional (not required to contain a value)
"x": 3,
"text": "Hello World!",
"flag": False,
"a_simple_list": ["one", "two", "three", "actually one value is made per line"],
# But of course you might want to have it nicer! Let's add some description to parameters.
# Note if you can add any Markdown formatting to the description, you need to use the description_md
# attribute.
"most_loved_number": Param(
42,
type="integer",
title="Your favorite number",
description_md="Everybody should have a **favorite** number. Not only _math teachers_. "
"If you can not think of any at the moment please think of the 42 which is very famous because"
"of the book [The Hitchhiker's Guide to the Galaxy]"
"(https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#"
"The_Answer_to_the_Ultimate_Question_of_Life,_the_Universe,_and_Everything_is_42).",
),
# If you want to have a selection list box then you can use the enum feature of JSON schema
"pick_one": Param(
"value 42",
type="string",
title="Select one Value",
description="You can use JSON schema enum's to generate drop down selection boxes.",
enum=[f"value {i}" for i in range(16, 64)],
),
# You can also label the selected values via values_display attribute
"pick_with_label": Param(
3,
type="number",
title="Select one Number",
description="With drop down selections you can also have nice display labels for the values.",
enum=[*range(1, 10)],
values_display={
1: "One",
2: "Two",
3: "Three",
4: "Four - is like you take three and get one for free!",
5: "Five",
6: "Six",
7: "Seven",
8: "Eight",
9: "Nine",
},
),
# If you want to have a list box with proposals but not enforcing a fixed list
# then you can use the examples feature of JSON schema
"proposals": Param(
"some value",
type="string",
title="Field with proposals",
description="You can use JSON schema examples's to generate drop down selection boxes "
"but allow also to enter custom values. Try typing an 'a' and see options.",
examples=(
"Alpha,Bravo,Charlie,Delta,Echo,Foxtrot,Golf,Hotel,India,Juliett,Kilo,Lima,Mike,November,Oscar,Papa,"
"Quebec,Romeo,Sierra,Tango,Uniform,Victor,Whiskey,X-ray,Yankee,Zulu"
).split(","),
),
# If you want to select multiple items from a fixed list JSON schema does not allow to use enum
# In this case the type "array" is being used together with "examples" as pick list
"multi_select": Param(
["two", "three"],
"Select from the list of options.",
type="array",
title="Multi Select",
examples=["one", "two", "three", "four", "five"],
),
# A multiple options selection can also be combined with values_display
"multi_select_with_label": Param(
["2", "3"],
"Select from the list of options. See that options can have nicer text and still technical values"
"are propagated as values during trigger to the DAG.",
type="array",
title="Multi Select with Labels",
examples=["1", "2", "3", "4", "5"],
values_display={
"1": "One box of choccolate",
"2": "Two bananas",
"3": "Three apples",
# Note: Value display mapping does not need to be complete
},
),
# An array of numbers
"array_of_numbers": Param(
[1, 2, 3],
"Only integers are accepted in this array",
type="array",
title="Array of numbers",
items={"type": "number"},
),
# Boolean as proper parameter with description
"bool": Param(
True,
type="boolean",
title="Please confirm",
description="A On/Off selection with a proper description.",
),
# Dates and Times are also supported
"date_time": Param(
f"{datetime.date.today()}T{datetime.time(hour=12, minute=17, second=00)}+00:00",
type="string",
format="date-time",
title="Date-Time Picker",
description="Please select a date and time, use the button on the left for a pop-up calendar.",
),
"date": Param(
f"{datetime.date.today()}",
type="string",
format="date",
title="Date Picker",
description="Please select a date, use the button on the left for a pop-up calendar. "
"See that here are no times!",
),
"time": Param(
f"{datetime.time(hour=12, minute=13, second=14)}",
type=["string", "null"],
format="time",
title="Time Picker",
description="Please select a time, use the button on the left for a pop-up tool.",
),
# Fields can be required or not. If the defined fields are typed they are getting required by default
# (else they would not pass JSON schema validation) - to make typed fields optional you must
# permit the optional "null" type.
# You can omit a default value if the DAG is triggered manually
"required_field": Param(
# In this example we have no default value
# Form will enforce a value supplied by users to be able to trigger
type="string",
title="Required text field",
description="This field is required. You can not submit without having text in here.",
),
"optional_field": Param(
"optional text, you can trigger also w/o text",
type=["null", "string"],
title="Optional text field",
description_md="This field is optional. As field content is JSON schema validated you must "
"allow the `null` type.",
),
# You can arrange the entry fields in sections so that you can have a better overview for the user
# Therefore you can add the "section" attribute.
# The benefit of the Params class definition is that the full scope of JSON schema validation
# can be leveraged for form fields and they will be validated before DAG submission.
"checked_text": Param(
"length-checked-field",
type="string",
title="Text field with length check",
description_md="""This field is required. And you need to provide something between 10 and 30
characters. See the JSON
[schema description (string)](https://json-schema.org/understanding-json-schema/reference/string.html)
for more details""",
minLength=10,
maxLength=20,
section="JSON Schema validation options",
),
"checked_number": Param(
100,
type="number",
title="Number field with value check",
description_md="""This field is required. You need to provide any number between 64 and 128.
See the JSON
[schema description (numbers)](https://json-schema.org/understanding-json-schema/reference/numeric.html)
for more details""",
minimum=64,
maximum=128,
section="JSON Schema validation options",
),
# Some further cool stuff as advanced options are also possible
# You can have the user entering a dict object as a JSON with validation
"object": Param(
{"key": "value"},
type=["object", "null"],
title="JSON entry field",
section="Special advanced stuff with form fields",
),
"array_of_objects": Param(
[{"name": "account_name", "country": "country_name"}],
description_md="Array with complex objects and validation rules. "
"See [JSON Schema validation options in specs]"
"(https://json-schema.org/understanding-json-schema/reference/array.html#items).",
type="array",
title="JSON array field",
items={
"type": "object",
"properties": {"name": {"type": "string"}, "country_name": {"type": "string"}},
"required": ["name"],
},
section="Special advanced stuff with form fields",
),
# If you want to have static parameters which are always passed and not editable by the user
# then you can use the JSON schema option of passing constant values. These parameters
# will not be displayed but passed to the DAG
"hidden_secret_field": Param("constant value", const="constant value"),
},
) as dag:
@task(task_display_name="Show used parameters")
[docs] def show_params(**kwargs) -> None:
params: ParamsDict = kwargs["params"]
print(f"This DAG was triggered with the following parameters:\n\n{json.dumps(params, indent=4)}\n")
show_params()