Coverage for /home/airflow/.local/lib/python3.12/site-packages/airflow/api_fastapi/core_api/routes/public/plugins.py: 91%
31 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 14:22 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 14:22 +0000
1# Licensed to the Apache Software Foundation (ASF) under one
2# or more contributor license agreements. See the NOTICE file
3# distributed with this work for additional information
4# regarding copyright ownership. The ASF licenses this file
5# to you under the Apache License, Version 2.0 (the
6# "License"); you may not use this file except in compliance
7# with the License. You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing,
12# software distributed under the License is distributed on an
13# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14# KIND, either express or implied. See the License for the
15# specific language governing permissions and limitations
16# under the License.
18from __future__ import annotations
20import structlog
21from fastapi import Depends
22from pydantic import ValidationError
24from airflow import plugins_manager
25from airflow.api_fastapi.auth.managers.models.resource_details import AccessView
26from airflow.api_fastapi.common.parameters import QueryLimit, QueryOffset
27from airflow.api_fastapi.common.router import AirflowRouter
28from airflow.api_fastapi.core_api.datamodels.plugins import (
29 PluginCollectionResponse,
30 PluginImportErrorCollectionResponse,
31 PluginResponse,
32)
33from airflow.api_fastapi.core_api.security import requires_access_view
35logger = structlog.get_logger(__name__)
37plugins_router = AirflowRouter(tags=["Plugin"], prefix="/plugins")
40@plugins_router.get(
41 "",
42 dependencies=[Depends(requires_access_view(AccessView.PLUGINS))],
43)
44def get_plugins(
45 limit: QueryLimit,
46 offset: QueryOffset,
47) -> PluginCollectionResponse:
48 plugins_info = sorted(plugins_manager.get_plugin_info(), key=lambda x: x["name"])
49 valid_plugins: list[PluginResponse] = []
50 for plugin_dict in plugins_info:
51 try:
52 # Validate each plugin individually
53 plugin = PluginResponse.model_validate(plugin_dict)
54 valid_plugins.append(plugin)
55 except ValidationError as e:
56 logger.warning(
57 "Skipping invalid plugin due to error",
58 plugin_name=plugin_dict.get("name", "<unknown>"),
59 error=str(e),
60 )
61 continue
63 offset_value = offset.value or 0
64 limit_value = limit.value if limit.value is not None else len(valid_plugins)
66 paginated_plugins = valid_plugins[offset_value : offset_value + limit_value]
67 return PluginCollectionResponse(
68 plugins=paginated_plugins,
69 total_entries=len(valid_plugins),
70 )
73@plugins_router.get(
74 "/importErrors",
75 dependencies=[Depends(requires_access_view(AccessView.PLUGINS))],
76)
77def import_errors() -> PluginImportErrorCollectionResponse:
78 import_errors = plugins_manager.get_import_errors()
79 return PluginImportErrorCollectionResponse.model_validate(
80 {
81 "import_errors": [{"source": source, "error": error} for source, error in import_errors.items()],
82 "total_entries": len(import_errors),
83 }
84 )