-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): implement loadLiquidClass command in PE (#16814)
# Overview This is the second half of AUTH-851. This implements the `loadLiquidClass()` command using the liquid class store from the previous PR. Liquid classes are read-only in the Protocol Engine, so each `liquidClassId` refers to one specific liquid class definition. Each mutation of a liquid class needs to be stored under a different `liquidClassId`. The caller can specify the `liquidClassId` if they want to, or else we will generate one for them. So there are 4 cases we need to handle: 1. Caller did not specify a `liquidClassId` and the liquid class is new: Generate a new `liquidClassId`. 2. Caller did not specify a `liquidClassId` but the liquid class has already been stored: Reuse the existing `liquidClassId`. 3. Caller specified a `liquidClassId` that we haven't seen before: Store the liquid class under the new `liquidClassId`. 4. Caller specified a `liquidClassId` that's already been loaded: Check that the incoming liquid class matches the one we previously stored, and: a. If the incoming liquid class exactly matches the existing one, do nothing. b. If they don't match, raise an error. ## Test Plan and Hands on Testing I added 5 test cases to cover each of the scenarios above. ## Review requests Could someone teach me what `StateView.get_summary()` is supposed to do? ## Risk assessment Low risk, liquid classes are not released yet and these changes should be dev-only. This change makes the Protocol Engine state marginally larger.
- Loading branch information
Showing
8 changed files
with
1,244 additions
and
3 deletions.
There are no files selected for viewing
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
137 changes: 137 additions & 0 deletions
137
api/src/opentrons/protocol_engine/commands/load_liquid_class.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,137 @@ | ||
"""LoadLiquidClass stores the liquid class settings used for a transfer into the Protocol Engine.""" | ||
from __future__ import annotations | ||
|
||
from typing import Optional, Type, TYPE_CHECKING | ||
from typing_extensions import Literal | ||
from pydantic import BaseModel, Field | ||
|
||
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData | ||
from ..errors import LiquidClassDoesNotExistError | ||
from ..errors.error_occurrence import ErrorOccurrence | ||
from ..errors.exceptions import LiquidClassRedefinitionError | ||
from ..state.update_types import LiquidClassLoadedUpdate, StateUpdate | ||
from ..types import LiquidClassRecord | ||
|
||
if TYPE_CHECKING: | ||
from ..state.state import StateView | ||
from ..resources import ModelUtils | ||
|
||
LoadLiquidClassCommandType = Literal["loadLiquidClass"] | ||
|
||
|
||
class LoadLiquidClassParams(BaseModel): | ||
"""The liquid class transfer properties to store.""" | ||
|
||
liquidClassId: Optional[str] = Field( | ||
None, | ||
description="Unique identifier for the liquid class to store. " | ||
"If you do not supply a liquidClassId, we will generate one.", | ||
) | ||
liquidClassRecord: LiquidClassRecord = Field( | ||
..., | ||
description="The liquid class to store.", | ||
) | ||
|
||
|
||
class LoadLiquidClassResult(BaseModel): | ||
"""Result from execution of LoadLiquidClass command.""" | ||
|
||
liquidClassId: str = Field( | ||
..., | ||
description="The ID for the liquid class that was loaded, either the one you " | ||
"supplied or the one we generated.", | ||
) | ||
|
||
|
||
class LoadLiquidClassImplementation( | ||
AbstractCommandImpl[LoadLiquidClassParams, SuccessData[LoadLiquidClassResult]] | ||
): | ||
"""Load Liquid Class command implementation.""" | ||
|
||
def __init__( | ||
self, state_view: StateView, model_utils: ModelUtils, **kwargs: object | ||
) -> None: | ||
self._state_view = state_view | ||
self._model_utils = model_utils | ||
|
||
async def execute( | ||
self, params: LoadLiquidClassParams | ||
) -> SuccessData[LoadLiquidClassResult]: | ||
"""Store the liquid class in the Protocol Engine.""" | ||
liquid_class_id: Optional[str] | ||
already_loaded = False | ||
|
||
if params.liquidClassId: | ||
liquid_class_id = params.liquidClassId | ||
if self._liquid_class_id_already_loaded( | ||
liquid_class_id, params.liquidClassRecord | ||
): | ||
already_loaded = True | ||
else: | ||
liquid_class_id = ( | ||
self._state_view.liquid_classes.get_id_for_liquid_class_record( | ||
params.liquidClassRecord | ||
) # if liquidClassRecord was already loaded, reuse the existing ID | ||
) | ||
if liquid_class_id: | ||
already_loaded = True | ||
else: | ||
liquid_class_id = self._model_utils.generate_id() | ||
|
||
if already_loaded: | ||
state_update = StateUpdate() # liquid class already loaded, do nothing | ||
else: | ||
state_update = StateUpdate( | ||
liquid_class_loaded=LiquidClassLoadedUpdate( | ||
liquid_class_id=liquid_class_id, | ||
liquid_class_record=params.liquidClassRecord, | ||
) | ||
) | ||
|
||
return SuccessData( | ||
public=LoadLiquidClassResult(liquidClassId=liquid_class_id), | ||
state_update=state_update, | ||
) | ||
|
||
def _liquid_class_id_already_loaded( | ||
self, liquid_class_id: str, liquid_class_record: LiquidClassRecord | ||
) -> bool: | ||
"""Check if the liquid_class_id has already been loaded. | ||
If it has, make sure that liquid_class_record matches the previously loaded definition. | ||
""" | ||
try: | ||
existing_liquid_class_record = self._state_view.liquid_classes.get( | ||
liquid_class_id | ||
) | ||
except LiquidClassDoesNotExistError: | ||
return False | ||
|
||
if liquid_class_record != existing_liquid_class_record: | ||
raise LiquidClassRedefinitionError( | ||
f"Liquid class {liquid_class_id} conflicts with previously loaded definition." | ||
) | ||
return True | ||
|
||
|
||
class LoadLiquidClass( | ||
BaseCommand[LoadLiquidClassParams, LoadLiquidClassResult, ErrorOccurrence] | ||
): | ||
"""Load Liquid Class command resource model.""" | ||
|
||
commandType: LoadLiquidClassCommandType = "loadLiquidClass" | ||
params: LoadLiquidClassParams | ||
result: Optional[LoadLiquidClassResult] | ||
|
||
_ImplementationCls: Type[ | ||
LoadLiquidClassImplementation | ||
] = LoadLiquidClassImplementation | ||
|
||
|
||
class LoadLiquidClassCreate(BaseCommandCreate[LoadLiquidClassParams]): | ||
"""Load Liquid Class command creation request.""" | ||
|
||
commandType: LoadLiquidClassCommandType = "loadLiquidClass" | ||
params: LoadLiquidClassParams | ||
|
||
_CommandCls: Type[LoadLiquidClass] = LoadLiquidClass |
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
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.