apache-airflow-providers-fab 3.0.1__py3-none-any.whl → 3.1.1rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. airflow/providers/fab/__init__.py +1 -1
  2. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +2 -2
  3. airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +0 -7
  4. airflow/providers/fab/auth_manager/api_fastapi/datamodels/roles.py +63 -0
  5. airflow/providers/fab/auth_manager/api_fastapi/openapi/v2-fab-auth-manager-generated.yaml +416 -16
  6. airflow/providers/fab/auth_manager/api_fastapi/parameters.py +55 -0
  7. airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +37 -5
  8. airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py +137 -0
  9. airflow/providers/fab/auth_manager/api_fastapi/security.py +32 -0
  10. airflow/providers/fab/auth_manager/api_fastapi/services/login.py +12 -25
  11. airflow/providers/fab/auth_manager/api_fastapi/services/roles.py +158 -0
  12. airflow/providers/fab/auth_manager/api_fastapi/sorting.py +49 -0
  13. airflow/providers/fab/auth_manager/cli_commands/permissions_command.py +6 -2
  14. airflow/providers/fab/auth_manager/fab_auth_manager.py +33 -3
  15. airflow/providers/fab/auth_manager/models/__init__.py +3 -8
  16. airflow/providers/fab/auth_manager/models/db.py +1 -1
  17. airflow/providers/fab/auth_manager/security_manager/override.py +60 -17
  18. airflow/providers/fab/version_compat.py +1 -0
  19. airflow/providers/fab/www/api_connexion/parameters.py +1 -46
  20. airflow/providers/fab/www/app.py +13 -10
  21. airflow/providers/fab/www/extensions/init_appbuilder.py +5 -2
  22. airflow/providers/fab/www/extensions/init_security.py +1 -1
  23. airflow/providers/fab/www/extensions/init_views.py +11 -7
  24. airflow/providers/fab/www/package-lock.json +417 -265
  25. airflow/providers/fab/www/package.json +13 -10
  26. airflow/providers/fab/www/session.py +5 -8
  27. airflow/providers/fab/www/static/dist/{743.935ed3d26e56ed8f63d3.js → 743.0c0bf201ae17e66a9a3f.js} +1 -1
  28. airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.js → main.bc1f701c3d133e2a3bab.js} +1 -1
  29. airflow/providers/fab/www/static/dist/manifest.json +13 -13
  30. airflow/providers/fab/www/views.py +18 -14
  31. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/METADATA +15 -14
  32. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/RECORD +51 -45
  33. /airflow/providers/fab/migrations/versions/{0001_1_4_0_create_ab_tables_if_missing.py → 0000_1_4_0_create_ab_tables_if_missing.py} +0 -0
  34. /airflow/providers/fab/www/static/dist/{743.935ed3d26e56ed8f63d3.js.LICENSE.txt → 743.0c0bf201ae17e66a9a3f.js.LICENSE.txt} +0 -0
  35. /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.ff5a35f322070b094aa2.css → airflowDefaultTheme.ef6fc04c9b6920cd75c9.css} +0 -0
  36. /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.ff5a35f322070b094aa2.js → airflowDefaultTheme.ef6fc04c9b6920cd75c9.js} +0 -0
  37. /airflow/providers/fab/www/static/dist/{flash.5583a9e0cf11f2be93da.css → flash.eaaf777ec1b3628cf7be.css} +0 -0
  38. /airflow/providers/fab/www/static/dist/{flash.5583a9e0cf11f2be93da.js → flash.eaaf777ec1b3628cf7be.js} +0 -0
  39. /airflow/providers/fab/www/static/dist/{loadingDots.2e5f555f0753107b0300.css → loadingDots.76f4332c0a932c3dc08f.css} +0 -0
  40. /airflow/providers/fab/www/static/dist/{loadingDots.2e5f555f0753107b0300.js → loadingDots.76f4332c0a932c3dc08f.js} +0 -0
  41. /airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.css → main.bc1f701c3d133e2a3bab.css} +0 -0
  42. /airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.js.LICENSE.txt → main.bc1f701c3d133e2a3bab.js.LICENSE.txt} +0 -0
  43. /airflow/providers/fab/www/static/dist/{materialIcons.3e67dd6fbfcc4f3b5105.css → materialIcons.ad07a489b2f0fc1a96bf.css} +0 -0
  44. /airflow/providers/fab/www/static/dist/{materialIcons.3e67dd6fbfcc4f3b5105.js → materialIcons.ad07a489b2f0fc1a96bf.js} +0 -0
  45. /airflow/providers/fab/www/static/dist/{moment.9baee5ec3d7639a10897.js → moment.5b85b4f6be2fe9c405ac.js} +0 -0
  46. /airflow/providers/fab/www/static/dist/{runtime.6ad9da077ea169d60db9.js → runtime.254c277d91ce3ac79c64.js} +0 -0
  47. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/WHEEL +0 -0
  48. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/entry_points.txt +0 -0
  49. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
  50. {airflow/providers/fab → apache_airflow_providers_fab-3.1.1rc1.dist-info/licenses}/LICENSE +0 -0
  51. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/licenses/NOTICE +0 -0
@@ -67,7 +67,7 @@ from sqlalchemy.orm import joinedload
67
67
  from werkzeug.security import check_password_hash, generate_password_hash
68
68
 
69
69
  from airflow.configuration import conf
70
- from airflow.exceptions import AirflowException
70
+ from airflow.providers.common.compat.sdk import AirflowException
71
71
  from airflow.providers.fab.auth_manager.models import (
72
72
  Action,
73
73
  Group,
@@ -101,7 +101,6 @@ from airflow.providers.fab.version_compat import AIRFLOW_V_3_1_PLUS
101
101
  from airflow.providers.fab.www.security import permissions
102
102
  from airflow.providers.fab.www.security_manager import AirflowSecurityManagerV2
103
103
  from airflow.providers.fab.www.session import AirflowDatabaseSessionInterface
104
- from airflow.security.permissions import RESOURCE_BACKFILL
105
104
 
106
105
  if TYPE_CHECKING:
107
106
  from airflow.providers.fab.www.security.permissions import (
@@ -109,7 +108,7 @@ if TYPE_CHECKING:
109
108
  RESOURCE_ASSET_ALIAS,
110
109
  )
111
110
  from airflow.sdk import DAG
112
- from airflow.serialization.serialized_objects import SerializedDAG
111
+ from airflow.serialization.definitions.dag import SerializedDAG
113
112
  else:
114
113
  from airflow.providers.common.compat.security.permissions import (
115
114
  RESOURCE_ASSET,
@@ -124,11 +123,17 @@ if AIRFLOW_V_3_1_PLUS:
124
123
  with create_session() as session:
125
124
  yield from DBDagBag().iter_all_latest_version_dags(session=session)
126
125
  else:
127
- from airflow.models.dagbag import DagBag # type: ignore[attr-defined, no-redef]
126
+ try:
127
+ from airflow.models.dagbag import DagBag
128
+ except (ImportError, AttributeError):
129
+ DagBag = None
128
130
 
129
131
  def _iter_dags() -> Iterable[DAG | SerializedDAG]:
130
- dagbag = DagBag(read_dags_from_db=True) # type: ignore[call-arg]
131
- dagbag.collect_dags_from_db() # type: ignore[attr-defined]
132
+ if DagBag is None:
133
+ return []
134
+ dagbag = DagBag(read_dags_from_db=True)
135
+ if hasattr(dagbag, "collect_dags_from_db"):
136
+ dagbag.collect_dags_from_db()
132
137
  return dagbag.dags.values()
133
138
 
134
139
 
@@ -230,7 +235,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
230
235
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG_WARNING),
231
236
  (permissions.ACTION_CAN_READ, RESOURCE_ASSET),
232
237
  (permissions.ACTION_CAN_READ, RESOURCE_ASSET_ALIAS),
233
- (permissions.ACTION_CAN_READ, RESOURCE_BACKFILL),
238
+ (permissions.ACTION_CAN_READ, permissions.RESOURCE_BACKFILL),
234
239
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_CLUSTER_ACTIVITY),
235
240
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_POOL),
236
241
  (permissions.ACTION_CAN_READ, permissions.RESOURCE_IMPORT_ERROR),
@@ -302,9 +307,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
302
307
  (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_XCOM),
303
308
  (permissions.ACTION_CAN_CREATE, RESOURCE_ASSET),
304
309
  (permissions.ACTION_CAN_DELETE, RESOURCE_ASSET),
305
- (permissions.ACTION_CAN_CREATE, RESOURCE_BACKFILL),
306
- (permissions.ACTION_CAN_EDIT, RESOURCE_BACKFILL),
307
- (permissions.ACTION_CAN_DELETE, RESOURCE_BACKFILL),
310
+ (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_BACKFILL),
311
+ (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_BACKFILL),
312
+ (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_BACKFILL),
308
313
  ]
309
314
  # [END security_op_perms]
310
315
 
@@ -727,6 +732,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
727
732
  """The JMESPATH role to use for user registration."""
728
733
  return current_app.config["AUTH_USER_REGISTRATION_ROLE_JMESPATH"]
729
734
 
735
+ @property
736
+ def auth_remote_user_env_var(self) -> str:
737
+ return current_app.config["AUTH_REMOTE_USER_ENV_VAR"]
738
+
730
739
  @property
731
740
  def auth_username_ci(self):
732
741
  """Get the auth username for CI."""
@@ -1039,7 +1048,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1039
1048
  self.remove_permission_from_role(role, perm)
1040
1049
 
1041
1050
  # Adding the access control permissions
1042
- for rolename, resource_actions in access_control.items():
1051
+ for rolename, resource_actions_raw in access_control.items():
1043
1052
  role = self.find_role(rolename)
1044
1053
  if not role:
1045
1054
  raise AirflowException(
@@ -1047,9 +1056,12 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1047
1056
  f"'{rolename}', but that role does not exist"
1048
1057
  )
1049
1058
 
1050
- if not isinstance(resource_actions, dict):
1051
- # Support for old-style access_control where only the actions are specified
1052
- resource_actions = {permissions.RESOURCE_DAG: set(resource_actions)}
1059
+ # Support for old-style access_control where only the actions are specified
1060
+ resource_actions = (
1061
+ resource_actions_raw
1062
+ if isinstance(resource_actions_raw, dict)
1063
+ else {permissions.RESOURCE_DAG: set(resource_actions_raw)}
1064
+ )
1053
1065
 
1054
1066
  for resource_name, actions in resource_actions.items():
1055
1067
  if resource_name not in self.RESOURCE_DETAILS_MAP:
@@ -1643,7 +1655,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1643
1655
  return perm
1644
1656
  resource = self.create_resource(resource_name)
1645
1657
  if resource is None:
1646
- log.error(const.LOGMSG_ERR_SEC_ADD_PERMVIEW, f"Resource creation failed {resource_name}")
1658
+ log.error(const.LOGMSG_ERR_SEC_ADD_PERMVIEW, "Resource creation failed %s", resource_name)
1647
1659
  return None
1648
1660
  action = self.create_action(action_name)
1649
1661
  perm = self.permission_model()
@@ -2006,7 +2018,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2006
2018
  if _provider["name"] == provider:
2007
2019
  return _provider.get("token_secret", "oauth_token_secret")
2008
2020
 
2009
- def auth_user_oauth(self, userinfo):
2021
+ def auth_user_oauth(self, userinfo, rotate_session_id=True):
2010
2022
  """
2011
2023
  Authenticate user with OAuth.
2012
2024
 
@@ -2060,7 +2072,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2060
2072
 
2061
2073
  # LOGIN SUCCESS (only if user is now registered)
2062
2074
  if user:
2063
- self._rotate_session_id()
2075
+ if rotate_session_id:
2076
+ self._rotate_session_id()
2064
2077
  self.update_user_auth_stat(user)
2065
2078
  return user
2066
2079
  return None
@@ -2204,6 +2217,36 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2204
2217
  # decode - if empty string, default to fallback, otherwise take first element
2205
2218
  return raw_value[0].decode("utf-8") or fallback
2206
2219
 
2220
+ def auth_user_remote_user(self, username):
2221
+ """
2222
+ REMOTE_USER user Authentication.
2223
+
2224
+ :param username: user's username for remote auth
2225
+ """
2226
+ user = self.find_user(username=username)
2227
+
2228
+ # User does not exist, create one if auto user registration.
2229
+ if user is None and self.auth_user_registration:
2230
+ user = self.add_user(
2231
+ # All we have is REMOTE_USER, so we set
2232
+ # the other fields to blank.
2233
+ username=username,
2234
+ first_name=username,
2235
+ last_name="-",
2236
+ email=username + "@email.notfound",
2237
+ role=self.find_role(self.auth_user_registration_role),
2238
+ )
2239
+
2240
+ # If user does not exist on the DB and not auto user registration,
2241
+ # or user is inactive, go away.
2242
+ elif user is None or (not user.is_active):
2243
+ log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
2244
+ return None
2245
+
2246
+ self._rotate_session_id()
2247
+ self.update_user_auth_stat(user)
2248
+ return user
2249
+
2207
2250
  """
2208
2251
  ---------------
2209
2252
  Private methods
@@ -34,3 +34,4 @@ def get_base_airflow_version_tuple() -> tuple[int, int, int]:
34
34
 
35
35
  AIRFLOW_V_3_1_PLUS = get_base_airflow_version_tuple() >= (3, 1, 0)
36
36
  AIRFLOW_V_3_1_1_PLUS = get_base_airflow_version_tuple() >= (3, 1, 1)
37
+ AIRFLOW_V_3_2_PLUS = get_base_airflow_version_tuple() >= (3, 2, 0)
@@ -17,21 +17,16 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  import logging
20
- from collections.abc import Callable, Container
20
+ from collections.abc import Callable
21
21
  from functools import wraps
22
22
  from typing import TYPE_CHECKING, Any, TypeVar, cast
23
23
 
24
- from pendulum.parsing import ParserError
25
- from sqlalchemy import text
26
-
27
24
  from airflow.configuration import conf
28
25
  from airflow.providers.fab.www.api_connexion.exceptions import BadRequest
29
- from airflow.utils import timezone
30
26
 
31
27
  if TYPE_CHECKING:
32
28
  from datetime import datetime
33
29
 
34
- from sqlalchemy.sql import Select
35
30
 
36
31
  log = logging.getLogger(__name__)
37
32
 
@@ -42,24 +37,6 @@ def validate_istimezone(value: datetime) -> None:
42
37
  raise BadRequest("Invalid datetime format", detail="Naive datetime is disallowed")
43
38
 
44
39
 
45
- def format_datetime(value: str) -> datetime:
46
- """
47
- Format datetime objects.
48
-
49
- Datetime format parser for args since connexion doesn't parse datetimes
50
- https://github.com/zalando/connexion/issues/476
51
-
52
- This should only be used within connection views because it raises 400
53
- """
54
- value = value.strip()
55
- if value[-1] != "Z":
56
- value = value.replace(" ", "+")
57
- try:
58
- return timezone.parse(value)
59
- except (ParserError, TypeError) as err:
60
- raise BadRequest("Incorrect datetime argument", detail=str(err))
61
-
62
-
63
40
  def check_limit(value: int) -> int:
64
41
  """
65
42
  Check the limit does not exceed configured value.
@@ -107,25 +84,3 @@ def format_parameters(params_formatters: dict[str, Callable[[Any], Any]]) -> Cal
107
84
  return cast("T", wrapped_function)
108
85
 
109
86
  return format_parameters_decorator
110
-
111
-
112
- def apply_sorting(
113
- query: Select,
114
- order_by: str,
115
- to_replace: dict[str, str] | None = None,
116
- allowed_attrs: Container[str] | None = None,
117
- ) -> Select:
118
- """Apply sorting to query."""
119
- lstriped_orderby = order_by.lstrip("-")
120
- if allowed_attrs and lstriped_orderby not in allowed_attrs:
121
- raise BadRequest(
122
- detail=f"Ordering with '{lstriped_orderby}' is disallowed or "
123
- f"the attribute does not exist on the model"
124
- )
125
- if to_replace:
126
- lstriped_orderby = to_replace.get(lstriped_orderby, lstriped_orderby)
127
- if order_by[0] == "-":
128
- order_by = f"{lstriped_orderby} desc"
129
- else:
130
- order_by = f"{lstriped_orderby} asc"
131
- return query.order_by(text(order_by))
@@ -18,6 +18,7 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from datetime import timedelta
21
+ from functools import cache
21
22
  from os.path import isabs
22
23
 
23
24
  from flask import Flask
@@ -30,7 +31,7 @@ from airflow.api_fastapi.app import get_auth_manager
30
31
  from airflow.configuration import conf
31
32
  from airflow.exceptions import AirflowConfigException
32
33
  from airflow.logging_config import configure_logging
33
- from airflow.providers.fab.www.extensions.init_appbuilder import init_appbuilder
34
+ from airflow.providers.fab.www.extensions.init_appbuilder import AirflowAppBuilder
34
35
  from airflow.providers.fab.www.extensions.init_jinja_globals import init_jinja_globals
35
36
  from airflow.providers.fab.www.extensions.init_manifest_files import configure_manifest_files
36
37
  from airflow.providers.fab.www.extensions.init_security import init_api_auth
@@ -44,8 +45,6 @@ from airflow.providers.fab.www.extensions.init_views import (
44
45
  from airflow.providers.fab.www.extensions.init_wsgi_middlewares import init_wsgi_middleware
45
46
  from airflow.providers.fab.www.utils import get_session_lifetime_config
46
47
 
47
- app: Flask | None = None
48
-
49
48
  # Initializes at the module level, so plugins can access it.
50
49
  # See: /docs/plugins.rst
51
50
  csrf = CSRFProtect()
@@ -85,6 +84,8 @@ def create_app(enable_plugins: bool):
85
84
  csrf.init_app(flask_app)
86
85
 
87
86
  db = SQLAlchemy(flask_app)
87
+ if settings.Session is None:
88
+ raise RuntimeError("Session not configured. Call configure_orm() first.")
88
89
  db.session = settings.Session
89
90
 
90
91
  configure_logging()
@@ -92,7 +93,12 @@ def create_app(enable_plugins: bool):
92
93
  init_api_auth(flask_app)
93
94
 
94
95
  with flask_app.app_context():
95
- init_appbuilder(flask_app, enable_plugins=enable_plugins)
96
+ AirflowAppBuilder(
97
+ app=flask_app,
98
+ session=db.session(),
99
+ base_template="airflow/main.html",
100
+ enable_plugins=enable_plugins,
101
+ )
96
102
  init_error_handlers(flask_app)
97
103
  # In two scenarios a Flask application can be created:
98
104
  # - To support Airflow 2 plugins relying on Flask (``enable_plugins`` is True)
@@ -112,15 +118,12 @@ def create_app(enable_plugins: bool):
112
118
  return flask_app
113
119
 
114
120
 
121
+ @cache
115
122
  def cached_app():
116
123
  """Return cached instance of Airflow WWW app."""
117
- global app
118
- if not app:
119
- app = create_app()
120
- return app
124
+ return create_app()
121
125
 
122
126
 
123
127
  def purge_cached_app():
124
128
  """Remove the cached version of the app in global state."""
125
- global app
126
- app = None
129
+ cached_app.cache_clear()
@@ -42,7 +42,7 @@ from airflow import settings
42
42
  from airflow.api_fastapi.app import create_auth_manager, get_auth_manager
43
43
  from airflow.configuration import conf
44
44
  from airflow.providers.fab.www.security_manager import AirflowSecurityManagerV2
45
- from airflow.providers.fab.www.views import FabIndexView
45
+ from airflow.providers.fab.www.views import FabIndexView, redirect
46
46
 
47
47
  if TYPE_CHECKING:
48
48
  from flask import Flask
@@ -216,6 +216,7 @@ class AirflowAppBuilder:
216
216
  from airflow.providers.fab.www.views import get_safe_url
217
217
 
218
218
  fab_sec_views.get_safe_redirect = get_safe_url
219
+ fab_sec_views.redirect = redirect
219
220
 
220
221
  def _init_extension(self, app):
221
222
  app.appbuilder = self
@@ -473,7 +474,7 @@ class AirflowAppBuilder:
473
474
  baseview=baseview,
474
475
  cond=cond,
475
476
  )
476
- if self.app:
477
+ if current_app:
477
478
  self._add_permissions_menu(name)
478
479
  if category:
479
480
  self._add_permissions_menu(category)
@@ -592,6 +593,8 @@ class AirflowAppBuilder:
592
593
 
593
594
  def init_appbuilder(app: Flask, enable_plugins: bool) -> AirflowAppBuilder:
594
595
  """Init `Flask App Builder <https://flask-appbuilder.readthedocs.io/en/latest/>`__."""
596
+ if settings.Session is None:
597
+ raise RuntimeError("Session not configured. Call configure_orm() first.")
595
598
  return AirflowAppBuilder(
596
599
  app=app,
597
600
  session=settings.Session(),
@@ -20,7 +20,7 @@ import logging
20
20
  from importlib import import_module
21
21
 
22
22
  from airflow.configuration import conf
23
- from airflow.exceptions import AirflowException
23
+ from airflow.providers.common.compat.sdk import AirflowException
24
24
 
25
25
  log = logging.getLogger(__name__)
26
26
 
@@ -26,7 +26,7 @@ from connexion.exceptions import BadRequestProblem, ProblemException
26
26
  from flask import request
27
27
 
28
28
  from airflow.api_fastapi.app import get_auth_manager
29
- from airflow.providers.fab.version_compat import AIRFLOW_V_3_1_PLUS
29
+ from airflow.providers.fab.version_compat import AIRFLOW_V_3_1_PLUS, AIRFLOW_V_3_2_PLUS
30
30
  from airflow.providers.fab.www.api_connexion.exceptions import common_error_handler
31
31
 
32
32
  if TYPE_CHECKING:
@@ -102,11 +102,17 @@ def init_plugins(app):
102
102
  """Integrate Flask and FAB with plugins."""
103
103
  from airflow import plugins_manager
104
104
 
105
- plugins_manager.initialize_flask_plugins()
105
+ if AIRFLOW_V_3_2_PLUS:
106
+ blueprints, appbuilder_views, appbuilder_menu_links = plugins_manager.get_flask_plugins()
107
+ else:
108
+ plugins_manager.initialize_flask_plugins() # type: ignore
109
+ blueprints = plugins_manager.flask_blueprints # type: ignore
110
+ appbuilder_views = plugins_manager.flask_appbuilder_views # type: ignore
111
+ appbuilder_menu_links = plugins_manager.flask_appbuilder_menu_links # type: ignore
106
112
 
107
113
  appbuilder = app.appbuilder
108
114
 
109
- for view in plugins_manager.flask_appbuilder_views:
115
+ for view in appbuilder_views:
110
116
  name = view.get("name")
111
117
  if name:
112
118
  filtered_view_kwargs = {k: v for k, v in view.items() if k not in ["view"]}
@@ -124,13 +130,11 @@ def init_plugins(app):
124
130
  # Since Airflow 3.1 flask_appbuilder_menu_links are added to the Airflow 3 UI
125
131
  # navbar..
126
132
  if not AIRFLOW_V_3_1_PLUS:
127
- for menu_link in sorted(
128
- plugins_manager.flask_appbuilder_menu_links, key=lambda x: (x.get("category", ""), x["name"])
129
- ):
133
+ for menu_link in sorted(appbuilder_menu_links, key=lambda x: (x.get("category", ""), x["name"])):
130
134
  log.debug("Adding menu link %s to %s", menu_link["name"], menu_link["href"])
131
135
  appbuilder.add_link(**menu_link)
132
136
 
133
- for blue_print in plugins_manager.flask_blueprints:
137
+ for blue_print in blueprints:
134
138
  log.debug("Adding blueprint %s:%s", blue_print["name"], blue_print["blueprint"].import_name)
135
139
  app.register_blueprint(blue_print["blueprint"])
136
140