app.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #
  2. # Licensed to the Apache Software Foundation (ASF) under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. The ASF licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing,
  13. # software distributed under the License is distributed on an
  14. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. # KIND, either express or implied. See the License for the
  16. # specific language governing permissions and limitations
  17. # under the License.
  18. from __future__ import annotations
  19. import warnings
  20. from datetime import timedelta
  21. from os.path import isabs
  22. from flask import Flask
  23. from flask_appbuilder import SQLA
  24. from flask_wtf.csrf import CSRFProtect
  25. from markupsafe import Markup
  26. from sqlalchemy.engine.url import make_url
  27. from airflow import settings
  28. from airflow.api_internal.internal_api_call import InternalApiConfig
  29. from airflow.configuration import conf
  30. from airflow.exceptions import AirflowConfigException, RemovedInAirflow3Warning
  31. from airflow.logging_config import configure_logging
  32. from airflow.models import import_all_models
  33. from airflow.settings import _ENABLE_AIP_44
  34. from airflow.utils.json import AirflowJsonProvider
  35. from airflow.www.extensions.init_appbuilder import init_appbuilder
  36. from airflow.www.extensions.init_appbuilder_links import init_appbuilder_links
  37. from airflow.www.extensions.init_auth_manager import get_auth_manager
  38. from airflow.www.extensions.init_cache import init_cache
  39. from airflow.www.extensions.init_dagbag import init_dagbag
  40. from airflow.www.extensions.init_jinja_globals import init_jinja_globals
  41. from airflow.www.extensions.init_manifest_files import configure_manifest_files
  42. from airflow.www.extensions.init_robots import init_robots
  43. from airflow.www.extensions.init_security import (
  44. init_api_experimental_auth,
  45. init_cache_control,
  46. init_check_user_active,
  47. init_xframe_protection,
  48. )
  49. from airflow.www.extensions.init_session import init_airflow_session_interface
  50. from airflow.www.extensions.init_views import (
  51. init_api_auth_provider,
  52. init_api_connexion,
  53. init_api_error_handlers,
  54. init_api_experimental,
  55. init_api_internal,
  56. init_appbuilder_views,
  57. init_error_handlers,
  58. init_flash_views,
  59. init_plugins,
  60. )
  61. from airflow.www.extensions.init_wsgi_middlewares import init_wsgi_middleware
  62. app: Flask | None = None
  63. # Initializes at the module level, so plugins can access it.
  64. # See: /docs/plugins.rst
  65. csrf = CSRFProtect()
  66. def create_app(config=None, testing=False):
  67. """Create a new instance of Airflow WWW app."""
  68. flask_app = Flask(__name__)
  69. flask_app.secret_key = conf.get("webserver", "SECRET_KEY")
  70. flask_app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=settings.get_session_lifetime_config())
  71. flask_app.config["MAX_CONTENT_LENGTH"] = conf.getfloat("webserver", "allowed_payload_size") * 1024 * 1024
  72. flask_app.config["MAX_FORM_PARTS"] = conf.getint("webserver", "max_form_parts")
  73. flask_app.config["MAX_FORM_MEMORY_SIZE"] = conf.getint("webserver", "max_form_memory_size")
  74. webserver_config = conf.get_mandatory_value("webserver", "config_file")
  75. # Enable customizations in webserver_config.py to be applied via Flask.current_app.
  76. with flask_app.app_context():
  77. flask_app.config.from_pyfile(webserver_config, silent=True)
  78. flask_app.config["TESTING"] = testing
  79. flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database", "SQL_ALCHEMY_CONN")
  80. instance_name = conf.get(section="webserver", key="instance_name", fallback="Airflow")
  81. require_confirmation_dag_change = conf.getboolean(
  82. section="webserver", key="require_confirmation_dag_change", fallback=False
  83. )
  84. instance_name_has_markup = conf.getboolean(
  85. section="webserver", key="instance_name_has_markup", fallback=False
  86. )
  87. if instance_name_has_markup:
  88. instance_name = Markup(instance_name).striptags()
  89. flask_app.config["APP_NAME"] = instance_name
  90. flask_app.config["REQUIRE_CONFIRMATION_DAG_CHANGE"] = require_confirmation_dag_change
  91. url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
  92. if url.drivername == "sqlite" and url.database and not isabs(url.database):
  93. raise AirflowConfigException(
  94. f'Cannot use relative path: `{conf.get("database", "SQL_ALCHEMY_CONN")}` to connect to sqlite. '
  95. "Please use absolute path such as `sqlite:////tmp/airflow.db`."
  96. )
  97. flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
  98. flask_app.config["SESSION_COOKIE_HTTPONLY"] = True
  99. flask_app.config["SESSION_COOKIE_SECURE"] = conf.getboolean("webserver", "COOKIE_SECURE")
  100. cookie_samesite_config = conf.get("webserver", "COOKIE_SAMESITE")
  101. if cookie_samesite_config == "":
  102. warnings.warn(
  103. "Old deprecated value found for `cookie_samesite` option in `[webserver]` section. "
  104. "Using `Lax` instead. Change the value to `Lax` in airflow.cfg to remove this warning.",
  105. RemovedInAirflow3Warning,
  106. stacklevel=2,
  107. )
  108. cookie_samesite_config = "Lax"
  109. flask_app.config["SESSION_COOKIE_SAMESITE"] = cookie_samesite_config
  110. # Above Flask 2.0.x, default value of SEND_FILE_MAX_AGE_DEFAULT changed 12 hours to None.
  111. # for static file caching, it needs to set value explicitly.
  112. flask_app.config["SEND_FILE_MAX_AGE_DEFAULT"] = timedelta(seconds=43200)
  113. if config:
  114. flask_app.config.from_mapping(config)
  115. if "SQLALCHEMY_ENGINE_OPTIONS" not in flask_app.config:
  116. flask_app.config["SQLALCHEMY_ENGINE_OPTIONS"] = settings.prepare_engine_args()
  117. # Configure the JSON encoder used by `|tojson` filter from Flask
  118. flask_app.json_provider_class = AirflowJsonProvider
  119. flask_app.json = AirflowJsonProvider(flask_app)
  120. if conf.getboolean("core", "database_access_isolation", fallback=False):
  121. InternalApiConfig.set_use_database_access("Gunicorn worker initialization")
  122. csrf.init_app(flask_app)
  123. init_wsgi_middleware(flask_app)
  124. db = SQLA()
  125. db.session = settings.Session
  126. db.init_app(flask_app)
  127. init_dagbag(flask_app)
  128. init_api_experimental_auth(flask_app)
  129. init_robots(flask_app)
  130. init_cache(flask_app)
  131. init_flash_views(flask_app)
  132. configure_logging()
  133. configure_manifest_files(flask_app)
  134. import_all_models()
  135. with flask_app.app_context():
  136. init_appbuilder(flask_app)
  137. init_appbuilder_views(flask_app)
  138. init_appbuilder_links(flask_app)
  139. init_plugins(flask_app)
  140. init_error_handlers(flask_app)
  141. init_api_connexion(flask_app)
  142. if conf.getboolean("webserver", "run_internal_api", fallback=False):
  143. if not _ENABLE_AIP_44:
  144. raise RuntimeError("The AIP_44 is not enabled so you cannot use it.")
  145. init_api_internal(flask_app)
  146. init_api_experimental(flask_app)
  147. init_api_auth_provider(flask_app)
  148. init_api_error_handlers(flask_app) # needs to be after all api inits to let them add their path first
  149. get_auth_manager().init()
  150. init_jinja_globals(flask_app)
  151. init_xframe_protection(flask_app)
  152. init_cache_control(flask_app)
  153. init_airflow_session_interface(flask_app)
  154. init_check_user_active(flask_app)
  155. return flask_app
  156. def cached_app(config=None, testing=False):
  157. """Return cached instance of Airflow WWW app."""
  158. global app
  159. if not app:
  160. app = create_app(config=config, testing=testing)
  161. return app
  162. def purge_cached_app():
  163. """Remove the cached version of the app in global state."""
  164. global app
  165. app = None