diff --git a/airflow/models/dag.py b/airflow/models/dag.py index ad129edf68072..89b79b98b5390 100644 --- a/airflow/models/dag.py +++ b/airflow/models/dag.py @@ -2819,6 +2819,10 @@ def get_last_dagrun(self, session=NEW_SESSION, include_externally_triggered=Fals self.dag_id, session=session, include_externally_triggered=include_externally_triggered ) + def get_is_paused(self, *, session: Optional[Session] = None) -> bool: + """Provide interface compatibility to 'DAG'.""" + return self.is_paused + @staticmethod @provide_session def get_paused_dag_ids(dag_ids: List[str], session: Session = NEW_SESSION) -> Set[str]: diff --git a/airflow/www/app.py b/airflow/www/app.py index efafcd9ede834..bd7a0e25a79a0 100644 --- a/airflow/www/app.py +++ b/airflow/www/app.py @@ -114,7 +114,8 @@ def create_app(config=None, testing=False): init_robots(flask_app) - Cache(app=flask_app, config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': gettempdir()}) + cache_config = {'CACHE_TYPE': 'flask_caching.backends.filesystem', 'CACHE_DIR': gettempdir()} + Cache(app=flask_app, config=cache_config) init_flash_views(flask_app) diff --git a/airflow/www/templates/airflow/dag.html b/airflow/www/templates/airflow/dag.html index 1ed60ecf71574..7d0cbb923e30a 100644 --- a/airflow/www/templates/airflow/dag.html +++ b/airflow/www/templates/airflow/dag.html @@ -27,6 +27,8 @@ {% endblock %} +{% set dag_is_paused = dag.get_is_paused() %} + {% block head_meta %} {{ super() }} @@ -36,7 +38,7 @@ - + {% if dag_model is defined and dag_model.next_dagrun_create_after is defined and dag_model.next_dagrun_create_after is not none %} @@ -70,11 +72,11 @@

{% if appbuilder.sm.can_edit_dag(dag.dag_id) %} {% set switch_tooltip = 'Pause/Unpause DAG' %} {% else %} - {% set switch_tooltip = 'DAG is Paused' if dag.is_paused else 'DAG is Active' %} + {% set switch_tooltip = 'DAG is Paused' if dag_is_paused else 'DAG is Active' %} {% endif %} diff --git a/airflow/www/templates/airflow/dags.html b/airflow/www/templates/airflow/dags.html index c38cf5b765c9e..c748d329a2193 100644 --- a/airflow/www/templates/airflow/dags.html +++ b/airflow/www/templates/airflow/dags.html @@ -146,16 +146,17 @@

{{ page_title }}

No results {% endif %} {% for dag in dags %} + {% set dag_is_paused = dag.get_is_paused() %} {% if dag.can_edit %} {% set switch_tooltip = 'Pause/Unpause DAG' %} {% else %} - {% set switch_tooltip = 'DAG is Paused' if dag.is_paused else 'DAG is Active' %} + {% set switch_tooltip = 'DAG is Paused' if dag_is_paused else 'DAG is Active' %} {% endif %} diff --git a/airflow/www/utils.py b/airflow/www/utils.py index 4cad9a5b47921..e5d0fb45d007f 100644 --- a/airflow/www/utils.py +++ b/airflow/www/utils.py @@ -23,11 +23,12 @@ import markdown import sqlalchemy as sqla -from flask import Markup, Response, request, url_for +from flask import Response, request, url_for from flask.helpers import flash from flask_appbuilder.forms import FieldConverter from flask_appbuilder.models.sqla import filters as fab_sqlafilters from flask_appbuilder.models.sqla.interface import SQLAInterface +from markupsafe import Markup from pendulum.datetime import DateTime from pygments import highlight, lexers from pygments.formatters import HtmlFormatter diff --git a/airflow/www/views.py b/airflow/www/views.py index 8144c9d0481e0..09ec76eb79021 100644 --- a/airflow/www/views.py +++ b/airflow/www/views.py @@ -25,6 +25,7 @@ import socket import sys import traceback +import warnings from collections import defaultdict from datetime import timedelta from functools import wraps @@ -38,12 +39,10 @@ import nvd3 import sqlalchemy as sqla from flask import ( - Markup, Response, abort, before_render_template, current_app, - escape, flash, g, jsonify, @@ -78,6 +77,7 @@ from flask_appbuilder.widgets import FormWidget from flask_babel import lazy_gettext from jinja2.utils import htmlsafe_json_dumps, pformat # type: ignore +from markupsafe import Markup, escape from pendulum.datetime import DateTime from pendulum.parsing.exceptions import ParserError from pygments import highlight, lexers @@ -1474,7 +1474,10 @@ def task(self, session): ti_attrs: Optional[List[Tuple[str, Any]]] = None else: ti.refresh_from_task(task) - all_ti_attrs = ((name, getattr(ti, name)) for name in dir(ti) if not name.startswith("_")) + # Some fields on TI are deprecated, but we don't want those warnings here. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + all_ti_attrs = ((name, getattr(ti, name)) for name in dir(ti) if not name.startswith("_")) ti_attrs = sorted((name, attr) for name, attr in all_ti_attrs if not callable(attr)) attr_renderers = wwwutils.get_attr_renderer() diff --git a/metastore_browser/hive_metastore.py b/metastore_browser/hive_metastore.py index 3400899dbd7a7..e2fdcd17d41cb 100644 --- a/metastore_browser/hive_metastore.py +++ b/metastore_browser/hive_metastore.py @@ -23,8 +23,9 @@ from typing import List import pandas as pd -from flask import Blueprint, Markup, request +from flask import Blueprint, request from flask_appbuilder import BaseView, expose +from markupsafe import Markup from airflow.plugins_manager import AirflowPlugin from airflow.providers.apache.hive.hooks.hive import HiveCliHook, HiveMetastoreHook diff --git a/setup.cfg b/setup.cfg index 1ed8e888734c3..3614322f89d06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -120,7 +120,10 @@ install_requires = iso8601>=0.1.12 # Logging is broken with itsdangerous > 2 itsdangerous>=1.1.0, <2.0 - jinja2>=2.10.1,<4 + # Jinja2 3.1 will remove the 'autoescape' and 'with' extensions, which would + # break Flask 1.x, so we limit this for future compatibility. Remove this + # when bumping Flask to >=2. + jinja2>=2.10.1,<3.1 jsonschema~=3.0 lazy-object-proxy lockfile>=0.12.2 diff --git a/tests/www/api/experimental/conftest.py b/tests/www/api/experimental/conftest.py index 1629d83f3967b..698eaea5204f1 100644 --- a/tests/www/api/experimental/conftest.py +++ b/tests/www/api/experimental/conftest.py @@ -21,6 +21,9 @@ from tests.test_utils.config import conf_vars from tests.test_utils.decorators import dont_initialize_flask_app_submodules +# This entire directory is testing deprecated functions. +pytestmark = pytest.mark.filterwarnings("ignore::DeprecationWarning") + @pytest.fixture(scope="session") def experiemental_api_app(): diff --git a/tests/www/test_app.py b/tests/www/test_app.py index 2e2579898da7e..7a06010776f02 100644 --- a/tests/www/test_app.py +++ b/tests/www/test_app.py @@ -240,7 +240,9 @@ def test_should_set_permanent_session_timeout(self): @conf_vars({('webserver', 'cookie_samesite'): ''}) @dont_initialize_flask_app_submodules def test_correct_default_is_set_for_cookie_samesite(self): - app = application.cached_app(testing=True) + """An empty 'cookie_samesite' should be corrected to 'Lax' with a deprecation warning.""" + with pytest.deprecated_call(): + app = application.cached_app(testing=True) assert app.config['SESSION_COOKIE_SAMESITE'] == 'Lax' diff --git a/tests/www/views/test_views.py b/tests/www/views/test_views.py index b98c1bc71253f..86e7ea03f4d0e 100644 --- a/tests/www/views/test_views.py +++ b/tests/www/views/test_views.py @@ -228,7 +228,11 @@ def test_mark_task_instance_state(test_app): task_1 >> [task_2, task_3, task_4, task_5] dagrun = dag.create_dagrun( - start_date=start_date, execution_date=start_date, state=State.FAILED, run_type=DagRunType.SCHEDULED + start_date=start_date, + execution_date=start_date, + data_interval=(start_date, start_date), + state=State.FAILED, + run_type=DagRunType.SCHEDULED, ) def get_task_instance(session, task): diff --git a/tests/www/views/test_views_acl.py b/tests/www/views/test_views_acl.py index 7ff7ac4959905..59cbb3eefc3d6 100644 --- a/tests/www/views/test_views_acl.py +++ b/tests/www/views/test_views_acl.py @@ -152,6 +152,7 @@ def init_dagruns(acl_app, reset_dagruns): acl_app.dag_bag.get_dag("example_bash_operator").create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) @@ -159,6 +160,7 @@ def init_dagruns(acl_app, reset_dagruns): run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, start_date=timezone.utcnow(), + data_interval=(DEFAULT_DATE, DEFAULT_DATE), state=State.RUNNING, ) yield diff --git a/tests/www/views/test_views_blocked.py b/tests/www/views/test_views_blocked.py index 26ad7bd28db38..8a3823a02da29 100644 --- a/tests/www/views/test_views_blocked.py +++ b/tests/www/views/test_views_blocked.py @@ -33,7 +33,7 @@ def running_subdag(admin_client, dag_maker): with dag_maker(dag_id="running_dag.subdag") as subdag: DummyOperator(task_id="dummy") - with dag_maker(dag_id="running_dag") as dag: + with pytest.deprecated_call(), dag_maker(dag_id="running_dag") as dag: SubDagOperator(task_id="subdag", subdag=subdag) dag_bag = DagBag(include_examples=False, include_smart_sensor=False) @@ -44,10 +44,12 @@ def running_subdag(admin_client, dag_maker): dag_bag.sync_to_db(session=session) # Simulate triggering the SubDagOperator to run the subdag. + logical_date = timezone.datetime(2016, 1, 1) subdag.create_dagrun( run_id="blocked_run_example_bash_operator", state=State.RUNNING, - execution_date=timezone.datetime(2016, 1, 1), + execution_date=logical_date, + data_interval=(logical_date, logical_date), start_date=timezone.datetime(2016, 1, 1), session=session, ) diff --git a/tests/www/views/test_views_dagrun.py b/tests/www/views/test_views_dagrun.py index 64ecadf4baf1a..8ba5c9b87eb7d 100644 --- a/tests/www/views/test_views_dagrun.py +++ b/tests/www/views/test_views_dagrun.py @@ -93,6 +93,7 @@ def running_dag_run(session): dr = dag.create_dagrun( state="running", execution_date=execution_date, + data_interval=(execution_date, execution_date), run_id="test_dag_runs_action", session=session, ) diff --git a/tests/www/views/test_views_decorators.py b/tests/www/views/test_views_decorators.py index 1a1c7955b979c..9b5383f2d654e 100644 --- a/tests/www/views/test_views_decorators.py +++ b/tests/www/views/test_views_decorators.py @@ -59,6 +59,7 @@ def dagruns(bash_dag, sub_dag, xcom_dag): bash_dagrun = bash_dag.create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=EXAMPLE_DAG_DEFAULT_DATE, + data_interval=(EXAMPLE_DAG_DEFAULT_DATE, EXAMPLE_DAG_DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) @@ -66,6 +67,7 @@ def dagruns(bash_dag, sub_dag, xcom_dag): sub_dagrun = sub_dag.create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=EXAMPLE_DAG_DEFAULT_DATE, + data_interval=(EXAMPLE_DAG_DEFAULT_DATE, EXAMPLE_DAG_DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) @@ -73,6 +75,7 @@ def dagruns(bash_dag, sub_dag, xcom_dag): xcom_dagrun = xcom_dag.create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=EXAMPLE_DAG_DEFAULT_DATE, + data_interval=(EXAMPLE_DAG_DEFAULT_DATE, EXAMPLE_DAG_DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) diff --git a/tests/www/views/test_views_extra_links.py b/tests/www/views/test_views_extra_links.py index 72e17eee95900..fef430a815edc 100644 --- a/tests/www/views/test_views_extra_links.py +++ b/tests/www/views/test_views_extra_links.py @@ -85,6 +85,7 @@ def _create_dag_run(*, execution_date, session): return dag.create_dagrun( state=DagRunState.RUNNING, execution_date=execution_date, + data_interval=(execution_date, execution_date), run_type=DagRunType.MANUAL, session=session, ) diff --git a/tests/www/views/test_views_graph_gantt.py b/tests/www/views/test_views_graph_gantt.py index 589ff1d5513e0..7aedb2860334f 100644 --- a/tests/www/views/test_views_graph_gantt.py +++ b/tests/www/views/test_views_graph_gantt.py @@ -50,6 +50,7 @@ def runs(dag): dag.create_dagrun( run_id=run_id, execution_date=execution_date, + data_interval=(execution_date, execution_date), state=State.SUCCESS, external_trigger=True, ) diff --git a/tests/www/views/test_views_home.py b/tests/www/views/test_views_home.py index 748f5ee6b3e25..9b16acea84fd2 100644 --- a/tests/www/views/test_views_home.py +++ b/tests/www/views/test_views_home.py @@ -19,6 +19,7 @@ from unittest import mock import flask +import markupsafe import pytest from airflow.dag_processing.processor import DagFileProcessor @@ -225,7 +226,7 @@ def test_dashboard_flash_messages_many(user_client): def test_dashboard_flash_messages_markup(user_client): link = 'hello world' - user_input = flask.Markup("Hello %s") % ("foo&bar",) + user_input = markupsafe.Markup("Hello %s") % ("foo&bar",) messages = [ UIAlert(link, html=True), UIAlert(user_input), diff --git a/tests/www/views/test_views_log.py b/tests/www/views/test_views_log.py index 2b42033d0dbc3..493e69c8b7e08 100644 --- a/tests/www/views/test_views_log.py +++ b/tests/www/views/test_views_log.py @@ -136,6 +136,7 @@ def tis(dags, session): dagrun = dag.create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=DEFAULT_DATE, state=DagRunState.RUNNING, session=session, @@ -145,6 +146,7 @@ def tis(dags, session): dagrun_removed = dag_removed.create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=DEFAULT_DATE, state=DagRunState.RUNNING, session=session, diff --git a/tests/www/views/test_views_pool.py b/tests/www/views/test_views_pool.py index fe003558d6834..9b1c0c1d401d3 100644 --- a/tests/www/views/test_views_pool.py +++ b/tests/www/views/test_views_pool.py @@ -16,6 +16,7 @@ # specific language governing permissions and limitations # under the License. import flask +import markupsafe import pytest from airflow.models import Pool @@ -79,10 +80,10 @@ def test_list(app, admin_client, pool_factory): # We should see this link with app.test_request_context(): url = flask.url_for('TaskInstanceModelView.list', _flt_3_pool='test-pool', _flt_3_state='running') - used_tag = flask.Markup("{slots}").format(url=url, slots=0) + used_tag = markupsafe.Markup("{slots}").format(url=url, slots=0) url = flask.url_for('TaskInstanceModelView.list', _flt_3_pool='test-pool', _flt_3_state='queued') - queued_tag = flask.Markup("{slots}").format(url=url, slots=0) + queued_tag = markupsafe.Markup("{slots}").format(url=url, slots=0) check_content_in_response(used_tag, resp) check_content_in_response(queued_tag, resp) diff --git a/tests/www/views/test_views_tasks.py b/tests/www/views/test_views_tasks.py index 932ea335b0c53..288d93be6fda2 100644 --- a/tests/www/views/test_views_tasks.py +++ b/tests/www/views/test_views_tasks.py @@ -56,18 +56,21 @@ def init_dagruns(app, reset_dagruns): app.dag_bag.get_dag("example_bash_operator").create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) app.dag_bag.get_dag("example_subdag_operator").create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) app.dag_bag.get_dag("example_xcom").create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) @@ -259,6 +262,7 @@ def test_dag_details_trigger_origin_tree_view(app, admin_client): app.dag_bag.get_dag('test_tree_view').create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, ) @@ -274,6 +278,7 @@ def test_dag_details_trigger_origin_graph_view(app, admin_client): app.dag_bag.get_dag('test_graph_view').create_dagrun( run_type=DagRunType.SCHEDULED, execution_date=DEFAULT_DATE, + data_interval=(DEFAULT_DATE, DEFAULT_DATE), start_date=timezone.utcnow(), state=State.RUNNING, )