diff --git a/docs/user/index.rst b/docs/user/index.rst index a36bc9f42..6857c1e77 100644 --- a/docs/user/index.rst +++ b/docs/user/index.rst @@ -51,8 +51,8 @@ side-bar. :maxdepth: 1 reference/api - reference/asyncapi - reference/openapi + reference/messaging-spec + reference/rest-spec ../genindex +++ diff --git a/docs/user/reference/asyncapi.rst b/docs/user/reference/asyncapi.rst deleted file mode 100644 index a5d6b1d6a..000000000 --- a/docs/user/reference/asyncapi.rst +++ /dev/null @@ -1,5 +0,0 @@ -AsyncAPI Specification -====================== - -.. literalinclude:: ./asyncapi.yaml - :language: yaml diff --git a/docs/user/reference/messaging-spec.rst b/docs/user/reference/messaging-spec.rst new file mode 100644 index 000000000..ef3b13687 --- /dev/null +++ b/docs/user/reference/messaging-spec.rst @@ -0,0 +1,10 @@ +Messaging Specification +======================= + +The Blueapi service publishes Bluesky documents and other events to the message +bus, allowing subscribers to keep track of the status of plans, as well as +other status changes. This page documents the channels to which clients can +subscribe to recieve these messages and their structure. + +.. literalinclude:: ./asyncapi.yaml + :language: yaml diff --git a/docs/user/reference/openapi.json b/docs/user/reference/openapi.json deleted file mode 100644 index e3059182b..000000000 --- a/docs/user/reference/openapi.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "openapi": "3.0.2", - "info": { - "title": "FastAPI", - "version": "0.1.0" - }, - "paths": { - "/plans": { - "get": { - "summary": "Get Plans", - "description": "Retrieve information about all available plans.", - "operationId": "get_plans_plans_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PlanResponse" - } - } - } - } - } - } - }, - "/plan/{name}": { - "get": { - "summary": "Get Plan By Name", - "description": "Retrieve information about a plan by its (unique) name.", - "operationId": "get_plan_by_name_plan__name__get", - "parameters": [ - { - "required": true, - "schema": { - "title": "Name", - "type": "string" - }, - "name": "name", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PlanModel" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/devices": { - "get": { - "summary": "Get Devices", - "description": "Retrieve information about all available devices.", - "operationId": "get_devices_devices_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceResponse" - } - } - } - } - } - } - }, - "/device/{name}": { - "get": { - "summary": "Get Device By Name", - "description": "Retrieve information about a devices by its (unique) name.", - "operationId": "get_device_by_name_device__name__get", - "parameters": [ - { - "required": true, - "schema": { - "title": "Name", - "type": "string" - }, - "name": "name", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceModel" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/task/{name}": { - "put": { - "summary": "Submit Task", - "description": "Submit a task onto the worker queue.", - "operationId": "submit_task_task__name__put", - "parameters": [ - { - "required": true, - "schema": { - "title": "Name", - "type": "string" - }, - "name": "name", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Task", - "type": "object" - }, - "example": { - "detectors": [ - "x" - ] - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TaskResponse" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "DeviceModel": { - "title": "DeviceModel", - "required": [ - "name", - "protocols" - ], - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string", - "description": "Name of the device" - }, - "protocols": { - "title": "Protocols", - "type": "array", - "items": { - "type": "string" - }, - "description": "Protocols that a device conforms to, indicating its capabilities" - } - }, - "additionalProperties": false, - "description": "Representation of a device" - }, - "DeviceResponse": { - "title": "DeviceResponse", - "required": [ - "devices" - ], - "type": "object", - "properties": { - "devices": { - "title": "Devices", - "type": "array", - "items": { - "$ref": "#/components/schemas/DeviceModel" - }, - "description": "Devices available to use in plans" - } - }, - "additionalProperties": false, - "description": "Response to a query for devices" - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": { - "$ref": "#/components/schemas/ValidationError" - } - } - } - }, - "PlanModel": { - "title": "PlanModel", - "required": [ - "name" - ], - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string", - "description": "Name of the plan" - } - }, - "additionalProperties": false, - "description": "Representation of a plan" - }, - "PlanResponse": { - "title": "PlanResponse", - "required": [ - "plans" - ], - "type": "object", - "properties": { - "plans": { - "title": "Plans", - "type": "array", - "items": { - "$ref": "#/components/schemas/PlanModel" - }, - "description": "Plans available to use by a worker" - } - }, - "additionalProperties": false, - "description": "Response to a query for plans" - }, - "TaskResponse": { - "title": "TaskResponse", - "required": [ - "taskName" - ], - "type": "object", - "properties": { - "taskName": { - "title": "Taskname", - "type": "string", - "description": "Unique identifier for the task" - } - }, - "additionalProperties": false, - "description": "Acknowledgement that a task has started, includes its ID" - }, - "ValidationError": { - "title": "ValidationError", - "required": [ - "loc", - "msg", - "type" - ], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - } - }, - "msg": { - "title": "Message", - "type": "string" - }, - "type": { - "title": "Error Type", - "type": "string" - } - } - } - } - } -} \ No newline at end of file diff --git a/docs/user/reference/openapi.rst b/docs/user/reference/openapi.rst deleted file mode 100644 index b059ed2c5..000000000 --- a/docs/user/reference/openapi.rst +++ /dev/null @@ -1,6 +0,0 @@ -REST API -======== - -The endpoints of the REST service are documented below. - -.. openapi:: ./openapi.json diff --git a/docs/user/reference/openapi.yaml b/docs/user/reference/openapi.yaml new file mode 100644 index 000000000..eff3760f5 --- /dev/null +++ b/docs/user/reference/openapi.yaml @@ -0,0 +1,217 @@ +components: + schemas: + DeviceModel: + additionalProperties: false + description: Representation of a device + properties: + name: + description: Name of the device + title: Name + type: string + protocols: + description: Protocols that a device conforms to, indicating its capabilities + items: + type: string + title: Protocols + type: array + required: + - name + - protocols + title: DeviceModel + type: object + DeviceResponse: + additionalProperties: false + description: Response to a query for devices + properties: + devices: + description: Devices available to use in plans + items: + $ref: '#/components/schemas/DeviceModel' + title: Devices + type: array + required: + - devices + title: DeviceResponse + type: object + HTTPValidationError: + properties: + detail: + items: + $ref: '#/components/schemas/ValidationError' + title: Detail + type: array + title: HTTPValidationError + type: object + PlanModel: + additionalProperties: false + description: Representation of a plan + properties: + name: + description: Name of the plan + title: Name + type: string + required: + - name + title: PlanModel + type: object + PlanResponse: + additionalProperties: false + description: Response to a query for plans + properties: + plans: + description: Plans available to use by a worker + items: + $ref: '#/components/schemas/PlanModel' + title: Plans + type: array + required: + - plans + title: PlanResponse + type: object + TaskResponse: + additionalProperties: false + description: Acknowledgement that a task has started, includes its ID + properties: + taskName: + description: Unique identifier for the task + title: Taskname + type: string + required: + - taskName + title: TaskResponse + type: object + ValidationError: + properties: + loc: + items: + anyOf: + - type: string + - type: integer + title: Location + type: array + msg: + title: Message + type: string + type: + title: Error Type + type: string + required: + - loc + - msg + - type + title: ValidationError + type: object +info: + title: BlueAPI Control + version: 0.1.0 +openapi: 3.0.2 +paths: + /device/{name}: + get: + description: Retrieve information about a devices by its (unique) name. + operationId: get_device_by_name_device__name__get + parameters: + - in: path + name: name + required: true + schema: + title: Name + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeviceModel' + description: Successful Response + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + description: Validation Error + summary: Get Device By 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 + /plan/{name}: + get: + description: Retrieve information about a plan by its (unique) name. + operationId: get_plan_by_name_plan__name__get + parameters: + - in: path + name: name + required: true + schema: + title: Name + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PlanModel' + description: Successful Response + '422': + content: + application/json: + schema: + $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 + requestBody: + content: + application/json: + example: + detectors: + - x + schema: + title: Task + type: object + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TaskResponse' + description: Successful Response + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + description: Validation Error + summary: Submit Task diff --git a/docs/user/reference/rest-spec.rst b/docs/user/reference/rest-spec.rst new file mode 100644 index 000000000..ded888033 --- /dev/null +++ b/docs/user/reference/rest-spec.rst @@ -0,0 +1,8 @@ +REST Specification +================== + +Blueapi runs a FastAPI server through which the blueapi worker can be +interacted with. This page documents all possible endpoints of this +server. + +.. openapi:: ./openapi.yaml diff --git a/src/blueapi/service/main.py b/src/blueapi/service/main.py index 60b0a6114..1cc8b85bc 100644 --- a/src/blueapi/service/main.py +++ b/src/blueapi/service/main.py @@ -8,7 +8,7 @@ from .handler import Handler, get_handler, setup_handler, teardown_handler from .model import DeviceModel, DeviceResponse, PlanModel, PlanResponse, TaskResponse -app = FastAPI(docs_url="/docs", on_shutdown=[teardown_handler]) +app = FastAPI(docs_url="/docs", on_shutdown=[teardown_handler], title="BlueAPI Control") @app.get("/plans", response_model=PlanResponse) diff --git a/src/blueapi/service/openapi.py b/src/blueapi/service/openapi.py index e279bc19d..b0f7cebb5 100644 --- a/src/blueapi/service/openapi.py +++ b/src/blueapi/service/openapi.py @@ -1,8 +1,8 @@ """Generate openapi.json.""" -import json from pathlib import Path +import yaml from fastapi.openapi.utils import get_openapi from blueapi.service.main import app @@ -10,7 +10,7 @@ def write_openapi_file(location: Path): with open(location, "w") as f: - json.dump( + yaml.dump( get_openapi( title=app.title, version=app.version, @@ -19,7 +19,6 @@ def write_openapi_file(location: Path): routes=app.routes, ), f, - indent=4, ) @@ -28,5 +27,5 @@ def init(location: Path): write_openapi_file(location) -location = Path(__file__).parents[3] / "docs" / "user" / "reference" / "openapi.json" +location = Path(__file__).parents[3] / "docs" / "user" / "reference" / "openapi.yaml" init(location) diff --git a/tests/service/test_openapi.py b/tests/service/test_openapi.py index ae247be02..3d7897d21 100644 --- a/tests/service/test_openapi.py +++ b/tests/service/test_openapi.py @@ -1,11 +1,10 @@ # this should test if we change app, what openapi is generated. -import json - # i.e.checking that the openapi generation actually works. from pathlib import Path import mock +import yaml from mock import Mock, PropertyMock @@ -28,12 +27,12 @@ def test_init(mock_app: Mock): from blueapi.service import openapi with mock.patch.object(openapi, "__name__", "__main__"): - location = Path(__file__).parent / "test_file.json" + location = Path(__file__).parent / "test_file.yaml" openapi.init(location) print("ah") with open(location, "r") as f: - result = json.load(f) + result = yaml.load(f, yaml.Loader) assert result == { "openapi": openapi_version(),