# 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.