-
Notifications
You must be signed in to change notification settings - Fork 178
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
feat(api): define & execute v3 json protocols #3312
Conversation
* define v3 json protocol schema * support v3 JSON protocol execution in APIv2 executor Closes #3110
Codecov Report
@@ Coverage Diff @@
## edge #3312 +/- ##
=========================================
+ Coverage 53.63% 54.8% +1.16%
=========================================
Files 716 721 +5
Lines 20907 21748 +841
=========================================
+ Hits 11213 11918 +705
- Misses 9694 9830 +136
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some code review changes but the structure looks good
dispatch_json(true_context, protocol_json, ins, lw) | ||
protocol_version = get_protocol_schema_version(protocol_json) | ||
if (protocol_version) > 3: | ||
raise RuntimeError('JSON Protocol version {} is not yet supported \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May fail lint; \ is not required inside braces, you can do
raise RuntimError('Json Protocol version {} is not yet supported'
'in this version of the API')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make -C api lint
is working for me as-is but would you prefer this?
raise RuntimeError((
'JSON Protocol version {} is not yet ' +
'supported in this version of the API')
.format(protocol_version))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also do this fancy new thing (similar to javascript evaluated strings)
f'Json Protocol version {protocol_version} is not yet supported in this version of the API'
if you don't want to use format
pipette_data = protocol.get('pipettes', {}) | ||
pipettes_by_id = {} | ||
for pipette_id, props in pipette_data.items(): | ||
model = props.get('model') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these keys are mandatory we should access them via dict indexing: model = props['model']
which will fail fast with a KeyError
if the protocol is malformed and the keys aren't present, and fixes issues with types since get()
returns Optional[T]
def load_pipettes_from_json( | ||
ctx: ProtocolContext, | ||
protocol: Dict[Any, Any]) -> Dict[str, InstrumentContext]: | ||
pipette_data = protocol.get('pipettes', {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accesses to mandatory keys should be dict indexing (protocol['pipettes']
) to avoid typing issues and fail fast
|
||
|
||
def _get_well(loaded_labware: Dict[str, labware.Labware], | ||
params: Dict[str, Any]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return type docs?
ctx: ProtocolContext, | ||
protocol: Dict[Any, Any]) -> Dict[str, labware.Labware]: | ||
protocol_labware = protocol.get('labware', {}) | ||
definitions = protocol.get('labwareDefinitions', {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mandatory keys?
# TODO: Ian 2018-11-06 remove this fallback to 'model' when | ||
# backwards-compatability for JSON protocols with versioned | ||
# pipettes is dropped (next JSON protocol schema major bump) | ||
pipette_name = protocol_pipette_data.get('model') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This TODO is in fact resolved in v3, right?
# trough def with arbitrary ID | ||
data = { | ||
"labwareDefinitions": { | ||
"someTroughDef": json.loads("""{"ordering":[["A1"],["A2"],["A3"],["A4"],["A5"],["A6"],["A7"],["A8"],["A9"],["A10"],["A11"],["A12"]],"otId":"THIS IS A CUSTOM ID","deprecated":false,"metadata":{"displayName":"CUSTOM 12 Channel Trough","displayVolumeUnits":"mL","displayCategory":"trough"},"cornerOffsetFromSlot":{"x":0,"y":0.32,"z":0},"dimensions":{"overallLength":127.76,"overallWidth":85.8,"overallHeight":44.45},"parameters":{"format":"trough","isTiprack":false,"isMagneticModuleCompatible":false,"loadName":"usa_scientific_12_trough_22_ml","quirks":["centerMultichannelOnWells"]},"wells":{"A1":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":13.94,"y":42.9,"z":2.29},"A2":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":23.03,"y":42.9,"z":2.29},"A3":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":32.12,"y":42.9,"z":2.29},"A4":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":41.21,"y":42.9,"z":2.29},"A5":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":50.3,"y":42.9,"z":2.29},"A6":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":59.39,"y":42.9,"z":2.29},"A7":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":68.48,"y":42.9,"z":2.29},"A8":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":77.57,"y":42.9,"z":2.29},"A9":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":86.66,"y":42.9,"z":2.29},"A10":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":95.75,"y":42.9,"z":2.29},"A11":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":104.84,"y":42.9,"z":2.29},"A12":{"shape":"rectangular","depth":42.16,"length":8.33,"width":71.88,"totalLiquidVolume":22000,"x":113.93,"y":42.9,"z":2.29}},"brand":{"brand":"USA Scientific","brandId":["1061-8150"]}}""") # noqa |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe put this in a data file in this dir?
|
||
def test_dispatch_commands(monkeypatch, loop): | ||
protocol_data = { | ||
"schemaVersion": "3", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put it in a data file in this dir?
def get_protocol_schema_version(protocol_json: Dict[Any, Any]) -> int: | ||
# v3 and above uses `schemaVersion: integer` | ||
version = protocol_json.get('schemaVersion') | ||
if None is not version: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you change this to either:
if protocol_json.get('schemaVersion'):
return protocol_json.get('schemaVersion')
or:
version = protocol_json.get('schemaVersion')
if version:
return version
return 1 | ||
if (legacyKebabVersion == '2.0.0'): | ||
return 2 | ||
if (legacyKebabVersion is not None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here:
if legacyKebabVersion:
Also may want to change format to if/elif as that's normally used for conditional checking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pylint yells at you if you do
if:
return
else:
other stuff
I personally disagree but if that's what the linter says that's what the linter says
@@ -364,8 +159,22 @@ def run_protocol(protocol_code: Any = None, | |||
if None is not protocol_code: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here for checking whether a variable is populated (if protocol_code
)
dispatch_json(true_context, protocol_json, ins, lw) | ||
protocol_version = get_protocol_schema_version(protocol_json) | ||
if (protocol_version) > 3: | ||
raise RuntimeError('JSON Protocol version {} is not yet supported \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also do this fancy new thing (similar to javascript evaluated strings)
f'Json Protocol version {protocol_version} is not yet supported in this version of the API'
if you don't want to use format
.get(params.get('pipette'), {}) | ||
pipette_name = protocol_pipette_data.get('name') | ||
|
||
if (not pipette_name): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't need parentheses here
"type": "object", | ||
"properties": { | ||
"name": { | ||
"description": "Name of the application that created the protocol. Should be namespaced under the organization or individual who owns the organization, eg \"opentrons/protocol-designer\"", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the name always guaranteed to be opentrons/protocol-designer
? Should we restrict it to that value only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, it's open for any 3rd party JSON protocol creator app to use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛫
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🌺
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐎
} | ||
|
||
|
||
def load_labware_from_json_defs( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sfoster1 unlike the old load_labware_from_json_loadnames
, this doesn't handle fixed-trash or slot 12 differently. We talked about having the protocol define the fixed trash def and have it as a labware in slot 12, because users will pretty much always use the 1.1L version. Does that all seem OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm OK with that.
protocol_version = get_protocol_schema_version(protocol_json) | ||
if protocol_version > 3: | ||
raise RuntimeError( | ||
f'JSON Protocol version {protocol_version } is not yet ' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f'JSON Protocol version {protocol_version } is not yet ' + | |
f'JSON Protocol version {protocol_version} is not yet ' + |
overview
Closes #3110 and implements execution in APIv2
Does NOT implement execution in APIv1, I think we don't need to support that because by the time v3 JSON Protocols are out in the wild, we'll have moved to APIv2? If not, we can update the APIv1 executor later (I think...)
changelog
review requests
NOTE: the example protocols and the schema are different than #3307. Following a suggestion from @sfoster1,
defaultValues
key has been deleted in favor of explicit per-command paramsv3jsonProtocolExamples.zip
Review V3 schema def
Are there other parts of the commands still using implicit values that should be make into params?
Check out the schema, make sure it's designed in a sensible way that accomplishes the things we've talked about and doesn't block anything we need
Play with the example v3 protocol files and make sure the schema validates them when they should and fails to validate "bad" protocol files
npm install -g ajv-cli
then you can use theajv
CLI to validate v3 JSON protocols with the new schema:ajv validate -s shared-data/protocol-json-schema/protocolSchemaV3.json -r shared-data/labware-json-schema/labware-schema.json -d path/to/exampleProtocol.json
(the-r
pulls in the v2 labware schema so it resolve the reference toopentronsLabwareSchemaV2
in the definitions section)labware-designer
should work when embedded in these protocols!