-
Notifications
You must be signed in to change notification settings - Fork 183
feat(robot-server): handle new offset locations in /labwareOffsets #17388
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
7c19837
a584f2e
b6b49ae
0269cd3
8b1bdda
3b9b2a1
aee58dc
ad0ef2f
3b4d736
699adee
04e5f85
b92e843
72fdbea
f541dcf
8111a80
7df44a2
443a8df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
"""Helper to build a search query.""" | ||
|
||
from __future__ import annotations | ||
from typing import Final, TYPE_CHECKING | ||
|
||
import sqlalchemy | ||
|
||
from opentrons.protocol_engine import ModuleModel | ||
|
||
from robot_server.persistence.tables import ( | ||
labware_offset_table, | ||
labware_offset_location_sequence_components_table, | ||
) | ||
from .models import DoNotFilterType, DO_NOT_FILTER | ||
|
||
if TYPE_CHECKING: | ||
from typing_extensions import Self | ||
|
||
|
||
class SearchQueryBuilder: | ||
"""Helper class to build a search query. | ||
|
||
This object is stateful, and should be kept around just long enough to have the parameters | ||
of a single search injected. | ||
""" | ||
|
||
def __init__(self) -> None: | ||
"""Build the object.""" | ||
super().__init__() | ||
self._filter_original: Final = sqlalchemy.select( | ||
labware_offset_table.c.row_id, | ||
labware_offset_table.c.offset_id, | ||
labware_offset_table.c.definition_uri, | ||
labware_offset_table.c.vector_x, | ||
labware_offset_table.c.vector_y, | ||
labware_offset_table.c.vector_z, | ||
labware_offset_table.c.created_at, | ||
labware_offset_table.c.active, | ||
labware_offset_location_sequence_components_table.c.sequence_ordinal, | ||
labware_offset_location_sequence_components_table.c.component_kind, | ||
labware_offset_location_sequence_components_table.c.primary_component_value, | ||
).select_from( | ||
sqlalchemy.join( | ||
labware_offset_table, | ||
labware_offset_location_sequence_components_table, | ||
labware_offset_table.c.row_id | ||
== labware_offset_location_sequence_components_table.c.offset_id, | ||
) | ||
) | ||
self._offset_location_alias: Final = ( | ||
labware_offset_location_sequence_components_table.alias() | ||
) | ||
self._current_base_filter_statement = self._filter_original | ||
self._current_positive_location_filter: ( | ||
sqlalchemy.sql.selectable.Exists | None | ||
) = None | ||
self._current_negative_filter_subqueries: list[ | ||
sqlalchemy.sql.selectable.Exists | ||
] = [] | ||
|
||
def _positive_query(self) -> sqlalchemy.sql.selectable.Exists: | ||
if self._current_positive_location_filter is not None: | ||
return self._current_positive_location_filter | ||
return sqlalchemy.exists().where( | ||
self._offset_location_alias.c.offset_id | ||
== labware_offset_location_sequence_components_table.c.offset_id | ||
) | ||
|
||
def build_query(self) -> sqlalchemy.sql.selectable.Selectable: | ||
"""Render the query into a sqlalchemy object suitable for passing to the database.""" | ||
statement = self._current_base_filter_statement | ||
if self._current_positive_location_filter is not None: | ||
statement = statement.where(self._current_positive_location_filter) | ||
for subq in self._current_negative_filter_subqueries: | ||
statement = statement.where(sqlalchemy.not_(subq)) | ||
statement = statement.order_by(labware_offset_table.c.row_id).order_by( | ||
labware_offset_location_sequence_components_table.c.sequence_ordinal | ||
) | ||
return statement | ||
|
||
def do_active_filter(self, active: bool) -> Self: | ||
"""Filter to only active=active rows.""" | ||
self._current_base_filter_statement = self._current_base_filter_statement.where( | ||
labware_offset_table.c.active == True # noqa: E712 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drive-by comment: What is the point of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the comment, the first active is the column name and the second active is the argument name. could stand to be a little clearer - it's saying it filters to only rows whose active column matches the parameter. Uhhh and that looks like a typo that it filters it to |
||
) | ||
return self | ||
|
||
def do_id_filter(self, id_filter: str | DoNotFilterType) -> Self: | ||
"""Filter to rows with only the given offset ID.""" | ||
if id_filter is DO_NOT_FILTER: | ||
return self | ||
|
||
self._current_base_filter_statement = self._current_base_filter_statement.where( | ||
labware_offset_table.c.offset_id == id_filter | ||
) | ||
return self | ||
|
||
def do_definition_uri_filter( | ||
self, definition_uri_filter: str | DoNotFilterType | ||
) -> Self: | ||
"""Filter to rows of an offset that apply to a definition URI.""" | ||
if definition_uri_filter is DO_NOT_FILTER: | ||
return self | ||
self._current_base_filter_statement = self._current_base_filter_statement.where( | ||
labware_offset_table.c.definition_uri == definition_uri_filter | ||
) | ||
return self | ||
|
||
def do_on_addressable_area_filter( | ||
self, | ||
addressable_area_filter: str | DoNotFilterType, | ||
) -> Self: | ||
"""Filter to rows of an offset that applies to the given addressable area.""" | ||
if addressable_area_filter is DO_NOT_FILTER: | ||
return self | ||
self._current_positive_location_filter = ( | ||
self._positive_query() | ||
.where(self._offset_location_alias.c.component_kind == "onAddressableArea") | ||
.where( | ||
self._offset_location_alias.c.primary_component_value | ||
== addressable_area_filter | ||
) | ||
) | ||
return self | ||
|
||
def do_on_labware_filter( | ||
self, labware_uri_filter: str | DoNotFilterType | None | ||
) -> Self: | ||
"""Filter to the rows of an offset located on the given labware (or no labware).""" | ||
if labware_uri_filter is DO_NOT_FILTER: | ||
return self | ||
if labware_uri_filter is None: | ||
self._current_negative_filter_subqueries.append( | ||
sqlalchemy.exists() | ||
.where( | ||
self._offset_location_alias.c.offset_id | ||
== labware_offset_location_sequence_components_table.c.offset_id | ||
) | ||
.where(self._offset_location_alias.c.component_kind == "onLabware") | ||
) | ||
return self | ||
self._current_positive_location_filter = ( | ||
self._positive_query() | ||
.where(self._offset_location_alias.c.component_kind == "onLabware") | ||
.where( | ||
self._offset_location_alias.c.primary_component_value | ||
== labware_uri_filter | ||
) | ||
) | ||
return self | ||
|
||
def do_on_module_filter( | ||
self, | ||
module_model_filter: ModuleModel | DoNotFilterType | None, | ||
) -> Self: | ||
"""Filter to the rows of an offset located on the given module (or no module).""" | ||
if module_model_filter is DO_NOT_FILTER: | ||
return self | ||
if module_model_filter is None: | ||
self._current_negative_filter_subqueries.append( | ||
sqlalchemy.exists() | ||
.where( | ||
self._offset_location_alias.c.offset_id | ||
== labware_offset_location_sequence_components_table.c.offset_id | ||
) | ||
.where(self._offset_location_alias.c.component_kind == "onModule") | ||
) | ||
return self | ||
self._current_positive_location_filter = ( | ||
self._positive_query() | ||
.where(self._offset_location_alias.c.component_kind == "onModule") | ||
.where( | ||
self._offset_location_alias.c.primary_component_value | ||
== module_model_filter.value | ||
) | ||
) | ||
return self |
Uh oh!
There was an error while loading. Please reload this page.