Skip to content

Commit

Permalink
Standardise api (#222)
Browse files Browse the repository at this point in the history
* Add Documentation reference

* Make API comply with guidelines and handle versioning

* Revise after Callum's comments

* Remove superfluous response params

* update api yaml file

* Correct API version typo

* Remove async on getState and fix tests

* Correct type error
  • Loading branch information
keithralphs authored May 23, 2023
1 parent 3b4ab8c commit f30aed0
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 53 deletions.
2 changes: 2 additions & 0 deletions catalog-info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ metadata:
name: blueapiControl
title: Athena BlueAPI Control
description: REST API for getting plans/devices from the worker (and running tasks)
annotations:
diamond.ac.uk/viewdocs-url: https://diamondlightsource.github.io/blueapi
spec:
type: openapi
lifecycle: production
Expand Down
86 changes: 48 additions & 38 deletions docs/user/reference/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ components:
- plans
title: PlanResponse
type: object
RunPlan:
additionalProperties: false
description: Task that will run a plan
properties:
name:
description: Name of plan to run
title: Name
type: string
params:
description: Values for parameters to plan, if any
title: Params
type: object
required:
- name
title: RunPlan
type: object
TaskResponse:
additionalProperties: false
description: Acknowledgement that a task has started, includes its ID
Expand Down Expand Up @@ -118,13 +134,25 @@ components:
type: string
info:
title: BlueAPI Control
version: 0.1.0
version: 0.0.2
openapi: 3.0.2
paths:
/device/{name}:
/devices:
get:
description: Retrieve information about all available devices.
operationId: get_devices_devices_get
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceResponse'
description: Successful Response
summary: Get Devices
/devices/{name}:
get:
description: Retrieve information about a devices by its (unique) name.
operationId: get_device_by_name_device__name__get
operationId: get_device_by_name_devices__name__get
parameters:
- in: path
name: name
Expand All @@ -146,22 +174,22 @@ paths:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Get Device By Name
/devices:
/plans:
get:
description: Retrieve information about all available devices.
operationId: get_devices_devices_get
description: Retrieve information about all available plans.
operationId: get_plans_plans_get
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceResponse'
$ref: '#/components/schemas/PlanResponse'
description: Successful Response
summary: Get Devices
/plan/{name}:
summary: Get Plans
/plans/{name}:
get:
description: Retrieve information about a plan by its (unique) name.
operationId: get_plan_by_name_plan__name__get
operationId: get_plan_by_name_plans__name__get
parameters:
- in: path
name: name
Expand All @@ -183,41 +211,23 @@ paths:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Get Plan By Name
/plans:
get:
description: Retrieve information about all available plans.
operationId: get_plans_plans_get
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PlanResponse'
description: Successful Response
summary: Get Plans
/task/{name}:
put:
description: Submit a task onto the worker queue.
operationId: submit_task_task__name__put
parameters:
- in: path
name: name
required: true
schema:
title: Name
type: string
/tasks:
post:
description: Submit a task to the worker.
operationId: submit_task_tasks_post
requestBody:
content:
application/json:
example:
detectors:
- x
name: count
params:
detectors:
- x
schema:
title: Task
type: object
$ref: '#/components/schemas/RunPlan'
required: true
responses:
'200':
'201':
content:
application/json:
schema:
Expand Down
33 changes: 23 additions & 10 deletions src/blueapi/service/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from contextlib import asynccontextmanager
from typing import Any, Mapping

from fastapi import Body, Depends, FastAPI, HTTPException
from fastapi import Body, Depends, FastAPI, HTTPException, Request, Response

from blueapi.config import ApplicationConfig
from blueapi.worker import RunPlan, WorkerState

from .handler import Handler, get_handler, setup_handler, teardown_handler
from .model import DeviceModel, DeviceResponse, PlanModel, PlanResponse, TaskResponse

REST_API_VERSION = "0.0.2"


@asynccontextmanager
async def lifespan(app: FastAPI):
Expand All @@ -23,6 +24,7 @@ async def lifespan(app: FastAPI):
on_shutdown=[teardown_handler],
title="BlueAPI Control",
lifespan=lifespan,
version=REST_API_VERSION,
)


Expand All @@ -34,7 +36,7 @@ def get_plans(handler: Handler = Depends(get_handler)):
)


@app.get("/plan/{name}", response_model=PlanModel)
@app.get("/plans/{name}", response_model=PlanModel)
def get_plan_by_name(name: str, handler: Handler = Depends(get_handler)):
"""Retrieve information about a plan by its (unique) name."""
try:
Expand All @@ -54,7 +56,7 @@ def get_devices(handler: Handler = Depends(get_handler)):
)


@app.get("/device/{name}", response_model=DeviceModel)
@app.get("/devices/{name}", response_model=DeviceModel)
def get_device_by_name(name: str, handler: Handler = Depends(get_handler)):
"""Retrieve information about a devices by its (unique) name."""
try:
Expand All @@ -63,20 +65,24 @@ def get_device_by_name(name: str, handler: Handler = Depends(get_handler)):
raise HTTPException(status_code=404, detail="Item not found")


@app.put("/task/{name}", response_model=TaskResponse)
@app.post("/tasks", response_model=TaskResponse, status_code=201)
def submit_task(
name: str,
task: Mapping[str, Any] = Body(..., example={"detectors": ["x"]}),
request: Request,
response: Response,
task: RunPlan = Body(
..., example=RunPlan(name="count", params={"detectors": ["x"]})
),
handler: Handler = Depends(get_handler),
):
"""Submit a task onto the worker queue."""
task_id = handler.worker.submit_task(RunPlan(name=name, params=task))
"""Submit a task to the worker."""
task_id: str = handler.worker.submit_task(task)
response.headers["Location"] = f"{request.url}/{task_id}"
handler.worker.begin_task(task_id)
return TaskResponse(task_id=task_id)


@app.get("/worker/state")
async def get_state(handler: Handler = Depends(get_handler)) -> WorkerState:
def get_state(handler: Handler = Depends(get_handler)) -> WorkerState:
"""Get the State of the Worker"""
return handler.worker.state

Expand All @@ -86,3 +92,10 @@ def start(config: ApplicationConfig):

app.state.config = config
uvicorn.run(app, host=config.api.host, port=config.api.port)


@app.middleware("http")
async def add_api_version_header(request: Request, call_next):
response = await call_next(request)
response.headers["X-API-Version"] = REST_API_VERSION
return response
11 changes: 6 additions & 5 deletions tests/service/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class MyModel(BaseModel):
plan = Plan(name="my-plan", model=MyModel)

handler.context.plans = {"my-plan": plan}
response = client.get("/plan/my-plan")
response = client.get("/plans/my-plan")

assert response.status_code == 200
assert response.json() == {"name": "my-plan"}
Expand Down Expand Up @@ -65,7 +65,7 @@ class MyDevice:
device = MyDevice("my-device")

handler.context.devices = {"my-device": device}
response = client.get("/device/my-device")
response = client.get("/devices/my-device")

assert response.status_code == 200
assert response.json() == {
Expand All @@ -75,13 +75,14 @@ class MyDevice:


def test_put_plan_submits_task(handler: Handler, client: TestClient) -> None:
task_json = {"detectors": ["x"]}
task_name = "count"
task_params = {"detectors": ["x"]}
task_json = {"name": task_name, "params": task_params}

client.put(f"/task/{task_name}", json=task_json)
client.post("/tasks", json=task_json)

assert handler.worker.get_pending_tasks()[0].task == RunPlan(
name=task_name, params=task_json
name=task_name, params=task_params
)


Expand Down

0 comments on commit f30aed0

Please sign in to comment.