Source code for aiida_restapi.routers.server

"""Declaration of FastAPI application."""

from __future__ import annotations

import re

from aiida import __version__ as aiida_version
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.routing import APIRoute
from starlette.routing import Route

from aiida_restapi.config import API_CONFIG
from aiida_restapi.models.server import ServerEndpoint, ServerInfo

read_router = APIRouter(prefix='/server')


[docs] @read_router.get( '/info', response_model=ServerInfo, ) async def get_server_info() -> dict[str, str]: """Get the API version information.""" api_version = API_CONFIG['VERSION'].split('.') return { 'api_major_version': api_version[0], 'api_minor_version': api_version[1], 'api_revision_version': api_version[2], 'api_prefix': API_CONFIG['PREFIX'], 'aiida_version': aiida_version, }
[docs] @read_router.get( '/endpoints', response_model=dict[str, list[ServerEndpoint]], ) async def get_server_endpoints(request: Request) -> dict[str, list[dict]]: """Get a JSON-serializable dictionary of all registered API routes.""" endpoints: list[dict] = [] for route in request.app.routes: if route.path == '/': continue group, methods, description = _get_route_parts(route) base_url = str(request.base_url).rstrip('/') endpoint = { 'path': base_url + route.path, 'group': group, 'methods': methods, 'description': description, } endpoints.append(endpoint) return {'endpoints': endpoints}
[docs] @read_router.get( '/endpoints/table', name='endpoints', response_class=HTMLResponse, ) async def get_server_endpoints_table(request: Request) -> HTMLResponse: """Get an HTML table of all registered API routes.""" routes = request.app.routes base_url = str(request.base_url).rstrip('/') rows = [] for route in routes: if route.path == '/': continue path = base_url + route.path group, methods, description = _get_route_parts(route) disable_url = ( ( isinstance(route, APIRoute) and any( param for param in route.dependant.path_params + route.dependant.query_params + route.dependant.body_params if param.required ) ) or 'POST' in (route.methods or {}) or 'auth' in path ) path_row = path if disable_url else f'<a href="{path}">{path}</a>' rows.append(f""" <tr> <td>{path_row}</td> <td>{group or '-'}</td> <td>{', '.join(methods)}</td> <td>{description or '-'}</td> </tr> """) return HTMLResponse( content=f""" <html> <head> <title>AiiDA REST API Endpoints</title> <style> body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; padding: 1em; color: #222; }} h1 {{ margin-bottom: 0.5em; }} table {{ border-collapse: collapse; width: 100%; }} th, td {{ border: 1px solid #ddd; padding: 0.5em 0.75em; text-align: left; }} th {{ background-color: #f4f4f4; }} tr:nth-child(even) {{ background-color: #fafafa; }} tr:hover {{ background-color: #f1f1f1; }} a {{ text-decoration: none; color: #0066cc; }} a:hover {{ text-decoration: underline; }} </style> </head> <body> <h1>AiiDA REST API Endpoints</h1> <table> <thead> <tr> <th>URL</th> <th>Group</th> <th>Methods</th> <th>Description</th> </tr> </thead> <tbody> {''.join(rows)} </tbody> </table> </body> </html> """ )
[docs] def _get_route_parts(route: Route) -> tuple[str | None, set[str], str]: """Return the parts of a route: path, group, methods, description. :param route: A FastAPI/Starlette Route object. :type route: Route :return: A tuple containing the group, methods, and description of the route. :rtype: tuple[str | None, set[str], str] """ prefix = re.escape(API_CONFIG['PREFIX']) match = re.match(rf'^{prefix}/([^/]+)/?.*', route.path) group = match.group(1) if match else None methods = (route.methods or set()) - {'HEAD', 'OPTIONS'} description = (route.endpoint.__doc__ or '').split('\n')[0].strip() return group, methods, description