From cd2d67f01deb38f7080d9ba66f5f99b9f93f8fcb Mon Sep 17 00:00:00 2001 From: hazlamshamin Date: Thu, 3 Sep 2026 19:11:42 +0800 Subject: [PATCH] Compare OT-2 API versions numerically Robot server versions can have multi-digit major components. A text comparison treats 26.6.0 as older than 7.1.0. Fixed-trash disposal then enters the tip-rack path and fails. Compare numeric components and cover the threshold and the current robot version. --- .../liquid_handling/backends/opentrons_backend.py | 9 ++++++++- .../backends/opentrons_backend_tests.py | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pylabrobot/legacy/liquid_handling/backends/opentrons_backend.py b/pylabrobot/legacy/liquid_handling/backends/opentrons_backend.py index bff19ae91c4..cc40ac31b90 100644 --- a/pylabrobot/legacy/liquid_handling/backends/opentrons_backend.py +++ b/pylabrobot/legacy/liquid_handling/backends/opentrons_backend.py @@ -47,6 +47,13 @@ logger = logging.getLogger(__name__) +def _version_is_at_least(version: str, minimum: str) -> bool: + """Compare versions that contain dot-separated integers.""" + return tuple(int(component) for component in version.split(".")) >= tuple( + int(component) for component in minimum.split(".") + ) + + class _IOLogger: """Transparent proxy over the ``ot_api`` module that logs every call at ``LOG_LEVEL_IO``. @@ -378,7 +385,7 @@ async def drop_tips(self, ops: List[Drop], use_channels: List[int]): op = ops[0] use_fixed_trash = ( - cast(str, self.ot_api_version) >= _OT_DECK_IS_ADDRESSABLE_AREA_VERSION + _version_is_at_least(cast(str, self.ot_api_version), _OT_DECK_IS_ADDRESSABLE_AREA_VERSION) and op.resource.name == "trash" ) if use_fixed_trash: diff --git a/pylabrobot/legacy/liquid_handling/backends/opentrons_backend_tests.py b/pylabrobot/legacy/liquid_handling/backends/opentrons_backend_tests.py index 7f753803de8..6b26dbfa6ba 100644 --- a/pylabrobot/legacy/liquid_handling/backends/opentrons_backend_tests.py +++ b/pylabrobot/legacy/liquid_handling/backends/opentrons_backend_tests.py @@ -8,6 +8,7 @@ from pylabrobot.legacy.liquid_handling import LiquidHandler from pylabrobot.legacy.liquid_handling.backends.opentrons_backend import ( _OT_DECK_IS_ADDRESSABLE_AREA_VERSION, + _version_is_at_least, OpentronsOT2Backend, ) from pylabrobot.legacy.liquid_handling.errors import NoChannelError @@ -36,6 +37,14 @@ def _mock_health_get(): } +@pytest.mark.parametrize( + ("version", "expected"), + (("7.0.1", False), ("7.1.0", True), ("26.6.0", True)), +) +def test_version_is_at_least(version: str, expected: bool) -> None: + assert _version_is_at_least(version, _OT_DECK_IS_ADDRESSABLE_AREA_VERSION) is expected + + class OpentronsBackendSetupTests(unittest.IsolatedAsyncioTestCase): """Tests for setup and stop""" @@ -233,7 +242,7 @@ async def test_tip_drop_to_trash_uses_addressable_area( area (move_to_addressable_area_for_drop_tip + drop_tip_in_place), not drop_tip.""" mock_define.side_effect = _mock_define mock_add.side_effect = _mock_add - self.backend.ot_api_version = _OT_DECK_IS_ADDRESSABLE_AREA_VERSION + self.backend.ot_api_version = "26.6.0" await self.lh.pick_up_tips(self.tip_rack["A1"]) await self.lh.discard_tips()