Coverage for /home/airflow/.local/lib/python3.12/site-packages/airflow/api_fastapi/core_api/datamodels/plugins.py: 98%
79 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
20from typing import Annotated, Any, Literal
22from pydantic import BeforeValidator, ConfigDict, Field, field_validator, model_validator
24from airflow.api_fastapi.core_api.base import BaseModel
25from airflow.plugins_manager import AirflowPluginSource
28def coerce_to_string(data: Any) -> Any:
29 return str(data)
32class FastAPIAppResponse(BaseModel):
33 """Serializer for Plugin FastAPI App responses."""
35 model_config = ConfigDict(extra="allow")
37 app: str
38 url_prefix: str
39 name: str
42class FastAPIRootMiddlewareResponse(BaseModel):
43 """Serializer for Plugin FastAPI root middleware responses."""
45 model_config = ConfigDict(extra="allow")
47 middleware: str
48 name: str
51class AppBuilderViewResponse(BaseModel):
52 """Serializer for AppBuilder View responses."""
54 model_config = ConfigDict(extra="allow")
56 name: str | None = None
57 category: str | None = None
58 view: str | None = None
59 label: str | None = None
62class AppBuilderMenuItemResponse(BaseModel):
63 """Serializer for AppBuilder Menu Item responses."""
65 model_config = ConfigDict(extra="allow")
67 name: str
68 href: str
69 category: str | None = None
72BaseDestinationLiteral = Literal["nav", "dag", "dag_run", "task", "task_instance", "base"]
75class BaseUIResponse(BaseModel):
76 """Base serializer for UI Plugin responses."""
78 model_config = ConfigDict(extra="allow")
80 name: str
81 icon: str | None = None
82 icon_dark_mode: str | None = None
83 url_route: str | None = None
84 category: str | None = None
85 nav_top_level: bool | None = False
88class ExternalViewResponse(BaseUIResponse):
89 """Serializer for External View Plugin responses."""
91 model_config = ConfigDict(extra="allow")
93 href: str
94 destination: BaseDestinationLiteral = "nav"
97class ReactAppResponse(BaseUIResponse):
98 """Serializer for React App Plugin responses."""
100 model_config = ConfigDict(extra="allow")
102 bundle_url: str
103 destination: Literal[BaseDestinationLiteral, "dashboard"] = "nav"
106class PluginResponse(BaseModel):
107 """Plugin serializer."""
109 name: str
110 macros: list[str]
111 flask_blueprints: list[str]
112 fastapi_apps: list[FastAPIAppResponse]
113 fastapi_root_middlewares: list[FastAPIRootMiddlewareResponse]
114 external_views: list[ExternalViewResponse] = Field(
115 description="Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here."
116 )
117 react_apps: list[ReactAppResponse]
118 appbuilder_views: list[AppBuilderViewResponse]
119 appbuilder_menu_items: list[AppBuilderMenuItemResponse] = Field(
120 deprecated="Kept for backward compatibility, use `external_views` instead.",
121 )
122 global_operator_extra_links: list[str]
123 operator_extra_links: list[str]
124 source: Annotated[str, BeforeValidator(coerce_to_string)]
125 listeners: list[str]
126 timetables: list[str]
128 @field_validator("source", mode="before")
129 @classmethod
130 def convert_source(cls, data: Any) -> Any:
131 if isinstance(data, AirflowPluginSource): 131 ↛ 133line 131 didn't jump to line 133 because the condition on line 131 was always true
132 return str(data)
133 return data
135 @model_validator(mode="before")
136 @classmethod
137 def convert_external_views(cls, data: Any) -> Any:
138 data["external_views"] = [*data["external_views"], *data["appbuilder_menu_items"]]
139 return data
142class PluginCollectionResponse(BaseModel):
143 """Plugin Collection serializer."""
145 plugins: list[PluginResponse]
146 total_entries: int
149class PluginImportErrorResponse(BaseModel):
150 """Plugin Import Error serializer for responses."""
152 source: str
153 error: str
156class PluginImportErrorCollectionResponse(BaseModel):
157 """Plugin Import Error Collection serializer."""
159 import_errors: list[PluginImportErrorResponse]
160 total_entries: int