This is an automated email from the ASF dual-hosted git repository.

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 56a26f956ab fab: centralize FastAPI auth manager routing (#61647)
56a26f956ab is described below

commit 56a26f956ab45ef7c3f19e3cafe89fc1e7309513
Author: Henry Chen <[email protected]>
AuthorDate: Sat Feb 21 01:33:07 2026 +0800

    fab: centralize FastAPI auth manager routing (#61647)
    
    * fab: centralize FastAPI auth manager routing
    
    * Add register_routes
---
 .../fab/auth_manager/api_fastapi/routes/login.py   | 10 +++---
 .../fab/auth_manager/api_fastapi/routes/roles.py   | 16 +++++-----
 .../fab/auth_manager/api_fastapi/routes/router.py  | 36 ++++++++++++++++++++++
 .../fab/auth_manager/api_fastapi/routes/users.py   | 14 ++++-----
 .../providers/fab/auth_manager/fab_auth_manager.py | 15 +++++----
 .../auth_manager/api_fastapi/routes/test_router.py | 36 ++++++++++++++++++++++
 6 files changed, 96 insertions(+), 31 deletions(-)

diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
index afd7b230d1c..d2bbd50276f 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
@@ -23,17 +23,15 @@ from fastapi.responses import RedirectResponse
 
 from airflow.api_fastapi.app import get_auth_manager
 from airflow.api_fastapi.auth.managers.base_auth_manager import 
COOKIE_NAME_JWT_TOKEN
-from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
 from airflow.configuration import conf
 from airflow.providers.fab.auth_manager.api_fastapi.datamodels.login import 
LoginResponse
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import 
auth_router
 from airflow.providers.fab.auth_manager.api_fastapi.services.login import 
FABAuthManagerLogin
 from airflow.providers.fab.auth_manager.cli_commands.utils import 
get_application_builder
 
-login_router = AirflowRouter(tags=["FabAuthManager"])
 
-
-@login_router.post(
+@auth_router.post(
     "/token",
     response_model=LoginResponse,
     status_code=status.HTTP_201_CREATED,
@@ -45,7 +43,7 @@ def create_token(request: Request, body: dict[str, Any] = 
Body(...)) -> LoginRes
         return FABAuthManagerLogin.create_token(headers=dict(request.headers), 
body=body)
 
 
-@login_router.post(
+@auth_router.post(
     "/token/cli",
     response_model=LoginResponse,
     status_code=status.HTTP_201_CREATED,
@@ -61,7 +59,7 @@ def create_token_cli(request: Request, body: dict[str, Any] = 
Body(...)) -> Logi
         )
 
 
-@login_router.get(
+@auth_router.get(
     "/logout",
     status_code=status.HTTP_307_TEMPORARY_REDIRECT,
 )
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
index 460cfd47053..64668b56659 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py
@@ -18,7 +18,6 @@ from __future__ import annotations
 
 from fastapi import Depends, Path, Query, status
 
-from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
 from airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import (
     PermissionCollectionResponse,
@@ -27,15 +26,14 @@ from 
airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import (
     RoleResponse,
 )
 from airflow.providers.fab.auth_manager.api_fastapi.parameters import 
get_effective_limit
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import 
fab_router
 from airflow.providers.fab.auth_manager.api_fastapi.security import 
requires_fab_custom_view
 from airflow.providers.fab.auth_manager.api_fastapi.services.roles import 
FABAuthManagerRoles
 from airflow.providers.fab.auth_manager.cli_commands.utils import 
get_application_builder
 from airflow.providers.fab.www.security import permissions
 
-roles_router = AirflowRouter(prefix="/fab/v1", tags=["FabAuthManager"])
 
-
-@roles_router.post(
+@fab_router.post(
     "/roles",
     responses=create_openapi_http_exception_doc(
         [
@@ -54,7 +52,7 @@ def create_role(body: RoleBody) -> RoleResponse:
         return FABAuthManagerRoles.create_role(body=body)
 
 
-@roles_router.get(
+@fab_router.get(
     "/roles",
     response_model=RoleCollectionResponse,
     responses=create_openapi_http_exception_doc(
@@ -77,7 +75,7 @@ def get_roles(
         return FABAuthManagerRoles.get_roles(order_by=order_by, limit=limit, 
offset=offset)
 
 
-@roles_router.delete(
+@fab_router.delete(
     "/roles/{name}",
     responses=create_openapi_http_exception_doc(
         [
@@ -94,7 +92,7 @@ def delete_role(name: str = Path(..., min_length=1)) -> None:
         return FABAuthManagerRoles.delete_role(name=name)
 
 
-@roles_router.get(
+@fab_router.get(
     "/roles/{name}",
     responses=create_openapi_http_exception_doc(
         [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, 
status.HTTP_404_NOT_FOUND]
@@ -107,7 +105,7 @@ def get_role(name: str = Path(..., min_length=1)) -> 
RoleResponse:
         return FABAuthManagerRoles.get_role(name=name)
 
 
-@roles_router.patch(
+@fab_router.patch(
     "/roles/{name}",
     responses=create_openapi_http_exception_doc(
         [
@@ -129,7 +127,7 @@ def patch_role(
         return FABAuthManagerRoles.patch_role(name=name, body=body, 
update_mask=update_mask)
 
 
-@roles_router.get(
+@fab_router.get(
     "/permissions",
     response_model=PermissionCollectionResponse,
     responses=create_openapi_http_exception_doc(
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/router.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/router.py
new file mode 100644
index 00000000000..ba64036b93d
--- /dev/null
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/router.py
@@ -0,0 +1,36 @@
+# 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
+
+from enum import Enum
+
+from airflow.api_fastapi.common.router import AirflowRouter
+
+FAB_AUTH_TAGS: list[str | Enum] = ["FabAuthManager"]
+FAB_AUTH_PREFIX = "/fab/v1"
+
+auth_router = AirflowRouter(tags=FAB_AUTH_TAGS)
+fab_router = AirflowRouter(prefix=FAB_AUTH_PREFIX, tags=FAB_AUTH_TAGS)
+
+
+def register_routes() -> None:
+    """Register FastAPI routes by importing modules for side effects."""
+    import importlib
+
+    
importlib.import_module("airflow.providers.fab.auth_manager.api_fastapi.routes.login")
+    
importlib.import_module("airflow.providers.fab.auth_manager.api_fastapi.routes.roles")
+    
importlib.import_module("airflow.providers.fab.auth_manager.api_fastapi.routes.users")
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
index b26433984f0..a42c3dd5baa 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/users.py
@@ -18,7 +18,6 @@ from __future__ import annotations
 
 from fastapi import Depends, Path, Query, status
 
-from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
 from airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import (
     UserBody,
@@ -27,15 +26,14 @@ from 
airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import (
     UserResponse,
 )
 from airflow.providers.fab.auth_manager.api_fastapi.parameters import 
get_effective_limit
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import 
fab_router
 from airflow.providers.fab.auth_manager.api_fastapi.security import 
requires_fab_custom_view
 from airflow.providers.fab.auth_manager.api_fastapi.services.users import 
FABAuthManagerUsers
 from airflow.providers.fab.auth_manager.cli_commands.utils import 
get_application_builder
 from airflow.providers.fab.www.security import permissions
 
-users_router = AirflowRouter(prefix="/fab/v1", tags=["FabAuthManager"])
 
-
-@users_router.post(
+@fab_router.post(
     "/users",
     responses=create_openapi_http_exception_doc(
         [
@@ -53,7 +51,7 @@ def create_user(body: UserBody) -> UserResponse:
         return FABAuthManagerUsers.create_user(body=body)
 
 
-@users_router.get(
+@fab_router.get(
     "/users",
     response_model=UserCollectionResponse,
     responses=create_openapi_http_exception_doc(
@@ -75,7 +73,7 @@ def get_users(
         return FABAuthManagerUsers.get_users(order_by=order_by, limit=limit, 
offset=offset)
 
 
-@users_router.get(
+@fab_router.get(
     "/users/{username}",
     responses=create_openapi_http_exception_doc(
         [
@@ -92,7 +90,7 @@ def get_user(username: str = Path(..., min_length=1)) -> 
UserResponse:
         return FABAuthManagerUsers.get_user(username=username)
 
 
-@users_router.patch(
+@fab_router.patch(
     "/users/{username}",
     responses=create_openapi_http_exception_doc(
         [
@@ -115,7 +113,7 @@ def update_user(
         return FABAuthManagerUsers.update_user(username=username, body=body, 
update_mask=update_mask)
 
 
-@users_router.delete(
+@fab_router.delete(
     "/users/{username}",
     status_code=status.HTTP_204_NO_CONTENT,
     responses=create_openapi_http_exception_doc(
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py 
b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
index 57ae2c4289d..e7f4278fad3 100644
--- a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
+++ b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
@@ -200,11 +200,11 @@ class FabAuthManager(BaseAuthManager[User]):
 
     def get_fastapi_app(self) -> FastAPI | None:
         """Get the FastAPI app."""
-        from airflow.providers.fab.auth_manager.api_fastapi.routes.login 
import (
-            login_router,
+        from airflow.providers.fab.auth_manager.api_fastapi.routes.router 
import (
+            auth_router,
+            fab_router,
+            register_routes,
         )
-        from airflow.providers.fab.auth_manager.api_fastapi.routes.roles 
import roles_router
-        from airflow.providers.fab.auth_manager.api_fastapi.routes.users 
import users_router
 
         flask_app = create_app(enable_plugins=False)
 
@@ -218,10 +218,9 @@ class FabAuthManager(BaseAuthManager[User]):
             ),
         )
 
-        # Add the login router to the FastAPI app
-        app.include_router(login_router)
-        app.include_router(roles_router)
-        app.include_router(users_router)
+        register_routes()
+        app.include_router(auth_router)
+        app.include_router(fab_router)
 
         # Session cleanup middleware to prevent PendingRollbackError.
         # FAB's Flask views (e.g., /users/list/, /roles/list/) are mounted 
below via
diff --git 
a/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_router.py 
b/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_router.py
new file mode 100644
index 00000000000..6655fbf1107
--- /dev/null
+++ 
b/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_router.py
@@ -0,0 +1,36 @@
+# 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
+
+from airflow.providers.fab.auth_manager.api_fastapi.routes.router import (
+    FAB_AUTH_PREFIX,
+    auth_router,
+    fab_router,
+)
+
+
+def test_root_routers_share_tags() -> None:
+    assert auth_router.tags == fab_router.tags
+
+
+def test_fab_router_prefix() -> None:
+    assert fab_router.prefix == FAB_AUTH_PREFIX
+
+
+def test_auth_router_prefix() -> None:
+    assert auth_router.prefix == ""

Reply via email to