opentrons 8.3.0a1__py2.py3-none-any.whl → 8.3.0a4__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/hardware_control/api.py +5 -1
- opentrons/hardware_control/ot3api.py +18 -8
- opentrons/hardware_control/protocols/liquid_handler.py +4 -1
- opentrons/hardware_control/protocols/motion_controller.py +1 -0
- opentrons/legacy_commands/commands.py +37 -0
- opentrons/legacy_commands/types.py +39 -0
- opentrons/protocol_api/core/engine/instrument.py +109 -0
- opentrons/protocol_api/core/instrument.py +27 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +50 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +24 -0
- opentrons/protocol_api/instrument_context.py +141 -0
- opentrons/protocol_engine/commands/__init__.py +40 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -1
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +1 -1
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -1
- opentrons/protocol_engine/commands/absorbance_reader/read.py +4 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +1 -1
- opentrons/protocol_engine/commands/command_unions.py +39 -0
- opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
- opentrons/protocol_engine/commands/get_next_tip.py +1 -1
- opentrons/protocol_engine/commands/liquid_probe.py +63 -12
- opentrons/protocol_engine/commands/load_labware.py +5 -0
- opentrons/protocol_engine/commands/load_lid.py +1 -1
- opentrons/protocol_engine/commands/load_lid_stack.py +1 -1
- opentrons/protocol_engine/commands/load_liquid_class.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +9 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +1 -1
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +1 -1
- opentrons/protocol_engine/commands/robot/move_axes_to.py +1 -1
- opentrons/protocol_engine/commands/robot/move_to.py +1 -1
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -1
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -1
- opentrons/protocol_engine/errors/__init__.py +4 -0
- opentrons/protocol_engine/errors/exceptions.py +26 -0
- opentrons/protocol_engine/execution/gantry_mover.py +5 -0
- opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
- opentrons/protocol_engine/execution/tip_handler.py +30 -9
- opentrons/protocol_engine/resources/labware_validation.py +13 -0
- opentrons/protocol_engine/state/commands.py +6 -2
- opentrons/protocol_engine/state/frustum_helpers.py +13 -44
- opentrons/protocol_engine/state/labware.py +13 -1
- opentrons/protocol_engine/state/pipettes.py +5 -0
- opentrons/protocols/labware.py +37 -8
- opentrons/util/entrypoint_util.py +2 -5
- {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/METADATA +4 -4
- {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/RECORD +58 -55
- {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/LICENSE +0 -0
- {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/WHEEL +0 -0
- {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/top_level.txt +0 -0
|
@@ -65,7 +65,7 @@ class HardwareStopper:
|
|
|
65
65
|
axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z]
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
async def
|
|
68
|
+
async def _try_to_drop_tips(self) -> None:
|
|
69
69
|
"""Drop currently attached tip, if any, into trash after a run cancel."""
|
|
70
70
|
attached_tips = self._state_store.pipettes.get_all_attached_tips()
|
|
71
71
|
|
|
@@ -134,9 +134,9 @@ class HardwareStopper:
|
|
|
134
134
|
PostRunHardwareState.HOME_THEN_DISENGAGE,
|
|
135
135
|
)
|
|
136
136
|
if drop_tips_after_run:
|
|
137
|
-
await self.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
await self._try_to_drop_tips()
|
|
138
|
+
|
|
139
|
+
await self._hardware_api.stop(home_after=False)
|
|
140
|
+
|
|
141
|
+
if home_after_stop:
|
|
142
|
+
await self._home_everything_except_plungers()
|
|
@@ -62,6 +62,7 @@ class TipHandler(TypingProtocol):
|
|
|
62
62
|
pipette_id: str,
|
|
63
63
|
labware_id: str,
|
|
64
64
|
well_name: str,
|
|
65
|
+
do_not_ignore_tip_presence: bool = True,
|
|
65
66
|
) -> TipGeometry:
|
|
66
67
|
"""Pick up the named tip.
|
|
67
68
|
|
|
@@ -75,7 +76,13 @@ class TipHandler(TypingProtocol):
|
|
|
75
76
|
"""
|
|
76
77
|
...
|
|
77
78
|
|
|
78
|
-
async def drop_tip(
|
|
79
|
+
async def drop_tip(
|
|
80
|
+
self,
|
|
81
|
+
pipette_id: str,
|
|
82
|
+
home_after: Optional[bool],
|
|
83
|
+
do_not_ignore_tip_presence: bool = True,
|
|
84
|
+
ignore_plunger: bool = False,
|
|
85
|
+
) -> None:
|
|
79
86
|
"""Drop the attached tip into the current location.
|
|
80
87
|
|
|
81
88
|
Pipette should be in place over the destination prior to calling this method.
|
|
@@ -230,6 +237,7 @@ class HardwareTipHandler(TipHandler):
|
|
|
230
237
|
pipette_id: str,
|
|
231
238
|
labware_id: str,
|
|
232
239
|
well_name: str,
|
|
240
|
+
do_not_ignore_tip_presence: bool = True,
|
|
233
241
|
) -> TipGeometry:
|
|
234
242
|
"""See documentation on abstract base class."""
|
|
235
243
|
hw_mount = self._get_hw_mount(pipette_id)
|
|
@@ -253,10 +261,11 @@ class HardwareTipHandler(TipHandler):
|
|
|
253
261
|
await self._hardware_api.tip_pickup_moves(
|
|
254
262
|
mount=hw_mount, presses=None, increment=None
|
|
255
263
|
)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
264
|
+
if do_not_ignore_tip_presence:
|
|
265
|
+
try:
|
|
266
|
+
await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
|
|
267
|
+
except TipNotAttachedError as e:
|
|
268
|
+
raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
|
|
260
269
|
|
|
261
270
|
self.cache_tip(pipette_id, tip_geometry)
|
|
262
271
|
|
|
@@ -264,7 +273,13 @@ class HardwareTipHandler(TipHandler):
|
|
|
264
273
|
|
|
265
274
|
return tip_geometry
|
|
266
275
|
|
|
267
|
-
async def drop_tip(
|
|
276
|
+
async def drop_tip(
|
|
277
|
+
self,
|
|
278
|
+
pipette_id: str,
|
|
279
|
+
home_after: Optional[bool],
|
|
280
|
+
do_not_ignore_tip_presence: bool = True,
|
|
281
|
+
ignore_plunger: bool = False,
|
|
282
|
+
) -> None:
|
|
268
283
|
"""See documentation on abstract base class."""
|
|
269
284
|
hw_mount = self._get_hw_mount(pipette_id)
|
|
270
285
|
|
|
@@ -275,10 +290,13 @@ class HardwareTipHandler(TipHandler):
|
|
|
275
290
|
else:
|
|
276
291
|
kwargs = {}
|
|
277
292
|
|
|
278
|
-
await self._hardware_api.tip_drop_moves(
|
|
293
|
+
await self._hardware_api.tip_drop_moves(
|
|
294
|
+
mount=hw_mount, ignore_plunger=ignore_plunger, **kwargs
|
|
295
|
+
)
|
|
279
296
|
|
|
280
|
-
|
|
281
|
-
|
|
297
|
+
if do_not_ignore_tip_presence:
|
|
298
|
+
# Allow TipNotAttachedError to propagate.
|
|
299
|
+
await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)
|
|
282
300
|
|
|
283
301
|
self.remove_tip(pipette_id)
|
|
284
302
|
|
|
@@ -383,6 +401,7 @@ class VirtualTipHandler(TipHandler):
|
|
|
383
401
|
pipette_id: str,
|
|
384
402
|
labware_id: str,
|
|
385
403
|
well_name: str,
|
|
404
|
+
do_not_ignore_tip_presence: bool = True,
|
|
386
405
|
) -> TipGeometry:
|
|
387
406
|
"""Pick up a tip at the current location using a virtual pipette.
|
|
388
407
|
|
|
@@ -424,6 +443,8 @@ class VirtualTipHandler(TipHandler):
|
|
|
424
443
|
self,
|
|
425
444
|
pipette_id: str,
|
|
426
445
|
home_after: Optional[bool],
|
|
446
|
+
do_not_ignore_tip_presence: bool = True,
|
|
447
|
+
ignore_plunger: bool = False,
|
|
427
448
|
) -> None:
|
|
428
449
|
"""Pick up a tip at the current location using a virtual pipette.
|
|
429
450
|
|
|
@@ -14,6 +14,11 @@ def is_absorbance_reader_lid(load_name: str) -> bool:
|
|
|
14
14
|
return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def is_evotips(load_name: str) -> bool:
|
|
18
|
+
"""Check if a labware is an evotips tiprack."""
|
|
19
|
+
return load_name == "evotips_opentrons_96_labware"
|
|
20
|
+
|
|
21
|
+
|
|
17
22
|
def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
|
|
18
23
|
"""Validate that one of the definition's allowed roles is `labware`.
|
|
19
24
|
|
|
@@ -44,6 +49,14 @@ def validate_labware_can_be_stacked(
|
|
|
44
49
|
return below_labware_load_name in top_labware_definition.stackingOffsetWithLabware
|
|
45
50
|
|
|
46
51
|
|
|
52
|
+
def validate_labware_can_be_ondeck(definition: LabwareDefinition) -> bool:
|
|
53
|
+
"""Validate that the labware being loaded onto the deck can sit in a slot."""
|
|
54
|
+
return (
|
|
55
|
+
definition.parameters.quirks is None
|
|
56
|
+
or "stackingOnly" not in definition.parameters.quirks
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
47
60
|
def validate_gripper_compatible(definition: LabwareDefinition) -> bool:
|
|
48
61
|
"""Validate that the labware definition does not have a quirk disallowing movement with gripper."""
|
|
49
62
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Protocol engine commands sub-state."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import enum
|
|
@@ -304,7 +305,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
304
305
|
# TODO(mc, 2021-06-22): mypy has trouble with this automatic
|
|
305
306
|
# request > command mapping, figure out how to type precisely
|
|
306
307
|
# (or wait for a future mypy version that can figure it out).
|
|
307
|
-
queued_command = action.request._CommandCls.model_construct(
|
|
308
|
+
queued_command = action.request._CommandCls.model_construct(
|
|
308
309
|
id=action.command_id,
|
|
309
310
|
key=(
|
|
310
311
|
action.request.key
|
|
@@ -506,7 +507,10 @@ class CommandStore(HasState[CommandState], HandlesActions):
|
|
|
506
507
|
pass
|
|
507
508
|
case QueueStatus.RUNNING | QueueStatus.PAUSED:
|
|
508
509
|
self._state.queue_status = QueueStatus.PAUSED
|
|
509
|
-
case
|
|
510
|
+
case (
|
|
511
|
+
QueueStatus.AWAITING_RECOVERY
|
|
512
|
+
| QueueStatus.AWAITING_RECOVERY_PAUSED
|
|
513
|
+
):
|
|
510
514
|
self._state.queue_status = QueueStatus.AWAITING_RECOVERY_PAUSED
|
|
511
515
|
elif action.door_state == DoorState.CLOSED:
|
|
512
516
|
self._state.is_door_blocking = False
|
|
@@ -82,19 +82,12 @@ def _circular_frustum_polynomial_roots(
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _volume_from_height_circular(
|
|
85
|
-
target_height: float,
|
|
86
|
-
total_frustum_height: float,
|
|
87
|
-
bottom_radius: float,
|
|
88
|
-
top_radius: float,
|
|
85
|
+
target_height: float, segment: ConicalFrustum
|
|
89
86
|
) -> float:
|
|
90
87
|
"""Find the volume given a height within a circular frustum."""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
total_frustum_height=total_frustum_height,
|
|
95
|
-
)
|
|
96
|
-
volume = a * (target_height**3) + b * (target_height**2) + c * target_height
|
|
97
|
-
return volume
|
|
88
|
+
heights = segment.height_to_volume_table.keys()
|
|
89
|
+
best_fit_height = min(heights, key=lambda x: abs(x - target_height))
|
|
90
|
+
return segment.height_to_volume_table[best_fit_height]
|
|
98
91
|
|
|
99
92
|
|
|
100
93
|
def _volume_from_height_rectangular(
|
|
@@ -138,26 +131,12 @@ def _volume_from_height_squared_cone(
|
|
|
138
131
|
|
|
139
132
|
|
|
140
133
|
def _height_from_volume_circular(
|
|
141
|
-
|
|
142
|
-
total_frustum_height: float,
|
|
143
|
-
bottom_radius: float,
|
|
144
|
-
top_radius: float,
|
|
134
|
+
target_volume: float, segment: ConicalFrustum
|
|
145
135
|
) -> float:
|
|
146
|
-
"""Find the height given a volume within a
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
total_frustum_height=total_frustum_height,
|
|
151
|
-
)
|
|
152
|
-
d = volume * -1
|
|
153
|
-
x_intercept_roots = (a, b, c, d)
|
|
154
|
-
|
|
155
|
-
height_from_volume_roots = roots(x_intercept_roots)
|
|
156
|
-
height = _reject_unacceptable_heights(
|
|
157
|
-
potential_heights=list(height_from_volume_roots),
|
|
158
|
-
max_height=total_frustum_height,
|
|
159
|
-
)
|
|
160
|
-
return height
|
|
136
|
+
"""Find the height given a volume within a squared cone segment."""
|
|
137
|
+
volumes = segment.volume_to_height_table.keys()
|
|
138
|
+
best_fit_volume = min(volumes, key=lambda x: abs(x - target_volume))
|
|
139
|
+
return segment.volume_to_height_table[best_fit_volume]
|
|
161
140
|
|
|
162
141
|
|
|
163
142
|
def _height_from_volume_rectangular(
|
|
@@ -243,9 +222,7 @@ def _get_segment_capacity(segment: WellSegment) -> float:
|
|
|
243
222
|
return (
|
|
244
223
|
_volume_from_height_circular(
|
|
245
224
|
target_height=section_height,
|
|
246
|
-
|
|
247
|
-
bottom_radius=(segment.bottomDiameter / 2),
|
|
248
|
-
top_radius=(segment.topDiameter / 2),
|
|
225
|
+
segment=segment,
|
|
249
226
|
)
|
|
250
227
|
* segment.count
|
|
251
228
|
)
|
|
@@ -293,12 +270,7 @@ def height_at_volume_within_section(
|
|
|
293
270
|
radius_of_curvature=section.radiusOfCurvature,
|
|
294
271
|
)
|
|
295
272
|
case ConicalFrustum():
|
|
296
|
-
return _height_from_volume_circular(
|
|
297
|
-
volume=target_volume_relative,
|
|
298
|
-
top_radius=(section.bottomDiameter / 2),
|
|
299
|
-
bottom_radius=(section.topDiameter / 2),
|
|
300
|
-
total_frustum_height=section_height,
|
|
301
|
-
)
|
|
273
|
+
return _height_from_volume_circular(target_volume_relative, section)
|
|
302
274
|
case CuboidalFrustum():
|
|
303
275
|
return _height_from_volume_rectangular(
|
|
304
276
|
volume=target_volume_relative,
|
|
@@ -334,10 +306,7 @@ def volume_at_height_within_section(
|
|
|
334
306
|
case ConicalFrustum():
|
|
335
307
|
return (
|
|
336
308
|
_volume_from_height_circular(
|
|
337
|
-
target_height=target_height_relative,
|
|
338
|
-
total_frustum_height=section_height,
|
|
339
|
-
bottom_radius=(section.bottomDiameter / 2),
|
|
340
|
-
top_radius=(section.topDiameter / 2),
|
|
309
|
+
target_height=target_height_relative, segment=section
|
|
341
310
|
)
|
|
342
311
|
* section.count
|
|
343
312
|
)
|
|
@@ -427,7 +396,7 @@ def _find_height_in_partial_frustum(
|
|
|
427
396
|
if (
|
|
428
397
|
bottom_section_volume
|
|
429
398
|
< target_volume
|
|
430
|
-
|
|
399
|
+
<= (bottom_section_volume + section_volume)
|
|
431
400
|
):
|
|
432
401
|
relative_target_volume = target_volume - bottom_section_volume
|
|
433
402
|
section_height = section.topHeight - section.bottomHeight
|
|
@@ -524,7 +524,6 @@ class LabwareView:
|
|
|
524
524
|
will be used.
|
|
525
525
|
"""
|
|
526
526
|
definition = self.get_definition(labware_id)
|
|
527
|
-
|
|
528
527
|
if well_name is None:
|
|
529
528
|
well_name = definition.ordering[0][0]
|
|
530
529
|
|
|
@@ -887,6 +886,19 @@ class LabwareView:
|
|
|
887
886
|
f"Labware {labware.loadName} is already present at {location}."
|
|
888
887
|
)
|
|
889
888
|
|
|
889
|
+
def raise_if_labware_cannot_be_ondeck(
|
|
890
|
+
self,
|
|
891
|
+
location: LabwareLocation,
|
|
892
|
+
labware_definition: LabwareDefinition,
|
|
893
|
+
) -> None:
|
|
894
|
+
"""Raise an error if the labware cannot be in the specified location."""
|
|
895
|
+
if isinstance(
|
|
896
|
+
location, (DeckSlotLocation, AddressableAreaLocation)
|
|
897
|
+
) and not labware_validation.validate_labware_can_be_ondeck(labware_definition):
|
|
898
|
+
raise errors.LabwareCannotSitOnDeckError(
|
|
899
|
+
f"{labware_definition.parameters.loadName} cannot sit in a slot by itself."
|
|
900
|
+
)
|
|
901
|
+
|
|
890
902
|
def raise_if_labware_incompatible_with_plate_reader(
|
|
891
903
|
self,
|
|
892
904
|
labware_definition: LabwareDefinition,
|
|
@@ -331,6 +331,11 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
331
331
|
def _update_aspirated(
|
|
332
332
|
self, update: update_types.PipetteAspiratedFluidUpdate
|
|
333
333
|
) -> None:
|
|
334
|
+
if self._state.pipette_contents_by_id[update.pipette_id] is None:
|
|
335
|
+
self._state.pipette_contents_by_id[
|
|
336
|
+
update.pipette_id
|
|
337
|
+
] = fluid_stack.FluidStack()
|
|
338
|
+
|
|
334
339
|
self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid)
|
|
335
340
|
|
|
336
341
|
def _update_ejected(self, update: update_types.PipetteEjectedFluidUpdate) -> None:
|
opentrons/protocols/labware.py
CHANGED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, AnyStr, Dict, Optional, Union, List
|
|
7
|
+
from typing import Any, AnyStr, Dict, Optional, Union, List, Sequence, Literal
|
|
8
8
|
|
|
9
9
|
import jsonschema # type: ignore
|
|
10
10
|
|
|
@@ -17,10 +17,30 @@ from opentrons.protocols.api_support.constants import (
|
|
|
17
17
|
USER_DEFS_PATH,
|
|
18
18
|
)
|
|
19
19
|
from opentrons_shared_data.labware.types import LabwareDefinition
|
|
20
|
+
from opentrons_shared_data.errors.exceptions import InvalidProtocolData
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
MODULE_LOG = logging.getLogger(__name__)
|
|
23
24
|
|
|
25
|
+
LabwareProblem = Literal[
|
|
26
|
+
"no-schema-id", "bad-schema-id", "schema-mismatch", "invalid-json"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NotALabwareError(InvalidProtocolData):
|
|
31
|
+
def __init__(
|
|
32
|
+
self, problem: LabwareProblem, wrapping: Sequence[BaseException]
|
|
33
|
+
) -> None:
|
|
34
|
+
messages: dict[LabwareProblem, str] = {
|
|
35
|
+
"no-schema-id": "No schema ID present in file",
|
|
36
|
+
"bad-schema-id": "Bad schema ID in file",
|
|
37
|
+
"invalid-json": "File does not contain valid JSON",
|
|
38
|
+
"schema-mismatch": "File does not match labware schema",
|
|
39
|
+
}
|
|
40
|
+
super().__init__(
|
|
41
|
+
message=messages[problem], detail={"kind": problem}, wrapping=wrapping
|
|
42
|
+
)
|
|
43
|
+
|
|
24
44
|
|
|
25
45
|
def get_labware_definition(
|
|
26
46
|
load_name: str,
|
|
@@ -126,7 +146,7 @@ def save_definition(
|
|
|
126
146
|
json.dump(labware_def, f)
|
|
127
147
|
|
|
128
148
|
|
|
129
|
-
def verify_definition(
|
|
149
|
+
def verify_definition( # noqa: C901
|
|
130
150
|
contents: Union[AnyStr, LabwareDefinition, Dict[str, Any]]
|
|
131
151
|
) -> LabwareDefinition:
|
|
132
152
|
"""Verify that an input string is a labware definition and return it.
|
|
@@ -146,15 +166,24 @@ def verify_definition(
|
|
|
146
166
|
if isinstance(contents, dict):
|
|
147
167
|
to_return = contents
|
|
148
168
|
else:
|
|
149
|
-
|
|
169
|
+
try:
|
|
170
|
+
to_return = json.loads(contents)
|
|
171
|
+
except json.JSONDecodeError as e:
|
|
172
|
+
raise NotALabwareError("invalid-json", [e]) from e
|
|
150
173
|
try:
|
|
151
174
|
schema_version = to_return["schemaVersion"]
|
|
175
|
+
except KeyError as e:
|
|
176
|
+
raise NotALabwareError("no-schema-id", [e]) from e
|
|
177
|
+
|
|
178
|
+
try:
|
|
152
179
|
schema = schemata_by_version[schema_version]
|
|
153
|
-
except KeyError:
|
|
154
|
-
raise
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
180
|
+
except KeyError as e:
|
|
181
|
+
raise NotALabwareError("bad-schema-id", [e]) from e
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
jsonschema.validate(to_return, schema)
|
|
185
|
+
except jsonschema.ValidationError as e:
|
|
186
|
+
raise NotALabwareError("schema-mismatch", [e]) from e
|
|
158
187
|
|
|
159
188
|
# we can type ignore this because if it passes the jsonschema it has
|
|
160
189
|
# the correct structure
|
|
@@ -6,7 +6,6 @@ import contextlib
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
|
-
from json import JSONDecodeError
|
|
10
9
|
import pathlib
|
|
11
10
|
import subprocess
|
|
12
11
|
import sys
|
|
@@ -21,8 +20,6 @@ from typing import (
|
|
|
21
20
|
TYPE_CHECKING,
|
|
22
21
|
)
|
|
23
22
|
|
|
24
|
-
from jsonschema import ValidationError # type: ignore
|
|
25
|
-
|
|
26
23
|
from opentrons.calibration_storage.deck_configuration import (
|
|
27
24
|
deserialize_deck_configuration,
|
|
28
25
|
)
|
|
@@ -32,7 +29,7 @@ from opentrons.config import (
|
|
|
32
29
|
JUPYTER_NOTEBOOK_LABWARE_DIR,
|
|
33
30
|
SystemArchitecture,
|
|
34
31
|
)
|
|
35
|
-
from opentrons.
|
|
32
|
+
from opentrons.protocols import labware
|
|
36
33
|
from opentrons.calibration_storage import helpers
|
|
37
34
|
from opentrons.protocol_engine.errors.error_occurrence import (
|
|
38
35
|
ErrorOccurrence as ProtocolEngineErrorOccurrence,
|
|
@@ -79,7 +76,7 @@ def labware_from_paths(
|
|
|
79
76
|
if child.is_file() and child.suffix.endswith("json"):
|
|
80
77
|
try:
|
|
81
78
|
defn = labware.verify_definition(child.read_bytes())
|
|
82
|
-
except
|
|
79
|
+
except labware.NotALabwareError:
|
|
83
80
|
log.info(f"{child}: invalid labware, ignoring")
|
|
84
81
|
log.debug(
|
|
85
82
|
f"{child}: labware invalid because of this exception.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opentrons
|
|
3
|
-
Version: 8.3.
|
|
3
|
+
Version: 8.3.0a4
|
|
4
4
|
Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
|
|
5
5
|
Author: Opentrons
|
|
6
6
|
Author-email: engineering@opentrons.com
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
23
|
License-File: ../LICENSE
|
|
24
|
-
Requires-Dist: opentrons-shared-data (==8.3.
|
|
24
|
+
Requires-Dist: opentrons-shared-data (==8.3.0a4)
|
|
25
25
|
Requires-Dist: aionotify (==0.3.1)
|
|
26
26
|
Requires-Dist: anyio (<4.0.0,>=3.6.1)
|
|
27
27
|
Requires-Dist: jsonschema (<4.18.0,>=3.0.1)
|
|
@@ -35,9 +35,9 @@ Requires-Dist: pyusb (==1.2.1)
|
|
|
35
35
|
Requires-Dist: packaging (>=21.0)
|
|
36
36
|
Requires-Dist: importlib-metadata (>=1.0) ; python_version < "3.8"
|
|
37
37
|
Provides-Extra: flex-hardware
|
|
38
|
-
Requires-Dist: opentrons-hardware[flex] (==8.3.
|
|
38
|
+
Requires-Dist: opentrons-hardware[flex] (==8.3.0a4) ; extra == 'flex-hardware'
|
|
39
39
|
Provides-Extra: ot2-hardware
|
|
40
|
-
Requires-Dist: opentrons-hardware (==8.3.
|
|
40
|
+
Requires-Dist: opentrons-hardware (==8.3.0a4) ; extra == 'ot2-hardware'
|
|
41
41
|
|
|
42
42
|
.. _Full API Documentation: http://docs.opentrons.com
|
|
43
43
|
|