The OpenAPI Specification (OAS) is placed under the backend/oas
directory with the following structure:
tree
.
├── openapi.yaml
├── paths
│ ├── hello.yaml
│ └── task.yaml
├── root.yaml
└── schemas
├── error.yaml
├── hello.yaml
└── task.yaml
The role and naming conventions for each file are as follows:
File Name | Role | Naming Convention |
---|---|---|
root.yaml | Root file for OAS | Fixed as root.yaml |
paths/*.yaml | Files defining API endpoints. The file granularity is by resource. | {resource_name}.yaml |
schemas/*.yaml | Files defining request and response schemas. The schema definitions are converted to Python data models using datamodel-code-generator (described later). |
{resource_name}.yaml |
openapi.yaml | A single OAS file generated based on root.yaml . This document renders openapi.yaml . |
Fixed as openapi.yaml |
root.yaml
is the root file of the OAS and should have the following structure:
#root.yaml
openapi: 3.0.1
info:
title: OQTOPUS Cloud User API
version: '1.0'
contact:
name: oqtopus-team
email: oqtopus-team[at]googlegroups.com
description: OQTOPUS Cloud User API. This API is used to interact with the OQTOPUS Cloud service. The API provides endpoints to manage devices, tasks, and results.
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
- url: http://localhost:8080
description: Local server url
paths:
/hello:
$ref: ./paths/hello.yaml#/root
/hello/{name}:
$ref: ./paths/hello.yaml#/root.name
/task:
$ref: ./paths/task.yaml#/root
/task/{taskId}:
$ref: ./paths/task.yaml#/root.taskId
paths
definitions should refer to files placed in thepaths
directory.- To improve readability, divide
paths
definitions by resource. - Define path parameters in camelCase.
- The paths directly under the resource should refer to the
root
in the files placed in thepaths
directory. - Deeper nested paths should be defined as
root.{path_name}.{path_name}
.
Note
Define as task/{taskId}
instead of task/{task_id}
.
The paths
directory should be split into files by resource. Each file should have the following structure:
root:
post:
tags:
- task
summary: post task
description: post task
operationId: postTask
security: []
requestBody:
description: "Quantum task to be submitted"
content:
application/json:
schema:
$ref: "../schemas/task.yaml#/task.PostTaskRequest"
example:
code: "OPENQASM 3; qubit[2] q; bit[2] c; h q[0]; cnot q[0], q[1]; c = measure q;"
name: "Bell State Sampling"
device: "Kawasaki"
n_nodes: 12
n_shots: 1000
skip_transpilation: false
seed_transpilation: 873
seed_simulation: 39058567
n_per_node: 2
simulation_opt: {
optimization_method: "light",
optimization_blockSize: 1,
optimization_swapLevel: 1,
}
note: "Bell State Sampling Example"
responses:
'200':
description: "Task submitted"
content:
application/json:
schema:
type: object
properties:
task_id:
$ref: "../schemas/task.yaml#/task.TaskId"
required: [
task_id
]
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '../schemas/error.yaml#/error.InternalServerError'
example:
detail: An internal system error has occurred.
root.taskId:
get:
tags:
- task
summary: get task
description: get task
operationId: getTaskByTaskId
security: []
parameters:
- in: path
name: taskId
description: "Quantum task identifier"
required: true
schema:
$ref: "../schemas/task.yaml#/task.TaskId"
responses:
'200':
description: task response
content:
application/json:
schema:
$ref: '../schemas/task.yaml#/task.GetTaskResponse'
example:
task_id: 7af020f6-2e38-4d70-8cf0-4349650ea08c
code: OPENQASM 3; qubit[2] q; bit[2] c; h q[0]; cnot q[0], q[1]; c = measure q;
device: Kawasaki
n_qubits: 12
n_nodes: 12
skip_transpilation: false
seed_transpilation: 873
seed_simulation: 39058567
n_per_node: 2
simulation_opt: {
optimization_method: "light",
optimization_block_size: 1,
optimization_swap_level: 1
}
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '../schemas/error.yaml#/error.BadRequest'
example:
detail: invalid task_id
'404':
description: Not Found
content:
application/json:
schema:
$ref: '../schemas/error.yaml#/error.NotFoundError'
example:
detail: task not found with given id
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '../schemas/error.yaml#/error.InternalServerError'
example:
detail: internal server error
- The path directly under a resource should be
root
. - Deeper nested paths should be defined as
root.{path_name}.{path_name}
. - Define path parameters in camelCase.
- Include examples for both request and response.
- Use
$ref
to reference schema files located in theschemas
directory. - Ensure the file granularity for schemas matches the resource.
The schemas
directory should be split into files by resource. Each file should have the following structure:
task.TaskId:
type: string
format: uuid
example: "7af020f6-2e38-4d70-8cf0-4349650ea08c"
task.GetTaskResponse:
description: detail of task response
type: object
properties:
task_id:
$ref: "task.yaml#/task.TaskId"
code: {type: string, example: "OPENQASM 3; qubit[2] q; bit[2] c; h q[0]; cnot q[0], q[1]; c = measure q;"}
device: {type: string, example: "Kawasaki"}
n_qubits:
type: integer
example: null
n_nodes:
description: "Parameter valid only for 'simulator' devices"
type: integer
example: 12
qubit_allocation:
description: "Parameter valid only for QPU devices"
type: object
additionalProperties:
type: integer
example:
"0": 12
"1": 16
skip_transpilation:
type: boolean
example: false
seed_transpilation:
description: "Parameter valid only if skipTranspilation is false"
type: integer
example: 873
seed_simulation:
description: "Parameter valid only for 'simulator' devices"
type: integer
example: 39058567
ro_error_mitigation:
description: "Parameter valid only for QPU devices"
type: string
enum: ["none", "pseudo_inverse", "least_square"]
example: "pseudo_inverse"
n_per_node:
description: "Parameter valid only for simulator devices"
type: integer
minimum: 1
example: 5
simulation_opt:
description: "Parameter valid only for simulator devices"
type: object
example: {
optimization_method: "light",
optimization_block_size: 1,
optimization_swap_level: 1
}
required: [
task_id, code, device, skip_transpilation
]
task.PostTaskRequest:
type: object
properties:
code: {type: string, example: "OPENQASM 3; qubit[2] q; bit[2] c; h q[0]; cnot q[0], q[1]; c = measure q;"}
name:
type: string
example: "Bell State Sampling"
device:
type: string
example: "Kawasaki"
n_qubits:
description: "Parameter exclusive with nNodes"
type: integer
n_nodes:
description: "Parameter exclusive with nQubits<br/>Parameter valid only for 'simulator' devices"
type: integer
example: 12
default: 1
n_shots:
type: integer
minimum: 1
maximum: 1e7
example: "1000"
qubit_allocation:
description: "Parameter valid only for QPU devices"
type: object
additionalProperties:
type: integer
example:
"0": 12
"1": 16
skip_transpilation:
type: boolean
default: false
example: false
seed_transpilation:
description: "Parameter valid only if skipTranspilation is false"
type: integer
example: 873
seed_simulation:
description: "Parameter valid only for 'simulator' devices"
type: integer
example: 39058567
ro_error_mitigation:
description: "Parameter valid only for QPU devices"
type: string
enum: ["none", "pseudo_inverse", "least_square"]
default: "none"
example: "pseudo_inverse"
n_per_node:
description: "Parameter valid only for simulator devices"
type: integer
minimum: 1
default: 1
example: 5
simulation_opt:
description: "Parameter valid only for simulator devices"
type: object
example: {
optimization_method: "light",
optimization_block_size: 1,
optimization_swap_level: 1
}
note:
type: string
example: "Bell State Sampling Example"
required: [
code, device, n_shots
]
- The schema file name should match the resource name.
- Schema definitions should follow the
{resource_name}.{schema_name}
format. The resource name will be generated as{resource_name}.py
bydatamodel-code-generator
. - Schema names are automatically generated as class names by
datamodel-code-generator
, so use PascalCase. - Schema field names are automatically generated as class field names, so use snake_case.
- Since
description
andexample
will be reflected in the generated code, write them appropriately.
Run make generate-all
to generate openapi.yaml
.
make generate-all
Below is a list of commands defined in backend/oas/Makefile
.
make help
Usage: make [target]
Available targets:
generate-all Generate user and provider openapi.yaml
generate-user Generate user openapi.yaml
generate-provider Generate provider openapi.yaml
help Show this help message
lint-all Lint user and provider
lint-user Lint user openapi.yaml
lint-provider Lint provider openapi.yaml
In this document, openapi.yaml
generated is rendered with Swagger UI.
Note
Swagger UI is set up with endpoints in the development environment, so you can send requests on Swagger UI by running make run-user
or make run-provider
under the backend directory.
Below is a list of commands defined in the backend/Makefile
.
make help
Usage: make [target]
Available targets:
down Stop the DB
fmt Format code
generate-all-schema Generate user and schemas
generate-user-schema Generate user schema
generate-provider-schema Generate provider schema
help Show this help message
lint Run linters
run-user Start the User API
run-provider Start the Provider API
test Run tests
up Start the DB
By running make generate-all-schema
, Python data models will be generated in the backend/oas/oqtopus_cloud/user/schemas
and backend/oas/oqtopus_cloud/provider/schemas
directories.
make generate-all-schema
The generated data models will look like the following:
# generated by datamodel-codegen:
# filename: openapi.yaml
# timestamp: 2024-04-04T03:36:51+00:00
# version: 0.25.5
from __future__ import annotations
from enum import Enum
from typing import Annotated, Any, Dict, Optional
from uuid import UUID
from pydantic import BaseModel, Field, RootModel
class GetTaskResponse(BaseModel):
"""
Detail of task response
"""
task_id: TaskId
code: Annotated[
str,
Field(
examples=[
"OPENQASM 3; qubit[2] q; bit[2] c; h q[0]; cnot q[0], q[1]; c = measure q;"
]
),
]
device: Annotated[str, Field(examples=["Kawasaki"])]
n_qubits: Annotated[Optional[int], Field(None, examples=[None])]
n_nodes: Annotated[Optional[int], Field(None, examples=[12])]
"""
Parameter valid only for 'simulator' devices
"""
qubit_allocation: Annotated[
Optional[Dict[str, int]], Field(None, examples=[{"0": 12, "1": 16}])
]
"""
Parameter valid only for QPU devices
"""
skip_transpilation: Annotated[bool, Field(examples=[False])]
seed_transpilation: Annotated[Optional[int], Field(None, examples=[873])]
"""
Parameter valid only if skipTranspilation is false
"""
seed_simulation: Annotated[Optional[int], Field(None, examples=[39058567])]
"""
Parameter valid only for 'simulator' devices
"""
ro_error_mitigation: Annotated[
Optional[RoErrorMitigation], Field("none", examples=["pseudo_inverse"])
]
"""
Parameter valid only for QPU devices
"""
n_per_node: Annotated[Optional[int], Field(None, examples=[5], ge=1)]
"""
Parameter valid only for simulator devices
"""
simulation_opt: Annotated[
Optional[Dict[str, Any]],
Field(
None,
examples=[
{
"optimization_method": "light",
"optimization_block_size": 1,
"optimization_swap_level": 1,
}
],
),
]
"""
Parameter valid only for simulator devices
"""
class PostTaskRequest(BaseModel):
code: Annotated[
str,
Field(
examples=[
"OPENQASM 3; qubit[2] q; bit[2] c; h q[0]; cnot q[0], q[1]; c = measure q;"
]
),
]
name: Annotated[Optional[str], Field(None, examples=["Bell State Sampling"])]
device: Annotated[str, Field(examples=["Kawasaki"])]
n_qubits: Optional[int] = None
"""
Parameter exclusive with nNodes
"""
n_nodes: Annotated[Optional[int], Field(1, examples=[12])]
"""
Parameter exclusive with nQubits<br/>Parameter valid only for 'simulator' devices
"""
n_shots: Annotated[int, Field(examples=["1000"], ge=1, le=10000000)]
qubit_allocation: Annotated[
Optional[Dict[str, int]], Field(None, examples=[{"0": 12, "1": 16}])
]
"""
Parameter valid only for QPU devices
"""
skip_transpilation: Annotated[Optional[bool], Field(False, examples=[False])]
seed_transpilation: Annotated[Optional[int], Field(None, examples=[873])]
"""
Parameter valid only if skipTranspilation is false
"""
seed_simulation: Annotated[Optional[int], Field(None, examples=[39058567])]
"""
Parameter valid only for 'simulator' devices
"""
ro_error_mitigation: Annotated[
Optional[RoErrorMitigation], Field("none", examples=["pseudo_inverse"])
]
"""
Parameter valid only for QPU devices
"""
n_per_node: Annotated[Optional[int], Field(1, examples=[5], ge=1)]
"""
Parameter valid only for simulator devices
"""
simulation_opt: Annotated[
Optional[Dict[str, Any]],
Field(
None,
examples=[
{
"optimization_method": "light",
"optimization_block_size": 1,
"optimization_swap_level": 1,
}
],
),
]
"""
Parameter valid only for simulator devices
"""
note: Annotated[
Optional[str], Field(None, examples=["Bell State Sampling Example"])
]
class RoErrorMitigation(Enum):
"""
Parameter valid only for QPU devices
"""
none = "none"
pseudo_inverse = "pseudo_inverse"
least_square = "least_square"
class TaskId(RootModel[UUID]):
root: Annotated[UUID, Field(examples=["7af020f6-2e38-4d70-8cf0-4349650ea08c"])]