Files
Airflow3Plugin/markdown_view_plugin/markdown_view_plugin.py

85 lines
3.3 KiB
Python

# Python backend for the Markdown View Plugin using FastAPI
import anyio # For asynchronous file operations
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from airflow.plugins_manager import AirflowPlugin
import importlib.resources as resources
# Use importlib.resources to locate static files and view.md
static_files_dir_ref = resources.files("markdown_view_plugin") / "ui" / "dist"
view_md_path_ref = resources.files("markdown_view_plugin") / "view.md"
# Create a FastAPI app
markdown_fastapi_app = FastAPI(
title="Markdown View Plugin",
description="A plugin to render Markdown content in Airflow UI.",
version="1.0.0",
)
# Mount static files for the React UI
# The path "/static/markdown_view_plugin" must match the 'base' in ui/vite.config.ts
with resources.as_file(static_files_dir_ref) as static_files_dir:
markdown_fastapi_app.mount(
"/static/markdown_view_plugin",
StaticFiles(directory=str(static_files_dir)),
name="markdown_view_plugin_static",
)
# API endpoint to get markdown content
@markdown_fastapi_app.get("/markdown_view/api/view")
async def get_markdown_content_api():
"""Asynchronously read and return the content of view.md."""
with resources.as_file(view_md_path_ref) as view_md_path:
try:
async with await anyio.open_file(view_md_path) as f:
content = await f.read()
return {"markdown": content}
except FileNotFoundError:
raise HTTPException(status_code=404, detail="view.md not found")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Endpoint to serve the main index.html of the React app
@markdown_fastapi_app.get("/markdown_view")
async def serve_markdown_ui():
"""Serve the main index.html for the plugin's UI."""
with resources.as_file(static_files_dir_ref / "index.html") as index_html_path:
if not index_html_path.exists():
raise HTTPException(
status_code=404,
detail="index.html not found. Did you build the UI? (cd ui && pnpm run build)",
)
return FileResponse(str(index_html_path))
# Define the Airflow plugin
class MarkdownViewPlugin(AirflowPlugin):
"""
Airflow Plugin for Markdown View.
This plugin provides a FastAPI backend and a React frontend
to display Markdown content within the Airflow UI.
"""
name = "markdown_view_plugin"
fastapi_apps = [
{
"app": markdown_fastapi_app,
"name": "markdown_view_app", # A unique name for this FastAPI app
"app_mount": "/markdown_view_plugin_mount",
}
]
# Example menu link (optional, can also be configured via Airflow UI settings)
menu_links = [
{
"name": "Markdown Viewer",
"href": "/markdown_view_plugin_mount/markdown_view", # This should match the app_mount + @markdown_fastapi_app.get path for the UI
"category": "Plugins",
}
]
# For Airflow to pick up the plugin, the class name must match the filename (snake_case to PascalCase)
# or be explicitly defined in an __init__.py in the plugin\'s root directory.
# Assuming filename is markdown_view_plugin.py, class MarkdownViewPlugin is correct.