-
Notifications
You must be signed in to change notification settings - Fork 16.9k
Add TypedDict type hints for AirflowPlugin dict-typed attributes #64606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,7 +28,7 @@ | |||||||||||||||||
| import sys | ||||||||||||||||||
| import types | ||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||
| from typing import TYPE_CHECKING, Any | ||||||||||||||||||
| from typing import TYPE_CHECKING, Any, Literal, TypedDict | ||||||||||||||||||
|
|
||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||
| if sys.version_info >= (3, 12): | ||||||||||||||||||
|
|
@@ -85,6 +85,162 @@ class AirflowPluginException(Exception): | |||||||||||||||||
| """Exception when loading plugin.""" | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||
| # TypedDicts for AirflowPlugin dict-typed attributes | ||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class FastAPIAppDict(TypedDict): | ||||||||||||||||||
| """ | ||||||||||||||||||
| Dict structure for entries in ``AirflowPlugin.fastapi_apps``. | ||||||||||||||||||
|
|
||||||||||||||||||
| Example:: | ||||||||||||||||||
|
|
||||||||||||||||||
| fastapi_apps = [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "app": my_fastapi_app, | ||||||||||||||||||
| "url_prefix": "/my-plugin", | ||||||||||||||||||
| "name": "My Plugin", | ||||||||||||||||||
| } | ||||||||||||||||||
| ] | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| app: Any # FastAPI application instance | ||||||||||||||||||
| url_prefix: str | ||||||||||||||||||
| name: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class _FastAPIRootMiddlewareDictRequired(TypedDict): | ||||||||||||||||||
| middleware: Any # Starlette/ASGI middleware class | ||||||||||||||||||
| name: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class FastAPIRootMiddlewareDict(_FastAPIRootMiddlewareDictRequired, total=False): | ||||||||||||||||||
| """ | ||||||||||||||||||
| Dict structure for entries in ``AirflowPlugin.fastapi_root_middlewares``. | ||||||||||||||||||
|
|
||||||||||||||||||
| Example:: | ||||||||||||||||||
|
|
||||||||||||||||||
| fastapi_root_middlewares = [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "middleware": MyMiddleware, | ||||||||||||||||||
| "name": "My Middleware", | ||||||||||||||||||
| "args": [], | ||||||||||||||||||
| "kwargs": {}, | ||||||||||||||||||
| } | ||||||||||||||||||
| ] | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| args: list[Any] | ||||||||||||||||||
| kwargs: dict[str, Any] | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class _ExternalViewDictRequired(TypedDict): | ||||||||||||||||||
| name: str | ||||||||||||||||||
| href: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class ExternalViewDict(_ExternalViewDictRequired, total=False): | ||||||||||||||||||
| """ | ||||||||||||||||||
| Dict structure for entries in ``AirflowPlugin.external_views``. | ||||||||||||||||||
|
|
||||||||||||||||||
| Example:: | ||||||||||||||||||
|
|
||||||||||||||||||
| external_views = [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "name": "My Dashboard", | ||||||||||||||||||
| "href": "https://dashboard.example.com", | ||||||||||||||||||
| "destination": "nav", | ||||||||||||||||||
| "url_route": "my-dashboard", | ||||||||||||||||||
| "icon": "https://example.com/icon.svg", | ||||||||||||||||||
| "category": "Tools", | ||||||||||||||||||
| } | ||||||||||||||||||
| ] | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| icon: str | ||||||||||||||||||
| icon_dark_mode: str | ||||||||||||||||||
| url_route: str | ||||||||||||||||||
| destination: Literal["nav", "dag", "dag_run", "task", "task_instance", "base"] | ||||||||||||||||||
| category: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class _ReactAppDictRequired(TypedDict): | ||||||||||||||||||
| name: str | ||||||||||||||||||
| bundle_url: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class ReactAppDict(_ReactAppDictRequired, total=False): | ||||||||||||||||||
| """ | ||||||||||||||||||
| Dict structure for entries in ``AirflowPlugin.react_apps``. | ||||||||||||||||||
|
|
||||||||||||||||||
| Example:: | ||||||||||||||||||
|
|
||||||||||||||||||
| react_apps = [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "name": "My React App", | ||||||||||||||||||
| "bundle_url": "https://assets.example.com/app.umd.js", | ||||||||||||||||||
| "destination": "nav", | ||||||||||||||||||
| "url_route": "my-react-app", | ||||||||||||||||||
| "icon": "https://example.com/icon.svg", | ||||||||||||||||||
| "category": "Tools", | ||||||||||||||||||
| } | ||||||||||||||||||
| ] | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| icon: str | ||||||||||||||||||
| icon_dark_mode: str | ||||||||||||||||||
| url_route: str | ||||||||||||||||||
| destination: Literal["nav", "dag", "dag_run", "task", "task_instance", "base", "dashboard"] | ||||||||||||||||||
| category: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class AppBuilderViewDict(TypedDict, total=False): | ||||||||||||||||||
| """ | ||||||||||||||||||
| Dict structure for entries in ``AirflowPlugin.appbuilder_views``. | ||||||||||||||||||
|
|
||||||||||||||||||
| Example:: | ||||||||||||||||||
|
|
||||||||||||||||||
| appbuilder_views = [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "name": "My View", | ||||||||||||||||||
| "category": "My Plugin", | ||||||||||||||||||
| "view": my_view_instance, | ||||||||||||||||||
| "label": "My View Label", | ||||||||||||||||||
| } | ||||||||||||||||||
| ] | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| name: str | ||||||||||||||||||
| category: str | ||||||||||||||||||
| view: Any # Flask-AppBuilder BaseView instance | ||||||||||||||||||
| label: str | ||||||||||||||||||
|
Comment on lines
+198
to
+217
|
||||||||||||||||||
| label: str | |
| label: str | |
| # Additional kwargs forwarded to ``appbuilder.add_view``. | |
| href: str | |
| icon: str | |
| category_icon: str | |
| category_label: str | |
| menu_cond: Any |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AppBuilderMenuItemDict only models name, href, category, and label, but these dicts are passed directly into appbuilder.add_link(**menu_link), which supports additional kwargs (e.g. icon, category_icon, category_label, cond, etc.). With the current TypedDict, those valid keys will be rejected by type checkers.
Suggested fix: add the additional supported FAB kwargs to this TypedDict so it matches the runtime interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new TypedDict re-exports are imported from
airflow._shared.plugins_manager, but that package’s__init__.pycurrently does not export these names (it only re-exports items from.plugins_managerlikeAirflowPlugin,make_module, etc.). As-is, this import will raiseImportErrorat runtime whenairflow.plugins_manageris imported.Suggested fix: re-export the new
*DictTypedDicts fromairflow/_shared/plugins_manager/__init__.py(and the shared-library equivalent), or change this import to pull directly fromairflow._shared.plugins_manager.plugins_managerwhere the classes are defined.