diff --git a/app/routers/account/account.py b/app/routers/account/account.py index b2fc4101..e7b0af16 100644 --- a/app/routers/account/account.py +++ b/app/routers/account/account.py @@ -5,6 +5,7 @@ from ...types.scalars import StrictDateTime from .. import iri_router from ..error_handlers import DEFAULT_RESPONSES +from ..iri_meta import iri_meta_dict from . import facility_adapter, models router = iri_router.IriRouter( @@ -21,6 +22,7 @@ responses=DEFAULT_RESPONSES, operation_id="getCapabilities", response_model_exclude_none=True, + openapi_extra=iri_meta_dict("production", "required") ) async def get_capabilities( request: Request, @@ -39,6 +41,7 @@ async def get_capabilities( description="Get a single capability at this facility.", responses=DEFAULT_RESPONSES, operation_id="getCapability", + openapi_extra=iri_meta_dict("production", "required") ) async def get_capability( capability_id: str, @@ -60,6 +63,7 @@ async def get_capability( description="Get a list of projects for the currently authenticated user at this facility.", responses=DEFAULT_RESPONSES, operation_id="getProjects", + openapi_extra=iri_meta_dict("production", "required") ) async def get_projects( request: Request, @@ -78,6 +82,7 @@ async def get_projects( description="Get a single project at this facility.", responses=DEFAULT_RESPONSES, operation_id="getProject", + openapi_extra=iri_meta_dict("production", "required") ) async def get_project( project_id: str, @@ -101,6 +106,7 @@ async def get_project( description="Get a list of allocations for the currently authenticated user's projects at this facility.", responses=DEFAULT_RESPONSES, operation_id="getProjectAllocationsByProject", + openapi_extra=iri_meta_dict("production", "required") ) async def get_project_allocations( project_id: str, @@ -124,6 +130,7 @@ async def get_project_allocations( description="Get a single project allocation at this facility for this user.", responses=DEFAULT_RESPONSES, operation_id="getProjectAllocationByProject", + openapi_extra=iri_meta_dict("production", "required") ) async def get_project_allocation( project_id: str, @@ -152,6 +159,7 @@ async def get_project_allocation( description="Get a list of user allocations for the currently authenticated user's projects at this facility.", responses=DEFAULT_RESPONSES, operation_id="getUserAllocationsByProjectAllocation", + openapi_extra=iri_meta_dict("production", "required") ) async def get_user_allocations( project_id: str, @@ -180,6 +188,7 @@ async def get_user_allocations( description="Get a user allocation for the currently authenticated user's projects at this facility.", responses=DEFAULT_RESPONSES, operation_id="getUserAllocationByProjectAllocation", + openapi_extra=iri_meta_dict("production", "required") ) async def get_user_allocation( project_id: str, diff --git a/app/routers/compute/compute.py b/app/routers/compute/compute.py index 6a63c11f..a186c92d 100644 --- a/app/routers/compute/compute.py +++ b/app/routers/compute/compute.py @@ -6,6 +6,7 @@ from ...types.scalars import StrictHTTPBool from .. import iri_router from ..error_handlers import DEFAULT_RESPONSES +from ..iri_meta import iri_meta_dict from ..status.status import router as status_router from . import facility_adapter, models @@ -23,6 +24,7 @@ response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="launchJob", + openapi_extra=iri_meta_dict("beta", "required") ) async def submit_job( resource_id: str, @@ -94,6 +96,7 @@ async def submit_job( response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="updateJob", + openapi_extra=iri_meta_dict("beta", "required") ) async def update_job( resource_id: str, @@ -129,6 +132,7 @@ async def update_job( response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="getJob", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_job_status( resource_id: str, @@ -159,6 +163,7 @@ async def get_job_status( response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="getJobs", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_job_statuses( resource_id: str, @@ -192,6 +197,7 @@ async def get_job_statuses( response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="cancelJob", + openapi_extra=iri_meta_dict("beta", "required") ) async def cancel_job( resource_id: str, diff --git a/app/routers/facility/facility.py b/app/routers/facility/facility.py index 19f6b282..f86cd9df 100644 --- a/app/routers/facility/facility.py +++ b/app/routers/facility/facility.py @@ -4,13 +4,22 @@ from ...types.scalars import StrictDateTime from .. import iri_router from ..error_handlers import DEFAULT_RESPONSES +from ..iri_meta import iri_meta_dict from . import facility_adapter, models router = iri_router.IriRouter(facility_adapter.FacilityAdapter, prefix="/facility", tags=["facility"]) -@router.get("", responses=DEFAULT_RESPONSES, operation_id="getFacility", response_model_exclude_none=True,) -@router.get("/", responses=DEFAULT_RESPONSES, operation_id="getFacilityWithSlash", response_model_exclude_none=True, include_in_schema=False,) +@router.get("", + responses=DEFAULT_RESPONSES, + operation_id="getFacility", + response_model_exclude_none=True, + openapi_extra=iri_meta_dict("production", "required")) +@router.get("/", + responses=DEFAULT_RESPONSES, + operation_id="getFacilityWithSlash", + response_model_exclude_none=True, + include_in_schema=False) async def get_facility( request: Request, modified_since: StrictDateTime = Query(default=None), @@ -23,7 +32,7 @@ async def get_facility( return facility -@router.get("/sites", responses=DEFAULT_RESPONSES, operation_id="getSites", response_model_exclude_none=True,) +@router.get("/sites", responses=DEFAULT_RESPONSES, operation_id="getSites", response_model_exclude_none=True, openapi_extra=iri_meta_dict("production", "required")) async def list_sites( request: Request, modified_since: StrictDateTime = Query(default=None), @@ -40,7 +49,7 @@ async def list_sites( return sites -@router.get("/sites/{site_id}", responses=DEFAULT_RESPONSES, operation_id="getSite", response_model_exclude_none=True,) +@router.get("/sites/{site_id}", responses=DEFAULT_RESPONSES, operation_id="getSite", response_model_exclude_none=True, openapi_extra=iri_meta_dict("production", "required")) async def get_site( request: Request, site_id: str, diff --git a/app/routers/filesystem/filesystem.py b/app/routers/filesystem/filesystem.py index b53521e8..0ba16958 100644 --- a/app/routers/filesystem/filesystem.py +++ b/app/routers/filesystem/filesystem.py @@ -9,6 +9,7 @@ from fastapi import Depends, HTTPException, status, Query, Request, File, UploadFile from .. import iri_router from ..error_handlers import DEFAULT_RESPONSES +from ..iri_meta import iri_meta_dict from ..status.status import router as status_router, models as status_models from ..account.account import models as account_models from ..task import facility_adapter as task_facility_adapter, models as task_models @@ -47,6 +48,7 @@ async def _user_resource( response_description="File permissions changed successfully", responses=DEFAULT_RESPONSES, operation_id="chmod", + openapi_extra=iri_meta_dict("beta", "required") ) async def put_chmod( resource_id: str, @@ -76,6 +78,7 @@ async def put_chmod( response_description="File ownership changed successfully", responses=DEFAULT_RESPONSES, operation_id="chown", + openapi_extra=iri_meta_dict("beta", "required") ) async def put_chown( resource_id: str, @@ -105,6 +108,7 @@ async def put_chown( response_description="Type returned successfully", responses=DEFAULT_RESPONSES, operation_id="file", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_file( resource_id: str, @@ -134,6 +138,7 @@ async def get_file( response_description="Stat returned successfully", responses=DEFAULT_RESPONSES, operation_id="stat", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_stat( resource_id: str, @@ -165,6 +170,7 @@ async def get_stat( response_description="Directory created successfully", responses=DEFAULT_RESPONSES, operation_id="mkdir", + openapi_extra=iri_meta_dict("beta", "required") ) async def post_mkdir( resource_id: str, @@ -194,6 +200,7 @@ async def post_mkdir( response_description="Symlink created successfully", responses=DEFAULT_RESPONSES, operation_id="symlink", + openapi_extra=iri_meta_dict("beta", "required") ) async def post_symlink( resource_id: str, @@ -224,6 +231,7 @@ async def post_symlink( include_in_schema=router.task_adapter is not None, responses=DEFAULT_RESPONSES, operation_id="ls", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_ls_async( resource_id: str, @@ -261,6 +269,7 @@ async def get_ls_async( response_description="Head operation finished successfully", responses=DEFAULT_RESPONSES, operation_id="head", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_head( resource_id: str, @@ -321,6 +330,7 @@ async def get_head( response_description="View operation finished successfully", responses=DEFAULT_RESPONSES, operation_id="view", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_view( resource_id: str, @@ -355,6 +365,7 @@ async def get_view( response_description="`tail` operation finished successfully", responses=DEFAULT_RESPONSES, operation_id="tail", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_tail( resource_id: str, @@ -408,6 +419,7 @@ async def get_tail( response_description="Checksum returned successfully", responses=DEFAULT_RESPONSES, operation_id="checksum", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_checksum( resource_id: str, @@ -435,6 +447,7 @@ async def get_checksum( response_description="File or directory deleted successfully", responses=DEFAULT_RESPONSES, operation_id="rm", + openapi_extra=iri_meta_dict("beta", "required") ) async def delete_rm( resource_id: str, @@ -464,6 +477,7 @@ async def delete_rm( response_description="File and/or directories compressed successfully", responses=DEFAULT_RESPONSES, operation_id="compress", + openapi_extra=iri_meta_dict("beta", "required") ) async def post_compress( resource_id: str, @@ -493,6 +507,7 @@ async def post_compress( response_description="File extracted successfully", responses=DEFAULT_RESPONSES, operation_id="extract", + openapi_extra=iri_meta_dict("beta", "required") ) async def post_extract( resource_id: str, @@ -522,6 +537,7 @@ async def post_extract( response_description="Move file or directory operation created successfully", responses=DEFAULT_RESPONSES, operation_id="mv", + openapi_extra=iri_meta_dict("beta", "required") ) async def move_mv( resource_id: str, @@ -551,6 +567,7 @@ async def move_mv( response_description="Copy file or directory operation created successfully", responses=DEFAULT_RESPONSES, operation_id="cp", + openapi_extra=iri_meta_dict("beta", "required") ) async def post_cp( resource_id: str, @@ -580,6 +597,7 @@ async def post_cp( response_description="File downloaded successfully", responses=DEFAULT_RESPONSES, operation_id="download", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_download( resource_id: str, @@ -609,6 +627,7 @@ async def get_download( response_description="File uploaded successfully", responses=DEFAULT_RESPONSES, operation_id="upload", + openapi_extra=iri_meta_dict("beta", "required") ) async def post_upload( resource_id: str, diff --git a/app/routers/iri_meta.py b/app/routers/iri_meta.py new file mode 100644 index 00000000..bc97b9f3 --- /dev/null +++ b/app/routers/iri_meta.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +""" +Utility for generating the IRI OpenAPI extension metadata. +It generates: +{ + "x-iri": { + "maturity": "production", + "implementation": { + "level": "required", + "required_if_capability": "dpu" + } + } +} +""" + + +def iri_meta_dict( + maturity: str | None = None, + implementation_level: str | None = None, + required_if: str | None = None, +) -> dict: + """Generate the IRI OpenAPI extension metadata.""" + + out_obj = {} + + if maturity is not None: + out_obj["maturity"] = maturity + + if implementation_level is not None: + out_obj.setdefault("implementation", {})["level"] = implementation_level + + if required_if is not None: + out_obj.setdefault("implementation", {})["required_if_capability"] = required_if + + if not out_obj: + return {} + + return {"x-iri": out_obj} diff --git a/app/routers/status/status.py b/app/routers/status/status.py index 4b8759b1..ce5958e4 100644 --- a/app/routers/status/status.py +++ b/app/routers/status/status.py @@ -6,6 +6,7 @@ from ...types.scalars import AllocationUnit, StrictDateTime from .. import iri_router from ..error_handlers import DEFAULT_RESPONSES +from ..iri_meta import iri_meta_dict from . import facility_adapter, models router = iri_router.IriRouter( @@ -22,6 +23,7 @@ responses=DEFAULT_RESPONSES, operation_id="getResources", response_model_exclude_none=True, + openapi_extra=iri_meta_dict("production", "required") ) async def get_resources( request: Request, @@ -47,6 +49,7 @@ async def get_resources( description="Get a specific resource for a given id", responses=DEFAULT_RESPONSES, operation_id="getResource", + openapi_extra=iri_meta_dict("production", "required") ) async def get_resource( request: Request, @@ -64,6 +67,7 @@ async def get_resource( description="Get a list of all incidents. Each incident will be returned without its events. You can optionally filter the returned list by specifying attributes.", responses=DEFAULT_RESPONSES, operation_id="getIncidents", + openapi_extra=iri_meta_dict("production", "required") ) async def get_incidents( request: Request, @@ -123,6 +127,7 @@ async def get_incidents( description="Get a specific incident for a given id. The incident's events will also be included. You can optionally filter the returned list by specifying attributes.", responses=DEFAULT_RESPONSES, operation_id="getIncident", + openapi_extra=iri_meta_dict("production", "required") ) async def get_incident(request: Request, incident_id: str) -> models.Incident: item = await router.adapter.get_incident(incident_id) @@ -137,6 +142,7 @@ async def get_incident(request: Request, incident_id: str) -> models.Incident: description="Get a list of all events. You can optionally filter the returned list by specifying attribtes.", responses=DEFAULT_RESPONSES, operation_id="getEventsByIncident", + openapi_extra=iri_meta_dict("production", "required") ) async def get_events( request: Request, @@ -167,6 +173,7 @@ async def get_events( description="Get a specific event for a given id", responses=DEFAULT_RESPONSES, operation_id="getEventByIncident", + openapi_extra=iri_meta_dict("production", "required") ) async def get_event(request: Request, event_id: str) -> models.Event: item = await router.adapter.get_event(event_id) diff --git a/app/routers/task/task.py b/app/routers/task/task.py index 74cfa6d4..14bee3fa 100644 --- a/app/routers/task/task.py +++ b/app/routers/task/task.py @@ -1,6 +1,7 @@ from fastapi import Request, HTTPException, Depends from .. import iri_router from ..error_handlers import DEFAULT_RESPONSES +from ..iri_meta import iri_meta_dict from . import models, facility_adapter router = iri_router.IriRouter( @@ -16,6 +17,7 @@ response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="getTask", + openapi_extra=iri_meta_dict("beta", "required") ) async def get_task( request: Request, @@ -31,7 +33,11 @@ async def get_task( return task -@router.get("", dependencies=[Depends(router.current_user)], response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, operation_id="getTasks") +@router.get("", + dependencies=[Depends(router.current_user)], + response_model_exclude_unset=True, responses=DEFAULT_RESPONSES, + operation_id="getTasks", + openapi_extra=iri_meta_dict("beta", "required")) @router.get("/", responses=DEFAULT_RESPONSES, operation_id="getTasksWithSlash", include_in_schema=False) async def get_tasks( @@ -48,6 +54,7 @@ async def get_tasks( dependencies=[Depends(router.current_user)], responses=DEFAULT_RESPONSES, operation_id="deleteTask", + openapi_extra=iri_meta_dict("beta", "required") ) async def delete_task( request: Request,