blob: a6dd9106cf38d87a7088ef8c26ffde1f73838ca0 [file]
#
# 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.
from __future__ import annotations
import pytest
from airflow.decorators import task
from airflow.exceptions import AirflowSensorTimeout
from airflow.models import XCom
from airflow.sensors.base import PokeReturnValue
from airflow.utils.state import State
class TestSensorDecorator:
def test_sensor_fails_on_none_python_callable(self, dag_maker):
not_callable = {}
with pytest.raises(TypeError):
task.sensor(not_callable)
def test_basic_sensor_success(self, dag_maker):
sensor_xcom_value = "xcom_value"
@task.sensor
def sensor_f():
return PokeReturnValue(is_done=True, xcom_value=sensor_xcom_value)
@task
def dummy_f():
pass
with dag_maker():
sf = sensor_f()
df = dummy_f()
sf >> df
dr = dag_maker.create_dagrun()
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.SUCCESS
if ti.task_id == "dummy_f":
assert ti.state == State.NONE
actual_xcom_value = XCom.get_one(
key="return_value", task_id="sensor_f", dag_id=dr.dag_id, run_id=dr.run_id
)
assert actual_xcom_value == sensor_xcom_value
def test_basic_sensor_success_returns_bool(self, dag_maker):
@task.sensor
def sensor_f():
return True
@task
def dummy_f():
pass
with dag_maker():
sf = sensor_f()
df = dummy_f()
sf >> df
dr = dag_maker.create_dagrun()
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.SUCCESS
if ti.task_id == "dummy_f":
assert ti.state == State.NONE
def test_basic_sensor_failure(self, dag_maker):
@task.sensor(timeout=0)
def sensor_f():
return PokeReturnValue(is_done=False, xcom_value="xcom_value")
@task
def dummy_f():
pass
with dag_maker():
sf = sensor_f()
df = dummy_f()
sf >> df
dr = dag_maker.create_dagrun()
with pytest.raises(AirflowSensorTimeout):
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.FAILED
if ti.task_id == "dummy_f":
assert ti.state == State.NONE
def test_basic_sensor_failure_returns_bool(self, dag_maker):
@task.sensor(timeout=0)
def sensor_f():
return False
@task
def dummy_f():
pass
with dag_maker():
sf = sensor_f()
df = dummy_f()
sf >> df
dr = dag_maker.create_dagrun()
with pytest.raises(AirflowSensorTimeout):
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.FAILED
if ti.task_id == "dummy_f":
assert ti.state == State.NONE
def test_basic_sensor_soft_fail(self, dag_maker):
@task.sensor(timeout=0, soft_fail=True)
def sensor_f():
return PokeReturnValue(is_done=False, xcom_value="xcom_value")
@task
def dummy_f():
pass
with dag_maker():
sf = sensor_f()
df = dummy_f()
sf >> df
dr = dag_maker.create_dagrun()
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.SKIPPED
if ti.task_id == "dummy_f":
assert ti.state == State.NONE
def test_basic_sensor_soft_fail_returns_bool(self, dag_maker):
@task.sensor(timeout=0, soft_fail=True)
def sensor_f():
return False
@task
def dummy_f():
pass
with dag_maker():
sf = sensor_f()
df = dummy_f()
sf >> df
dr = dag_maker.create_dagrun()
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.SKIPPED
if ti.task_id == "dummy_f":
assert ti.state == State.NONE
def test_basic_sensor_get_upstream_output(self, dag_maker):
ret_val = 100
sensor_xcom_value = "xcom_value"
@task
def upstream_f() -> int:
return ret_val
@task.sensor
def sensor_f(n: int):
assert n == ret_val
return PokeReturnValue(is_done=True, xcom_value=sensor_xcom_value)
with dag_maker():
uf = upstream_f()
sf = sensor_f(uf)
dr = dag_maker.create_dagrun()
uf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date, ignore_ti_state=True)
sf.operator.run(start_date=dr.execution_date, end_date=dr.execution_date)
tis = dr.get_task_instances()
assert len(tis) == 2
for ti in tis:
if ti.task_id == "sensor_f":
assert ti.state == State.SUCCESS
if ti.task_id == "dummy_f":
assert ti.state == State.SUCCESS
actual_xcom_value = XCom.get_one(
key="return_value", task_id="sensor_f", dag_id=dr.dag_id, run_id=dr.run_id
)
assert actual_xcom_value == sensor_xcom_value