-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(robot-server): Implement the
/labwareOffsets
endpoints with an…
… in-memory store (#17059)
- Loading branch information
1 parent
68947bb
commit 5405714
Showing
8 changed files
with
455 additions
and
17 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
robot-server/robot_server/labware_offsets/fastapi_dependencies.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"""FastAPI dependencies for the `/labwareOffsets` endpoints.""" | ||
|
||
|
||
from typing import Annotated | ||
|
||
from fastapi import Depends | ||
|
||
from server_utils.fastapi_utils.app_state import ( | ||
AppState, | ||
AppStateAccessor, | ||
get_app_state, | ||
) | ||
from .store import LabwareOffsetStore | ||
|
||
|
||
_labware_offset_store_accessor = AppStateAccessor[LabwareOffsetStore]( | ||
"labware_offset_store" | ||
) | ||
|
||
|
||
async def get_labware_offset_store( | ||
app_state: Annotated[AppState, Depends(get_app_state)], | ||
) -> LabwareOffsetStore: | ||
"""Get the server's singleton LabwareOffsetStore.""" | ||
labware_offset_store = _labware_offset_store_accessor.get_from(app_state) | ||
if labware_offset_store is None: | ||
labware_offset_store = LabwareOffsetStore() | ||
_labware_offset_store_accessor.set_on(app_state, labware_offset_store) | ||
return labware_offset_store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"""Request/response models for the `/labwareOffsets` endpoints.""" | ||
|
||
|
||
from typing import Literal, Type | ||
from typing_extensions import Self | ||
|
||
from robot_server.errors.error_responses import ErrorDetails | ||
|
||
|
||
class LabwareOffsetNotFound(ErrorDetails): | ||
"""An error returned when a requested labware offset does not exist.""" | ||
|
||
id: Literal["LabwareOffsetNotFound"] = "LabwareOffsetNotFound" | ||
title: str = "Labware Offset Not Found" | ||
|
||
@classmethod | ||
def build(cls: Type[Self], bad_offset_id: str) -> Self: | ||
"""Return an error with a standard message.""" | ||
return cls.construct(detail=f'No offset found with ID "{bad_offset_id}".') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# noqa: D100 | ||
|
||
from opentrons.protocol_engine import LabwareOffset, ModuleModel | ||
from opentrons.types import DeckSlotName | ||
|
||
|
||
# todo(mm, 2024-12-06): Convert to be SQL-based and persistent instead of in-memory. | ||
# https://opentrons.atlassian.net/browse/EXEC-1015 | ||
class LabwareOffsetStore: | ||
"""A persistent store for labware offsets, to support the `/labwareOffsets` endpoints.""" | ||
|
||
def __init__(self) -> None: | ||
self._offsets_by_id: dict[str, LabwareOffset] = {} | ||
|
||
def add(self, offset: LabwareOffset) -> None: | ||
"""Store a new labware offset.""" | ||
assert offset.id not in self._offsets_by_id | ||
self._offsets_by_id[offset.id] = offset | ||
|
||
def search( | ||
self, | ||
id_filter: str | None, | ||
definition_uri_filter: str | None, | ||
location_slot_name_filter: DeckSlotName | None, | ||
location_module_model_filter: ModuleModel | None, | ||
location_definition_uri_filter: str | None, | ||
# todo(mm, 2024-12-06): Support pagination (cursor & pageLength query params). | ||
# The logic for that is currently duplicated across several places in | ||
# robot-server and api. We should try to clean that up, or at least avoid | ||
# making it worse. | ||
) -> list[LabwareOffset]: | ||
"""Return all matching labware offsets in order from oldest-added to newest.""" | ||
|
||
def is_match(candidate: LabwareOffset) -> bool: | ||
return ( | ||
id_filter in (None, candidate.id) | ||
and definition_uri_filter in (None, candidate.definitionUri) | ||
and location_slot_name_filter in (None, candidate.location.slotName) | ||
and location_module_model_filter | ||
in (None, candidate.location.moduleModel) | ||
and location_definition_uri_filter | ||
in (None, candidate.location.definitionUri) | ||
) | ||
|
||
return [ | ||
candidate | ||
for candidate in self._offsets_by_id.values() | ||
if is_match(candidate) | ||
] | ||
|
||
def delete(self, offset_id: str) -> LabwareOffset: | ||
"""Delete a labware offset by its ID. Return what was just deleted.""" | ||
try: | ||
return self._offsets_by_id.pop(offset_id) | ||
except KeyError: | ||
raise LabwareOffsetNotFoundError(bad_offset_id=offset_id) from None | ||
|
||
def delete_all(self) -> None: | ||
"""Delete all labware offsets.""" | ||
self._offsets_by_id.clear() | ||
|
||
|
||
class LabwareOffsetNotFoundError(KeyError): | ||
"""Raised when trying to access a labware offset that doesn't exist.""" | ||
|
||
def __init__(self, bad_offset_id: str) -> None: | ||
super().__init__(bad_offset_id) | ||
self.bad_offset_id = bad_offset_id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.