Skip to content

Commit

Permalink
docs(robot-server): Audit HTTP API documentation (#14294)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxColoring authored Mar 8, 2024
1 parent 3821b0e commit f50e261
Show file tree
Hide file tree
Showing 24 changed files with 304 additions and 94 deletions.
3 changes: 3 additions & 0 deletions robot-server/robot_server/app_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
),
version=__version__,
exception_handlers=exception_handlers,
# Disable documentation hosting via Swagger UI, normally at /docs.
# We instead focus on the docs hosted by ReDoc, at /redoc.
docs_url=None,
)

# cors
Expand Down
10 changes: 8 additions & 2 deletions robot-server/robot_server/deck_configuration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,21 @@ class DeckConfigurationRequest(pydantic.BaseModel):
"""A request to set the robot's deck configuration."""

cutoutFixtures: List[CutoutFixture] = pydantic.Field(
description="A full list of all the cutout fixtures that are mounted onto the deck."
description=(
"A full list of all the cutout fixtures that are mounted onto the deck."
" The order is arbitrary."
)
)


class DeckConfigurationResponse(pydantic.BaseModel):
"""A response for the robot's current deck configuration."""

cutoutFixtures: List[CutoutFixture] = pydantic.Field(
description="A full list of all the cutout fixtures that are mounted onto the deck."
description=(
"A full list of all the cutout fixtures that are mounted onto the deck."
" The order is arbitrary."
)
)
lastModifiedAt: Optional[datetime] = pydantic.Field(
description=(
Expand Down
14 changes: 10 additions & 4 deletions robot-server/robot_server/deck_configuration/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@PydanticResponse.wrap_route(
router.put,
path="/deck_configuration",
summary="Set the deck configuration",
summary="Set the Flex deck configuration",
description=(
"Inform the robot how its deck is physically set up."
"\n\n"
Expand All @@ -38,15 +38,18 @@
" configuration, such as loading a labware into a staging area slot that this deck"
" configuration doesn't provide, the run command will fail with an error."
"\n\n"
"After you set the deck configuration, it will persist, even across reboots,"
" until you set it to something else."
"\n\n"
"**Warning:**"
" Currently, you can call this endpoint at any time, even while there is an active run."
" However, the robot can't adapt to deck configuration changes in the middle of a run."
" The robot will effectively take a snapshot of the deck configuration when the run is"
" first played. In the future, this endpoint may error if you try to call it in the middle"
" of an active run, so don't rely on being able to do that."
"\n\n"
"After you set the deck configuration, it will persist, even across reboots,"
" until you set it to something else."
"**Warning:** Only use this on Flex robots, never OT-2 robots. The behavior on"
" OT-2 robots is currently undefined and it may interfere with protocol execution."
),
responses={
fastapi.status.HTTP_200_OK: {
Expand Down Expand Up @@ -86,10 +89,13 @@ async def put_deck_configuration( # noqa: D103
@PydanticResponse.wrap_route(
router.get,
path="/deck_configuration",
summary="Get the deck configuration",
summary="Get the Flex deck configuration",
description=(
"Get the robot's current deck configuration."
" See `PUT /deck_configuration` for background information."
"\n\n"
"**Warning:** The behavior of this endpoint is currently only defined for Flex"
" robots, not OT-2 robots."
),
responses={
fastapi.status.HTTP_200_OK: {
Expand Down
11 changes: 8 additions & 3 deletions robot-server/robot_server/instruments/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,14 @@ async def _get_attached_instruments_ot2(
@PydanticResponse.wrap_route(
instruments_router.get,
path="/instruments",
summary="Get attached instruments.",
description="Get a list of all instruments (pipettes & gripper) currently attached"
" to the robot.",
summary="Get attached instruments",
description=(
"Get a list of all instruments (pipettes & gripper) currently attached"
" to the robot."
"\n\n"
"**Warning:** The behavior of this endpoint is currently only defined for Flex"
" robots. For OT-2 robots, use `/pipettes` instead."
),
responses={status.HTTP_200_OK: {"model": SimpleMultiBody[AttachedItem]}},
)
async def get_attached_instruments(
Expand Down
8 changes: 7 additions & 1 deletion robot-server/robot_server/protocols/protocol_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,10 @@ class Protocol(ResourceModel):
),
)

key: Optional[str] = None
key: Optional[str] = Field(
None,
description=(
"An arbitrary client-defined string, set when this protocol was uploaded."
" See `POST /protocols`."
),
)
12 changes: 11 additions & 1 deletion robot-server/robot_server/protocols/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
FileHasher,
)
from opentrons_shared_data.robot.dev_types import RobotType

from robot_server.errors.error_responses import ErrorDetails, ErrorBody
from robot_server.hardware import get_robot_type
from robot_server.service.task_runner import TaskRunner, get_task_runner
Expand Down Expand Up @@ -154,7 +155,16 @@ async def create_protocol(
files: List[UploadFile] = File(...),
# use Form because request is multipart/form-data
# https://fastapi.tiangolo.com/tutorial/request-forms-and-files/
key: Optional[str] = Form(None),
key: Optional[str] = Form(
default=None,
description=(
"An arbitrary client-defined string to attach to the new protocol resource."
" This should be no longer than ~100 characters or so."
" It's intended to store something like a UUID, to help clients that store"
" protocols locally keep track of which local files correspond to which"
" protocol resources on the robot."
),
),
protocol_directory: Path = Depends(get_protocol_directory),
protocol_store: ProtocolStore = Depends(get_protocol_store),
analysis_store: AnalysisStore = Depends(get_analysis_store),
Expand Down
6 changes: 3 additions & 3 deletions robot-server/robot_server/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

router.include_router(
router=deck_configuration_router,
tags=["Deck Configuration"],
tags=["Flex Deck Configuration"],
dependencies=[Depends(check_version_header)],
)

Expand All @@ -90,7 +90,7 @@

router.include_router(
router=deprecated_session_router,
tags=["Session Management"],
tags=["OT-2 Calibration Sessions"],
dependencies=[Depends(check_version_header)],
)

Expand Down Expand Up @@ -120,7 +120,7 @@

router.include_router(
router=subsystems_router,
tags=["Subsystem Management"],
tags=["Flex Subsystem Management"],
dependencies=[Depends(check_version_header)],
)

Expand Down
4 changes: 2 additions & 2 deletions robot-server/robot_server/service/json_api/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class SimpleMultiBody(BaseResponseBody, GenericModel, Generic[ResponseDataT]):
# to be covariant is to make data the covariant Sequence protocol.
meta: MultiBodyMeta = Field(
...,
description="Metadata about the colletion response.",
description="Metadata about the collection response.",
)


Expand All @@ -125,7 +125,7 @@ class MultiBody(
links: ResponseLinksT = Field(..., description=DESCRIPTION_LINKS)
meta: MultiBodyMeta = Field(
...,
description="Metadata about the colletion response.",
description="Metadata about the collection response.",
)


Expand Down
3 changes: 3 additions & 0 deletions robot-server/robot_server/service/labware/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class LabwareCalibrationEndpointsRemoved(ErrorDetails):
"This endpoint has been removed."
" Use the `/runs` endpoints to manage labware offsets."
),
deprecated=True,
response_model=None,
responses={
status.HTTP_200_OK: {"model": lw_models.MultipleCalibrationsResponse},
Expand All @@ -62,6 +63,7 @@ async def get_all_labware_calibrations(
"This endpoint has been removed."
" Use the `/runs` endpoints to manage labware offsets."
),
deprecated=True,
response_model=None,
responses={
status.HTTP_404_NOT_FOUND: {"model": ErrorBody},
Expand Down Expand Up @@ -89,6 +91,7 @@ async def get_specific_labware_calibration(
"This endpoint has been removed."
" Use the `/runs` endpoints to manage labware offsets."
),
deprecated=True,
response_model=None,
responses={
status.HTTP_404_NOT_FOUND: {"model": ErrorBody},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,22 @@


class InstrumentOffset(BaseModel):
single: Offset
multi: Offset
single: Offset = Field(
...,
deprecated=True,
description=(
"This will always be `[0, 0, 0]`."
" Use the `GET /calibration/pipette_offset` endpoint instead."
),
)
multi: Offset = Field(
...,
deprecated=True,
description=(
"This will always be `[0, 0, 0]`."
" Use the `GET /calibration/pipette_offset` endpoint instead."
),
)


class InstrumentCalibrationStatus(BaseModel):
Expand Down Expand Up @@ -59,7 +73,12 @@ class DeckCalibrationData(BaseModel):
None, description="The ID of the pipette used in this calibration"
)
tiprack: typing.Optional[str] = Field(
None, description="The sha256 hash of the tiprack used in this calibration"
None,
description="A hash of the labware definition of the tip rack that"
" was used in this calibration."
" This is deprecated because it was prone to bugs where semantically identical"
" definitions had different hashes.",
deprecated=True,
)
source: typing.Optional[SourceType] = Field(
None, description="The calibration source"
Expand Down
30 changes: 15 additions & 15 deletions robot-server/robot_server/service/legacy/models/networking.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ class WifiNetworkFull(WifiNetwork):

signal: int = Field(
...,
description="A unitless signal strength; a higher number is a " "better signal",
description="A unitless signal strength; a higher number is a better signal",
)
active: bool = Field(..., description="Whether there is a connection active")
security: str = Field(
..., description="The raw NetworkManager output about the wifi " "security"
..., description="The raw NetworkManager output about the Wi-Fi security"
)
securityType: NetworkingSecurityType

Expand Down Expand Up @@ -133,32 +133,32 @@ class WifiConfiguration(BaseModel):
...,
description="The SSID to connect to. If this isn't an SSID that "
"is being broadcast by a network, you "
"should also set hidden to true.",
"should also set `hidden` to `true`.",
)
hidden: typing.Optional[bool] = Field(
False,
description="True if the network is hidden (not broadcasting an "
"ssid). False (default if key is not "
"present) otherwise",
description="`true` if the network is hidden (not broadcasting an SSID). "
"`false` (default if key is not "
"present) otherwise.",
)
securityType: typing.Optional[NetworkingSecurityType]

psk: typing.Optional[SecretStr] = Field(
None,
description="If this is a PSK-secured network (securityType is "
"wpa-psk), the PSK",
description="If this is a PSK-secured network (`securityType` is "
'`"wpa-psk"`), the PSK',
)
eapConfig: typing.Optional[typing.Dict[str, str]] = Field(
None,
description="All options required to configure EAP access to the"
" wifi. All options should match one of the cases "
"described in /wifi/eap-options; for instance, "
" Wi-Fi. All options should match one of the cases "
"described in `/wifi/eap-options`; for instance, "
"configuring for peap/mschapv2 should have "
'"peap/mschapv2" as the eapType; it should have '
'"identity" and "password" props, both of which '
"are identified as mandatory in /wifi/eap-options; "
'and it may also have "anonymousIdentity" and '
'"caCert" properties, both of which are identified'
'`"peap/mschapv2"` as the `eapType`; it should have '
'`"identity"` and `"password"` props, both of which '
"are identified as mandatory in `/wifi/eap-options`; "
'and it may also have `"anonymousIdentity"` and '
'`"caCert"` properties, both of which are identified'
" as present but not required.",
required=["eapType"],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class AdvancedSetting(BaseModel):
...,
description="The ID by which the property used to be known; not"
" useful now and may contain spaces or hyphens",
deprecated=True,
)
title: str = Field(
...,
Expand Down
27 changes: 22 additions & 5 deletions robot-server/robot_server/service/legacy/routers/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@

@router.post(
"/identify",
description="Blink the OT-2's gantry lights so you can pick it " "out of a crowd",
summary="Blink the lights",
description="Blink the gantry lights so you can pick it out of a crowd",
)
async def post_identify(
seconds: int = Query(..., description="Time to blink the lights for"),
Expand All @@ -37,8 +38,15 @@ async def post_identify(

@router.get(
"/robot/positions",
description="Get a list of useful positions",
summary="Get robot positions",
description=(
"Get a list of useful positions."
"\n\n"
"**Deprecated:** This data only makes sense for OT-2 robots, not Flex robots."
" There is currently no public way to get these positions for Flex robots."
),
response_model=control.RobotPositionsResponse,
deprecated=True,
)
async def get_robot_positions() -> control.RobotPositionsResponse:
"""
Expand All @@ -60,14 +68,20 @@ async def get_robot_positions() -> control.RobotPositionsResponse:

@router.post(
path="/robot/move",
summary="Move the robot",
description=(
"Move the robot's gantry to a position (usually to a "
"position retrieved from GET /robot/positions)"
"position retrieved from `GET /robot/positions`)."
"\n\n"
"**Deprecated:**"
" Run a `moveToCoordinates` command in a maintenance run instead."
" See the `/maintenance_runs` endpoints."
),
response_model=V1BasicResponse,
responses={
status.HTTP_403_FORBIDDEN: {"model": LegacyErrorResponse},
},
deprecated=True,
)
async def post_move_robot(
robot_move_target: control.RobotMoveTarget,
Expand All @@ -85,7 +99,8 @@ async def post_move_robot(

@router.post(
path="/robot/home",
description="Home the robot",
summary="Home the robot",
description="Home the robot.",
response_model=V1BasicResponse,
responses={
status.HTTP_400_BAD_REQUEST: {"model": LegacyErrorResponse},
Expand Down Expand Up @@ -126,7 +141,8 @@ async def post_home_robot(

@router.get(
"/robot/lights",
description="Get the current status of the OT-2's rail lights",
summary="Get whether the lights are on",
description="Get the current status of the robot's rail lights",
response_model=control.RobotLightState,
)
async def get_robot_light_state(
Expand All @@ -138,6 +154,7 @@ async def get_robot_light_state(

@router.post(
"/robot/lights",
summary="Turn the lights on or off",
description="Turn the rail lights on or off",
response_model=control.RobotLightState,
)
Expand Down
Loading

0 comments on commit f50e261

Please sign in to comment.