opentrons 8.3.2a0__py2.py3-none-any.whl → 8.4.0__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.
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +102 -5
- opentrons/legacy_commands/helpers.py +74 -1
- opentrons/legacy_commands/types.py +33 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1356 -107
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +6 -14
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
- opentrons/protocol_api/core/engine/well.py +73 -5
- opentrons/protocol_api/core/instrument.py +71 -21
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +23 -2
- opentrons/protocol_api/instrument_context.py +454 -150
- opentrons/protocol_api/labware.py +98 -50
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +163 -19
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +66 -36
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +102 -33
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +27 -13
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/move_to_well.py +5 -11
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +159 -8
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
- opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
- opentrons/protocol_engine/errors/__init__.py +10 -0
- opentrons/protocol_engine/errors/exceptions.py +62 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +235 -25
- opentrons/protocol_engine/execution/tip_handler.py +82 -32
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +36 -14
- opentrons/protocol_engine/state/geometry.py +892 -227
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +210 -67
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +55 -9
- opentrons/protocol_engine/types/__init__.py +300 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +111 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +301 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +27 -23
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +20 -7
- opentrons/util/logging_config.py +94 -25
- opentrons/util/logging_queue_handler.py +61 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
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
|
|
7
|
+
from typing import Mapping, Optional, Union, List, Sequence, Literal
|
|
8
8
|
|
|
9
9
|
import jsonschema # type: ignore
|
|
10
10
|
|
|
@@ -46,8 +46,8 @@ def get_labware_definition(
|
|
|
46
46
|
load_name: str,
|
|
47
47
|
namespace: Optional[str] = None,
|
|
48
48
|
version: Optional[int] = None,
|
|
49
|
-
bundled_defs: Optional[
|
|
50
|
-
extra_defs: Optional[
|
|
49
|
+
bundled_defs: Optional[Mapping[str, LabwareDefinition]] = None,
|
|
50
|
+
extra_defs: Optional[Mapping[str, LabwareDefinition]] = None,
|
|
51
51
|
) -> LabwareDefinition:
|
|
52
52
|
"""
|
|
53
53
|
Look up and return a definition by load_name + namespace + version and
|
|
@@ -147,51 +147,55 @@ def save_definition(
|
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def verify_definition( # noqa: C901
|
|
150
|
-
contents:
|
|
150
|
+
contents: str | bytes | LabwareDefinition | object,
|
|
151
151
|
) -> LabwareDefinition:
|
|
152
152
|
"""Verify that an input string is a labware definition and return it.
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
:param contents: The untrusted input to parse and validate. If str or bytes, it's
|
|
155
|
+
parsed as JSON. Otherwise, it should be the output of json.load().
|
|
156
156
|
|
|
157
|
-
:raises
|
|
158
|
-
|
|
159
|
-
:returns: The parsed definition
|
|
157
|
+
:raises NotALabwareError:
|
|
158
|
+
|
|
159
|
+
:returns: The parsed and validated definition
|
|
160
160
|
"""
|
|
161
161
|
schemata_by_version = {
|
|
162
162
|
2: json.loads(load_shared_data("labware/schemas/2.json").decode("utf-8")),
|
|
163
163
|
3: json.loads(load_shared_data("labware/schemas/3.json").decode("utf-8")),
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
if isinstance(contents, dict):
|
|
167
|
-
to_return = contents
|
|
168
|
-
else:
|
|
169
|
-
try:
|
|
170
|
-
to_return = json.loads(contents)
|
|
171
|
-
except json.JSONDecodeError as e:
|
|
172
|
-
raise NotALabwareError("invalid-json", [e]) from e
|
|
173
166
|
try:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
167
|
+
parsed_json: object = (
|
|
168
|
+
json.loads(contents) if isinstance(contents, (str, bytes)) else contents
|
|
169
|
+
)
|
|
170
|
+
except json.JSONDecodeError as e:
|
|
171
|
+
raise NotALabwareError("invalid-json", [e]) from e
|
|
172
|
+
|
|
173
|
+
if isinstance(parsed_json, dict):
|
|
174
|
+
try:
|
|
175
|
+
schema_version: object = parsed_json["schemaVersion"]
|
|
176
|
+
except KeyError as e:
|
|
177
|
+
raise NotALabwareError("no-schema-id", [e]) from e
|
|
178
|
+
else:
|
|
179
|
+
raise NotALabwareError("no-schema-id", [])
|
|
177
180
|
|
|
178
181
|
try:
|
|
179
|
-
|
|
182
|
+
# we can type ignore this because we handle the KeyError below
|
|
183
|
+
schema = schemata_by_version[schema_version] # type: ignore[index]
|
|
180
184
|
except KeyError as e:
|
|
181
185
|
raise NotALabwareError("bad-schema-id", [e]) from e
|
|
182
186
|
|
|
183
187
|
try:
|
|
184
|
-
jsonschema.validate(
|
|
188
|
+
jsonschema.validate(parsed_json, schema)
|
|
185
189
|
except jsonschema.ValidationError as e:
|
|
186
190
|
raise NotALabwareError("schema-mismatch", [e]) from e
|
|
187
191
|
|
|
188
192
|
# we can type ignore this because if it passes the jsonschema it has
|
|
189
193
|
# the correct structure
|
|
190
|
-
return
|
|
194
|
+
return parsed_json # type: ignore[return-value]
|
|
191
195
|
|
|
192
196
|
|
|
193
197
|
def _get_labware_definition_from_bundle(
|
|
194
|
-
bundled_labware:
|
|
198
|
+
bundled_labware: Mapping[str, LabwareDefinition],
|
|
195
199
|
load_name: str,
|
|
196
200
|
namespace: Optional[str] = None,
|
|
197
201
|
version: Optional[int] = None,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Convenience re-exports of models that are especially common or important.
|
|
2
|
-
# More detailed sub-models are always available through the underlying
|
|
3
|
-
# submodules.
|
|
4
|
-
#
|
|
5
|
-
# If re-exporting something, its name should still make sense when it's separated
|
|
6
|
-
# from the name of its parent submodule. e.g. re-exporting models.json_protocol.Labware
|
|
7
|
-
# as models.Labware could be confusing.
|
|
8
|
-
|
|
9
|
-
# TODO(mc, 2022-03-11): remove this re-export when it won't break pickling
|
|
10
|
-
# https://opentrons.atlassian.net/browse/RSS-94
|
|
11
|
-
from opentrons_shared_data.labware.labware_definition import (
|
|
12
|
-
LabwareDefinition,
|
|
13
|
-
WellDefinition,
|
|
14
|
-
)
|
|
15
|
-
from .json_protocol import Model as JsonProtocol
|
|
16
|
-
|
|
17
|
-
__all__ = [
|
|
18
|
-
"LabwareDefinition",
|
|
19
|
-
"WellDefinition",
|
|
20
|
-
"JsonProtocol",
|
|
21
|
-
]
|
opentrons/simulate.py
CHANGED
|
@@ -71,7 +71,9 @@ from opentrons.protocols.api_support.deck_type import (
|
|
|
71
71
|
should_load_fixed_trash_labware_for_python_protocol,
|
|
72
72
|
)
|
|
73
73
|
from opentrons.protocols.api_support.types import APIVersion
|
|
74
|
-
from opentrons_shared_data.labware.labware_definition import
|
|
74
|
+
from opentrons_shared_data.labware.labware_definition import (
|
|
75
|
+
labware_definition_type_adapter,
|
|
76
|
+
)
|
|
75
77
|
|
|
76
78
|
from .util import entrypoint_util
|
|
77
79
|
|
|
@@ -829,7 +831,7 @@ def _create_live_context_pe(
|
|
|
829
831
|
# Non-async would use call_soon_threadsafe(), which makes the waiting harder.
|
|
830
832
|
async def add_all_extra_labware() -> None:
|
|
831
833
|
for labware_definition_dict in extra_labware.values():
|
|
832
|
-
labware_definition =
|
|
834
|
+
labware_definition = labware_definition_type_adapter.validate_python(
|
|
833
835
|
labware_definition_dict
|
|
834
836
|
)
|
|
835
837
|
pe.add_labware_definition(labware_definition)
|
opentrons/types.py
CHANGED
|
@@ -88,6 +88,15 @@ LocationLabware = Union[
|
|
|
88
88
|
]
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class MeniscusTrackingTarget(enum.Enum):
|
|
92
|
+
START = "start"
|
|
93
|
+
END = "end"
|
|
94
|
+
DYNAMIC = "dynamic"
|
|
95
|
+
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
return self.name
|
|
98
|
+
|
|
99
|
+
|
|
91
100
|
class Location:
|
|
92
101
|
"""Location(point: Point, labware: Union["Labware", "Well", str, "ModuleGeometry", LabwareLike, None, "ModuleContext"])
|
|
93
102
|
|
|
@@ -129,12 +138,12 @@ class Location:
|
|
|
129
138
|
"ModuleContext",
|
|
130
139
|
],
|
|
131
140
|
*,
|
|
132
|
-
|
|
141
|
+
_meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
|
|
133
142
|
):
|
|
134
143
|
self._point = point
|
|
135
144
|
self._given_labware = labware
|
|
136
145
|
self._labware = LabwareLike(labware)
|
|
137
|
-
self.
|
|
146
|
+
self._meniscus_tracking = _meniscus_tracking
|
|
138
147
|
|
|
139
148
|
# todo(mm, 2021-10-01): Figure out how to get .point and .labware to show up
|
|
140
149
|
# in the rendered docs, and then update the class docstring to use cross-references.
|
|
@@ -148,8 +157,8 @@ class Location:
|
|
|
148
157
|
return self._labware
|
|
149
158
|
|
|
150
159
|
@property
|
|
151
|
-
def
|
|
152
|
-
return self.
|
|
160
|
+
def meniscus_tracking(self) -> Optional[MeniscusTrackingTarget]:
|
|
161
|
+
return self._meniscus_tracking
|
|
153
162
|
|
|
154
163
|
def __iter__(self) -> Iterator[Union[Point, LabwareLike]]:
|
|
155
164
|
"""Iterable interface to support unpacking. Like a tuple.
|
|
@@ -167,7 +176,7 @@ class Location:
|
|
|
167
176
|
isinstance(other, Location)
|
|
168
177
|
and other._point == self._point
|
|
169
178
|
and other._labware == self._labware
|
|
170
|
-
and other.
|
|
179
|
+
and other._meniscus_tracking == self._meniscus_tracking
|
|
171
180
|
)
|
|
172
181
|
|
|
173
182
|
def move(self, point: Point) -> "Location":
|
|
@@ -190,10 +199,14 @@ class Location:
|
|
|
190
199
|
|
|
191
200
|
"""
|
|
192
201
|
|
|
193
|
-
return Location(
|
|
202
|
+
return Location(
|
|
203
|
+
point=self.point + point,
|
|
204
|
+
labware=self._given_labware,
|
|
205
|
+
_meniscus_tracking=self._meniscus_tracking,
|
|
206
|
+
)
|
|
194
207
|
|
|
195
208
|
def __repr__(self) -> str:
|
|
196
|
-
return f"Location(point={repr(self._point)}, labware={self._labware},
|
|
209
|
+
return f"Location(point={repr(self._point)}, labware={self._labware}, meniscus_tracking={self._meniscus_tracking})"
|
|
197
210
|
|
|
198
211
|
|
|
199
212
|
# TODO(mc, 2020-10-22): use MountType implementation for Mount
|
opentrons/util/logging_config.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from logging.config import dictConfig
|
|
3
|
+
from logging.handlers import QueueListener, RotatingFileHandler
|
|
3
4
|
import sys
|
|
4
|
-
from
|
|
5
|
+
from queue import Queue
|
|
5
6
|
|
|
6
7
|
from opentrons.config import CONFIG, ARCHITECTURE, SystemArchitecture
|
|
7
8
|
|
|
@@ -12,11 +13,33 @@ else:
|
|
|
12
13
|
SENSOR_LOG_NAME = "unused"
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
# We want this big enough to smooth over any temporary stalls in journald's ability
|
|
17
|
+
# to consume our records--but bounded, so if we consistently outpace journald for
|
|
18
|
+
# some reason, we don't leak memory or get latency from buffer bloat.
|
|
19
|
+
# 50000 is basically an arbitrary guess.
|
|
20
|
+
_LOG_QUEUE_SIZE = 50000
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
log_queue = Queue[logging.LogRecord](maxsize=_LOG_QUEUE_SIZE)
|
|
24
|
+
"""A buffer through which log records will pass.
|
|
25
|
+
|
|
26
|
+
This is intended to work around problems when our logs are going to journald:
|
|
27
|
+
we think journald can block for a while when it flushes records to the filesystem,
|
|
28
|
+
and the backpressure from that will cause calls like `log.debug()` to block and
|
|
29
|
+
interfere with timing-sensitive hardware control.
|
|
30
|
+
https://github.com/Opentrons/opentrons/issues/18034
|
|
31
|
+
|
|
32
|
+
`log_init()` will configure all the logs that this package knows about to pass through
|
|
33
|
+
this queue. This queue is exposed so consumers of this package (i.e. robot-server)
|
|
34
|
+
can do the same thing with their own logs, which is important to preserve ordering.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _config_for_host(level_value: int) -> None:
|
|
16
39
|
serial_log_filename = CONFIG["serial_log_file"]
|
|
17
40
|
api_log_filename = CONFIG["api_log_file"]
|
|
18
41
|
sensor_log_filename = CONFIG["sensor_log_file"]
|
|
19
|
-
|
|
42
|
+
config = {
|
|
20
43
|
"version": 1,
|
|
21
44
|
"disable_existing_loggers": False,
|
|
22
45
|
"formatters": {
|
|
@@ -90,13 +113,20 @@ def _host_config(level_value: int) -> Dict[str, Any]:
|
|
|
90
113
|
},
|
|
91
114
|
}
|
|
92
115
|
|
|
116
|
+
dictConfig(config)
|
|
93
117
|
|
|
94
|
-
|
|
118
|
+
|
|
119
|
+
def _config_for_robot(level_value: int) -> None:
|
|
95
120
|
# Import systemd.journald here since it is generally unavailble on non
|
|
96
121
|
# linux systems and we probably don't want to use it on linux desktops
|
|
97
122
|
# either
|
|
123
|
+
from systemd.journal import JournalHandler # type: ignore
|
|
124
|
+
|
|
98
125
|
sensor_log_filename = CONFIG["sensor_log_file"]
|
|
99
|
-
|
|
126
|
+
|
|
127
|
+
sensor_log_queue = Queue[logging.LogRecord](maxsize=_LOG_QUEUE_SIZE)
|
|
128
|
+
|
|
129
|
+
config = {
|
|
100
130
|
"version": 1,
|
|
101
131
|
"disable_existing_loggers": False,
|
|
102
132
|
"formatters": {
|
|
@@ -104,36 +134,38 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]:
|
|
|
104
134
|
},
|
|
105
135
|
"handlers": {
|
|
106
136
|
"api": {
|
|
107
|
-
"class": "
|
|
137
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
108
138
|
"level": logging.DEBUG,
|
|
109
139
|
"formatter": "message_only",
|
|
110
|
-
"SYSLOG_IDENTIFIER": "opentrons-api",
|
|
140
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api"},
|
|
141
|
+
"queue": log_queue,
|
|
111
142
|
},
|
|
112
143
|
"serial": {
|
|
113
|
-
"class": "
|
|
144
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
114
145
|
"level": logging.DEBUG,
|
|
115
146
|
"formatter": "message_only",
|
|
116
|
-
"SYSLOG_IDENTIFIER": "opentrons-api-serial",
|
|
147
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api-serial"},
|
|
148
|
+
"queue": log_queue,
|
|
117
149
|
},
|
|
118
150
|
"can_serial": {
|
|
119
|
-
"class": "
|
|
151
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
120
152
|
"level": logging.DEBUG,
|
|
121
153
|
"formatter": "message_only",
|
|
122
|
-
"SYSLOG_IDENTIFIER": "opentrons-api-serial-can",
|
|
154
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api-serial-can"},
|
|
155
|
+
"queue": log_queue,
|
|
123
156
|
},
|
|
124
157
|
"usbbin_serial": {
|
|
125
|
-
"class": "
|
|
158
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
126
159
|
"level": logging.DEBUG,
|
|
127
160
|
"formatter": "message_only",
|
|
128
|
-
"SYSLOG_IDENTIFIER": "opentrons-api-serial-usbbin",
|
|
161
|
+
"extra": {"SYSLOG_IDENTIFIER": "opentrons-api-serial-usbbin"},
|
|
162
|
+
"queue": log_queue,
|
|
129
163
|
},
|
|
130
164
|
"sensor": {
|
|
131
|
-
"class": "
|
|
132
|
-
"formatter": "message_only",
|
|
133
|
-
"filename": sensor_log_filename,
|
|
134
|
-
"maxBytes": 1000000,
|
|
165
|
+
"class": "opentrons.util.logging_queue_handler.CustomQueueHandler",
|
|
135
166
|
"level": logging.DEBUG,
|
|
136
|
-
"
|
|
167
|
+
"formatter": "message_only",
|
|
168
|
+
"queue": sensor_log_queue,
|
|
137
169
|
},
|
|
138
170
|
},
|
|
139
171
|
"loggers": {
|
|
@@ -169,12 +201,47 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]:
|
|
|
169
201
|
},
|
|
170
202
|
}
|
|
171
203
|
|
|
204
|
+
# Start draining from the queue and sending messages to journald.
|
|
205
|
+
# Then, stash the queue listener in a global variable so it doesn't get garbage-collected.
|
|
206
|
+
# I don't know if we actually need to do this, but let's not find out the hard way.
|
|
207
|
+
global _queue_listener
|
|
208
|
+
if _queue_listener is not None:
|
|
209
|
+
# In case this log init function was called multiple times for some reason.
|
|
210
|
+
_queue_listener.stop()
|
|
211
|
+
_queue_listener = QueueListener(log_queue, JournalHandler())
|
|
212
|
+
_queue_listener.start()
|
|
213
|
+
|
|
214
|
+
# Sensor logs are a special one-off thing that go to their own file instead of journald.
|
|
215
|
+
# We apply the same QueueListener performance workaround for basically the same reasons.
|
|
216
|
+
sensor_rotating_file_handler = RotatingFileHandler(
|
|
217
|
+
filename=sensor_log_filename, maxBytes=1000000, backupCount=3
|
|
218
|
+
)
|
|
219
|
+
sensor_rotating_file_handler.setLevel(logging.DEBUG)
|
|
220
|
+
sensor_rotating_file_handler.setFormatter(logging.Formatter(fmt="%(message)s"))
|
|
221
|
+
global _sensor_queue_listener
|
|
222
|
+
if _sensor_queue_listener is not None:
|
|
223
|
+
_sensor_queue_listener.stop()
|
|
224
|
+
_sensor_queue_listener = QueueListener(
|
|
225
|
+
sensor_log_queue, sensor_rotating_file_handler
|
|
226
|
+
)
|
|
227
|
+
_sensor_queue_listener.start()
|
|
228
|
+
|
|
229
|
+
dictConfig(config)
|
|
172
230
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
231
|
+
# TODO(2025-04-15): We need some kind of log_deinit() function to call
|
|
232
|
+
# queue_listener.stop() before the process ends. Not doing that means we're
|
|
233
|
+
# dropping some records when the process shuts down.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
_queue_listener: QueueListener | None = None
|
|
237
|
+
_sensor_queue_listener: QueueListener | None = None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _config(arch: SystemArchitecture, level_value: int) -> None:
|
|
241
|
+
{
|
|
242
|
+
SystemArchitecture.YOCTO: _config_for_robot,
|
|
243
|
+
SystemArchitecture.BUILDROOT: _config_for_robot,
|
|
244
|
+
SystemArchitecture.HOST: _config_for_host,
|
|
178
245
|
}[arch](level_value)
|
|
179
246
|
|
|
180
247
|
|
|
@@ -191,6 +258,8 @@ def log_init(level_name: str) -> None:
|
|
|
191
258
|
f"Defaulting to {fallback_log_level}\n"
|
|
192
259
|
)
|
|
193
260
|
ot_log_level = fallback_log_level
|
|
261
|
+
|
|
262
|
+
# todo(mm, 2025-04-14): Use logging.getLevelNamesMapping() when we have Python >=3.11.
|
|
194
263
|
level_value = logging._nameToLevel[ot_log_level]
|
|
195
|
-
|
|
196
|
-
|
|
264
|
+
|
|
265
|
+
_config(ARCHITECTURE, level_value)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import logging.handlers
|
|
5
|
+
import logging
|
|
6
|
+
from queue import Queue
|
|
7
|
+
from typing import cast
|
|
8
|
+
from typing_extensions import override
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomQueueHandler(logging.handlers.QueueHandler):
|
|
12
|
+
"""A logging.QueueHandler with some customizations.
|
|
13
|
+
|
|
14
|
+
- Allow adding `extra` data to handled log records.
|
|
15
|
+
|
|
16
|
+
- Simplify and optimize for single-process use.
|
|
17
|
+
|
|
18
|
+
- If a new message comes in but the queue is full, block until it has room.
|
|
19
|
+
(The default QueueHandler drops records in a way we probably wouldn't notice.)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self, *, queue: Queue[logging.LogRecord], extra: dict[str, object] | None = None
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Construct the handler.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
queue: When this handler receives a log record, it will insert the message
|
|
29
|
+
into this queue.
|
|
30
|
+
extra: Extra data to attach to each log record, to be interpreted by
|
|
31
|
+
whatever handler is on the consuming side of the queue. e.g. if that's
|
|
32
|
+
`systemd.journal.JournalHandler`, you could add a "SYSLOG_IDENTIFIER"
|
|
33
|
+
key here. This corresponds to the `extra` arg of `Logger.debug()`.
|
|
34
|
+
"""
|
|
35
|
+
super().__init__(queue=queue)
|
|
36
|
+
|
|
37
|
+
# Double underscore because we're subclassing external code so we should try to
|
|
38
|
+
# avoid collisions with its attributes.
|
|
39
|
+
self.__extra = extra
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def prepare(self, record: logging.LogRecord) -> logging.LogRecord:
|
|
43
|
+
"""Called internally by the superclass before enqueueing a record."""
|
|
44
|
+
if self.__extra is not None:
|
|
45
|
+
# This looks questionable, but updating __dict__ is the documented behavior
|
|
46
|
+
# of `Logger.debug(msg, extra=...)`.
|
|
47
|
+
record.__dict__.update(self.__extra)
|
|
48
|
+
|
|
49
|
+
# We intentionally do *not* call `super().prepare(record)`. It's documented to
|
|
50
|
+
# muck with the data in the LogRecord, apparently as part of supporting
|
|
51
|
+
# inter-process use. Since we don't need that, we can preserve the original
|
|
52
|
+
# data and also save some compute time.
|
|
53
|
+
return record
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
def enqueue(self, record: logging.LogRecord) -> None:
|
|
57
|
+
"""Called internally by the superclass to enqueue a record."""
|
|
58
|
+
# This cast is safe because we constrain the type of `self.queue`
|
|
59
|
+
# in our `__init__()` and nobody should mutate it after-the-fact, in practice.
|
|
60
|
+
queue = cast(Queue[logging.LogRecord], self.queue)
|
|
61
|
+
queue.put(record)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opentrons
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.4.0
|
|
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.
|
|
24
|
+
Requires-Dist: opentrons-shared-data (==8.4.0)
|
|
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.
|
|
38
|
+
Requires-Dist: opentrons-hardware[flex] (==8.4.0) ; extra == 'flex-hardware'
|
|
39
39
|
Provides-Extra: ot2-hardware
|
|
40
|
-
Requires-Dist: opentrons-hardware (==8.
|
|
40
|
+
Requires-Dist: opentrons-hardware (==8.4.0) ; extra == 'ot2-hardware'
|
|
41
41
|
|
|
42
42
|
.. _Full API Documentation: http://docs.opentrons.com
|
|
43
43
|
|