opentrons 8.6.0a11__py3-none-any.whl → 8.7.0__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/_version.py +2 -2
- opentrons/cli/analyze.py +58 -2
- opentrons/drivers/asyncio/communication/serial_connection.py +8 -5
- opentrons/drivers/flex_stacker/driver.py +6 -1
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +25 -13
- opentrons/hardware_control/backends/ot3simulator.py +2 -1
- opentrons/hardware_control/dev_types.py +3 -1
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +1 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1 -0
- opentrons/hardware_control/ot3api.py +3 -1
- opentrons/hardware_control/protocols/gripper_controller.py +1 -0
- opentrons/protocol_api/core/engine/_default_liquid_class_versions.py +56 -0
- opentrons/protocol_api/core/engine/instrument.py +143 -18
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +77 -17
- opentrons/protocol_api/core/engine/protocol.py +53 -7
- opentrons/protocol_api/core/engine/transfer_components_executor.py +36 -20
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -1
- opentrons/protocol_api/core/protocol.py +1 -1
- opentrons/protocol_api/labware.py +36 -2
- opentrons/protocol_api/module_contexts.py +146 -14
- opentrons/protocol_api/protocol_context.py +162 -12
- opentrons/protocol_api/validation.py +4 -0
- opentrons/protocol_engine/commands/command_unions.py +2 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +13 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +20 -2
- opentrons/protocol_engine/execution/labware_movement.py +14 -12
- opentrons/protocol_engine/resources/pipette_data_provider.py +3 -0
- opentrons/protocol_engine/state/geometry.py +33 -5
- opentrons/protocol_engine/state/labware.py +66 -0
- opentrons/protocol_engine/state/modules.py +6 -0
- opentrons/protocol_engine/state/pipettes.py +12 -3
- opentrons/protocol_engine/types/__init__.py +2 -0
- opentrons/protocol_engine/types/labware.py +9 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- {opentrons-8.6.0a11.dist-info → opentrons-8.7.0.dist-info}/METADATA +4 -4
- {opentrons-8.6.0a11.dist-info → opentrons-8.7.0.dist-info}/RECORD +40 -39
- {opentrons-8.6.0a11.dist-info → opentrons-8.7.0.dist-info}/WHEEL +0 -0
- {opentrons-8.6.0a11.dist-info → opentrons-8.7.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.6.0a11.dist-info → opentrons-8.7.0.dist-info}/licenses/LICENSE +0 -0
opentrons/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '8.
|
|
32
|
-
__version_tuple__ = version_tuple = (8,
|
|
31
|
+
__version__ = version = '8.7.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (8, 7, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
opentrons/cli/analyze.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Opentrons analyze CLI."""
|
|
2
|
+
|
|
2
3
|
import click
|
|
3
4
|
|
|
4
5
|
from anyio import run
|
|
@@ -24,7 +25,9 @@ from typing import (
|
|
|
24
25
|
import logging
|
|
25
26
|
import sys
|
|
26
27
|
import json
|
|
28
|
+
import gc
|
|
27
29
|
|
|
30
|
+
from opentrons.protocol_engine import ProtocolEngine
|
|
28
31
|
from opentrons.protocol_engine.types import (
|
|
29
32
|
RunTimeParameter,
|
|
30
33
|
CSVRuntimeParamPaths,
|
|
@@ -94,6 +97,18 @@ class _Output:
|
|
|
94
97
|
help="Return analysis results as JSON, formatted for human eyes. Specify --human-json-output=- to use stdout, but be aware that Python protocols may contain print() which will make the output JSON invalid.",
|
|
95
98
|
type=click.File(mode="wb"),
|
|
96
99
|
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"--leaks",
|
|
102
|
+
help="Fail (via exit code) if the analysis engine has not been garbage collected after analysis is complete.",
|
|
103
|
+
is_flag=True,
|
|
104
|
+
default=False,
|
|
105
|
+
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--leaks-debug",
|
|
108
|
+
help="Drop into a PDB shell if a leak is detected",
|
|
109
|
+
is_flag=True,
|
|
110
|
+
default=False,
|
|
111
|
+
)
|
|
97
112
|
@click.option(
|
|
98
113
|
"--check",
|
|
99
114
|
help="Fail (via exit code) if the protocol had an error. If not specified, always succeed.",
|
|
@@ -133,6 +148,8 @@ def analyze(
|
|
|
133
148
|
log_output: str,
|
|
134
149
|
log_level: str,
|
|
135
150
|
check: bool,
|
|
151
|
+
leaks: bool,
|
|
152
|
+
leaks_debug: bool,
|
|
136
153
|
) -> int:
|
|
137
154
|
"""Analyze a protocol.
|
|
138
155
|
|
|
@@ -147,7 +164,18 @@ def analyze(
|
|
|
147
164
|
|
|
148
165
|
try:
|
|
149
166
|
with _capture_logs(log_output, log_level):
|
|
150
|
-
sys.exit(
|
|
167
|
+
sys.exit(
|
|
168
|
+
run(
|
|
169
|
+
_analyze,
|
|
170
|
+
files,
|
|
171
|
+
rtp_values,
|
|
172
|
+
rtp_files,
|
|
173
|
+
outputs,
|
|
174
|
+
check,
|
|
175
|
+
leaks or leaks_debug,
|
|
176
|
+
leaks_debug,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
151
179
|
except click.ClickException:
|
|
152
180
|
raise
|
|
153
181
|
except Exception as e:
|
|
@@ -344,12 +372,14 @@ async def _do_analyze(
|
|
|
344
372
|
return await orchestrator.run(deck_configuration=[])
|
|
345
373
|
|
|
346
374
|
|
|
347
|
-
async def _analyze(
|
|
375
|
+
async def _analyze( # noqa: C901
|
|
348
376
|
files_and_dirs: Sequence[Path],
|
|
349
377
|
rtp_values: str,
|
|
350
378
|
rtp_files: str,
|
|
351
379
|
outputs: Sequence[_Output],
|
|
352
380
|
check: bool,
|
|
381
|
+
fail_on_leak: bool,
|
|
382
|
+
debug_on_leak: bool,
|
|
353
383
|
) -> int:
|
|
354
384
|
input_files = _get_input_files(files_and_dirs)
|
|
355
385
|
parsed_rtp_values = _get_runtime_parameter_values(rtp_values)
|
|
@@ -366,6 +396,32 @@ async def _analyze(
|
|
|
366
396
|
analysis = await _do_analyze(protocol_source, parsed_rtp_values, rtp_paths)
|
|
367
397
|
return_code = _get_return_code(analysis)
|
|
368
398
|
|
|
399
|
+
# This ugly code checks to see if an engine remains past garbage collection
|
|
400
|
+
# after analysis is complete.
|
|
401
|
+
# It should be here and open coded to make it a little easier to present
|
|
402
|
+
# the debug option.
|
|
403
|
+
if fail_on_leak or debug_on_leak:
|
|
404
|
+
gc.collect()
|
|
405
|
+
leaked_engine = next(
|
|
406
|
+
(obj for obj in gc.get_objects() if isinstance(obj, ProtocolEngine)), None
|
|
407
|
+
)
|
|
408
|
+
if leaked_engine:
|
|
409
|
+
if fail_on_leak:
|
|
410
|
+
print(
|
|
411
|
+
"A ProtocolEngine instance exists even after garbage collection; "
|
|
412
|
+
"some thing (likely in the protocol) has caused it to be leaked, "
|
|
413
|
+
"likely by reference to the engine or something that refers to the "
|
|
414
|
+
"engine after the run function ends.",
|
|
415
|
+
file=sys.stderr,
|
|
416
|
+
)
|
|
417
|
+
return_code = -2
|
|
418
|
+
if debug_on_leak:
|
|
419
|
+
print(
|
|
420
|
+
"You are now in an interactive PDB (https://docs.python.org/3.10/library/pdb.html) "
|
|
421
|
+
"session; the leaked engine is bound to the variable leaked_engine."
|
|
422
|
+
)
|
|
423
|
+
breakpoint()
|
|
424
|
+
|
|
369
425
|
if not outputs:
|
|
370
426
|
return return_code
|
|
371
427
|
|
|
@@ -462,7 +462,10 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
462
462
|
self._async_error_ack = async_error_ack.lower()
|
|
463
463
|
|
|
464
464
|
async def send_command(
|
|
465
|
-
self,
|
|
465
|
+
self,
|
|
466
|
+
command: CommandBuilder,
|
|
467
|
+
retries: int | None = None,
|
|
468
|
+
timeout: float | None = None,
|
|
466
469
|
) -> str:
|
|
467
470
|
"""
|
|
468
471
|
Send a command and return the response.
|
|
@@ -478,12 +481,12 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
478
481
|
"""
|
|
479
482
|
return await self.send_data(
|
|
480
483
|
data=command.build(),
|
|
481
|
-
retries=retries
|
|
484
|
+
retries=retries if retries is not None else self._number_of_retries,
|
|
482
485
|
timeout=timeout,
|
|
483
486
|
)
|
|
484
487
|
|
|
485
488
|
async def send_data(
|
|
486
|
-
self, data: str, retries: int =
|
|
489
|
+
self, data: str, retries: int | None = None, timeout: float | None = None
|
|
487
490
|
) -> str:
|
|
488
491
|
"""
|
|
489
492
|
Send data and return the response.
|
|
@@ -501,7 +504,8 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
501
504
|
"timeout", timeout
|
|
502
505
|
):
|
|
503
506
|
return await self._send_data(
|
|
504
|
-
data=data,
|
|
507
|
+
data=data,
|
|
508
|
+
retries=retries if retries is not None else self._number_of_retries,
|
|
505
509
|
)
|
|
506
510
|
|
|
507
511
|
async def _send_data(self, data: str, retries: int = 0) -> str:
|
|
@@ -517,7 +521,6 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
517
521
|
Raises: SerialException
|
|
518
522
|
"""
|
|
519
523
|
data_encode = data.encode()
|
|
520
|
-
retries = retries or self._number_of_retries
|
|
521
524
|
|
|
522
525
|
for retry in range(retries + 1):
|
|
523
526
|
log.debug(f"{self._name}: Write -> {data_encode!r}")
|
|
@@ -461,7 +461,12 @@ class FlexStackerDriver(AbstractFlexStackerDriver):
|
|
|
461
461
|
command = GCODE.GET_TOF_MEASUREMENT.build_command().add_element(sensor.name)
|
|
462
462
|
if resend:
|
|
463
463
|
command.add_element("R")
|
|
464
|
-
|
|
464
|
+
|
|
465
|
+
# Note: We DONT want to auto resend the request if it fails, because the
|
|
466
|
+
# firmware will send the next frame id instead of the current one missed.
|
|
467
|
+
# So lets set `retries=0` so we only send the frame once and we can
|
|
468
|
+
# use the retry mechanism of the `get_tof_histogram` method instead.
|
|
469
|
+
resp = await self._connection.send_command(command, retries=0)
|
|
465
470
|
return self.parse_get_tof_measurement(resp)
|
|
466
471
|
|
|
467
472
|
async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:
|
|
@@ -686,9 +686,9 @@ class OT3Controller(FlexBackend):
|
|
|
686
686
|
return (
|
|
687
687
|
MoveGroupRunner(
|
|
688
688
|
move_groups=[move_group],
|
|
689
|
-
ignore_stalls=
|
|
690
|
-
|
|
691
|
-
|
|
689
|
+
ignore_stalls=(
|
|
690
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
691
|
+
),
|
|
692
692
|
),
|
|
693
693
|
False,
|
|
694
694
|
)
|
|
@@ -712,9 +712,9 @@ class OT3Controller(FlexBackend):
|
|
|
712
712
|
return (
|
|
713
713
|
MoveGroupRunner(
|
|
714
714
|
move_groups=[tip_motor_move_group],
|
|
715
|
-
ignore_stalls=
|
|
716
|
-
|
|
717
|
-
|
|
715
|
+
ignore_stalls=(
|
|
716
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
717
|
+
),
|
|
718
718
|
),
|
|
719
719
|
True,
|
|
720
720
|
)
|
|
@@ -939,9 +939,9 @@ class OT3Controller(FlexBackend):
|
|
|
939
939
|
|
|
940
940
|
runner = MoveGroupRunner(
|
|
941
941
|
move_groups=[move_group],
|
|
942
|
-
ignore_stalls=
|
|
943
|
-
|
|
944
|
-
|
|
942
|
+
ignore_stalls=(
|
|
943
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
944
|
+
),
|
|
945
945
|
)
|
|
946
946
|
try:
|
|
947
947
|
positions = await runner.run(can_messenger=self._messenger)
|
|
@@ -976,9 +976,9 @@ class OT3Controller(FlexBackend):
|
|
|
976
976
|
move_group = self._build_tip_action_group(origin, targets)
|
|
977
977
|
runner = MoveGroupRunner(
|
|
978
978
|
move_groups=[move_group],
|
|
979
|
-
ignore_stalls=
|
|
980
|
-
|
|
981
|
-
|
|
979
|
+
ignore_stalls=(
|
|
980
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
981
|
+
),
|
|
982
982
|
)
|
|
983
983
|
try:
|
|
984
984
|
positions = await runner.run(can_messenger=self._messenger)
|
|
@@ -1763,6 +1763,7 @@ class OT3Controller(FlexBackend):
|
|
|
1763
1763
|
max_allowed_grip_error: float,
|
|
1764
1764
|
hard_limit_lower: float,
|
|
1765
1765
|
hard_limit_upper: float,
|
|
1766
|
+
disable_geometry_grip_check: bool = False,
|
|
1766
1767
|
) -> None:
|
|
1767
1768
|
"""
|
|
1768
1769
|
Check if the gripper is at the expected location.
|
|
@@ -1777,7 +1778,16 @@ class OT3Controller(FlexBackend):
|
|
|
1777
1778
|
expected_grip_width + grip_width_uncertainty_wider
|
|
1778
1779
|
)
|
|
1779
1780
|
current_gripper_position = jaw_width
|
|
1780
|
-
|
|
1781
|
+
log.info(
|
|
1782
|
+
f"Checking gripper position: current {jaw_width}; max error {max_allowed_grip_error}; hard limits {hard_limit_lower}, {hard_limit_upper}; expected {expected_gripper_position_min}, {expected_grip_width}, {expected_gripper_position_max}; uncertainty {grip_width_uncertainty_narrower}, {grip_width_uncertainty_wider}"
|
|
1783
|
+
)
|
|
1784
|
+
if (
|
|
1785
|
+
isclose(current_gripper_position, hard_limit_lower)
|
|
1786
|
+
# this odd check handles internal backlash that can lead the position to read as if
|
|
1787
|
+
# the gripper has overshot its lower bound; this is physically impossible and an
|
|
1788
|
+
# artifact of the gearing, so it always indicates a hard stop
|
|
1789
|
+
or current_gripper_position < hard_limit_lower
|
|
1790
|
+
):
|
|
1781
1791
|
raise FailedGripperPickupError(
|
|
1782
1792
|
message="Failed to grip: jaws all the way closed",
|
|
1783
1793
|
details={
|
|
@@ -1796,6 +1806,7 @@ class OT3Controller(FlexBackend):
|
|
|
1796
1806
|
if (
|
|
1797
1807
|
current_gripper_position - expected_gripper_position_min
|
|
1798
1808
|
< -max_allowed_grip_error
|
|
1809
|
+
and not disable_geometry_grip_check
|
|
1799
1810
|
):
|
|
1800
1811
|
raise FailedGripperPickupError(
|
|
1801
1812
|
message="Failed to grip: jaws closed too far",
|
|
@@ -1809,6 +1820,7 @@ class OT3Controller(FlexBackend):
|
|
|
1809
1820
|
if (
|
|
1810
1821
|
current_gripper_position - expected_gripper_position_max
|
|
1811
1822
|
> max_allowed_grip_error
|
|
1823
|
+
and not disable_geometry_grip_check
|
|
1812
1824
|
):
|
|
1813
1825
|
raise FailedGripperPickupError(
|
|
1814
1826
|
message="Failed to grip: jaws could not close far enough",
|
|
@@ -781,7 +781,7 @@ class OT3Simulator(FlexBackend):
|
|
|
781
781
|
next_fw_version=1,
|
|
782
782
|
fw_update_needed=False,
|
|
783
783
|
current_fw_sha="simulated",
|
|
784
|
-
pcba_revision="A1",
|
|
784
|
+
pcba_revision="A1.0",
|
|
785
785
|
update_state=None,
|
|
786
786
|
)
|
|
787
787
|
for axis in self._present_axes
|
|
@@ -848,6 +848,7 @@ class OT3Simulator(FlexBackend):
|
|
|
848
848
|
max_allowed_grip_error: float,
|
|
849
849
|
hard_limit_lower: float,
|
|
850
850
|
hard_limit_upper: float,
|
|
851
|
+
disable_geometry_grip_check: bool = False,
|
|
851
852
|
) -> None:
|
|
852
853
|
# This is a (pretty bad) simulation of the gripper actually gripping something,
|
|
853
854
|
# but it should work.
|
|
@@ -14,8 +14,9 @@ from opentrons_shared_data.pipette.types import (
|
|
|
14
14
|
PipetteModel,
|
|
15
15
|
PipetteName,
|
|
16
16
|
ChannelCount,
|
|
17
|
+
PipetteTipType,
|
|
18
|
+
LiquidClasses,
|
|
17
19
|
)
|
|
18
|
-
from opentrons_shared_data.pipette.types import PipetteTipType
|
|
19
20
|
from opentrons_shared_data.pipette.pipette_definition import (
|
|
20
21
|
PipetteConfigurations,
|
|
21
22
|
SupportedTipsDefinition,
|
|
@@ -104,6 +105,7 @@ class PipetteDict(InstrumentDict):
|
|
|
104
105
|
plunger_positions: Dict[str, float]
|
|
105
106
|
shaft_ul_per_mm: float
|
|
106
107
|
available_sensors: AvailableSensorDefinition
|
|
108
|
+
volume_mode: LiquidClasses # LiquidClasses refer to volume mode in this context
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
class PipetteStateDict(TypedDict):
|
|
@@ -267,6 +267,7 @@ class PipetteHandlerProvider(Generic[MountType]):
|
|
|
267
267
|
"drop_tip": instr.plunger_positions.drop_tip,
|
|
268
268
|
}
|
|
269
269
|
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
|
|
270
|
+
result["volume_mode"] = instr.liquid_class_name
|
|
270
271
|
return cast(PipetteDict, result)
|
|
271
272
|
|
|
272
273
|
@property
|
|
@@ -294,6 +294,7 @@ class OT3PipetteHandler:
|
|
|
294
294
|
}
|
|
295
295
|
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
|
|
296
296
|
result["available_sensors"] = instr.config.available_sensors
|
|
297
|
+
result["volume_mode"] = instr.liquid_class_name
|
|
297
298
|
return cast(PipetteDict, result)
|
|
298
299
|
|
|
299
300
|
@property
|
|
@@ -1462,6 +1462,7 @@ class OT3API(
|
|
|
1462
1462
|
expected_grip_width: float,
|
|
1463
1463
|
grip_width_uncertainty_wider: float,
|
|
1464
1464
|
grip_width_uncertainty_narrower: float,
|
|
1465
|
+
disable_geometry_grip_check: bool = False,
|
|
1465
1466
|
) -> None:
|
|
1466
1467
|
"""Ensure that a gripper pickup succeeded.
|
|
1467
1468
|
|
|
@@ -1480,8 +1481,9 @@ class OT3API(
|
|
|
1480
1481
|
grip_width_uncertainty_narrower,
|
|
1481
1482
|
gripper.jaw_width,
|
|
1482
1483
|
gripper.max_allowed_grip_error,
|
|
1483
|
-
gripper.max_jaw_width,
|
|
1484
1484
|
gripper.min_jaw_width,
|
|
1485
|
+
gripper.max_jaw_width,
|
|
1486
|
+
disable_geometry_grip_check,
|
|
1485
1487
|
)
|
|
1486
1488
|
|
|
1487
1489
|
def gripper_jaw_can_home(self) -> bool:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""The versions of standard liquid classes that the Protocol API should load by default."""
|
|
2
|
+
|
|
3
|
+
from typing import TypeAlias
|
|
4
|
+
from opentrons.protocols.api_support.types import APIVersion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DefaultLiquidClassVersions: TypeAlias = dict[APIVersion, dict[str, int]]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# This:
|
|
11
|
+
#
|
|
12
|
+
# {
|
|
13
|
+
# APIVersion(2, 100): {
|
|
14
|
+
# "foo_liquid": 3,
|
|
15
|
+
# },
|
|
16
|
+
# APIVersion(2, 105): {
|
|
17
|
+
# "foo_liquid": 7
|
|
18
|
+
# }
|
|
19
|
+
# }
|
|
20
|
+
#
|
|
21
|
+
# Means this:
|
|
22
|
+
#
|
|
23
|
+
# apiLevels name Default liquid class version
|
|
24
|
+
# ---------------------------------------------------------------
|
|
25
|
+
# <2.100 foo_liquid 1
|
|
26
|
+
# >=2.100,<2.105 foo_liquid 3
|
|
27
|
+
# >=2.105 foo_liquid 7
|
|
28
|
+
# [any] [anything else] 1
|
|
29
|
+
DEFAULT_LIQUID_CLASS_VERSIONS: DefaultLiquidClassVersions = {
|
|
30
|
+
APIVersion(2, 26): {
|
|
31
|
+
"ethanol_80": 2,
|
|
32
|
+
"glycerol_50": 2,
|
|
33
|
+
"water": 2,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_liquid_class_version(
|
|
39
|
+
api_version: APIVersion,
|
|
40
|
+
liquid_class_name: str,
|
|
41
|
+
) -> int:
|
|
42
|
+
"""Return what version of a liquid class the Protocol API should load by default."""
|
|
43
|
+
default_lc_versions_newest_to_oldest = sorted(
|
|
44
|
+
DEFAULT_LIQUID_CLASS_VERSIONS.items(), key=lambda kv: kv[0], reverse=True
|
|
45
|
+
)
|
|
46
|
+
for (
|
|
47
|
+
breakpoint_api_version,
|
|
48
|
+
breakpoint_liquid_class_versions,
|
|
49
|
+
) in default_lc_versions_newest_to_oldest:
|
|
50
|
+
if (
|
|
51
|
+
api_version >= breakpoint_api_version
|
|
52
|
+
and liquid_class_name in breakpoint_liquid_class_versions
|
|
53
|
+
):
|
|
54
|
+
return breakpoint_liquid_class_versions[liquid_class_name]
|
|
55
|
+
|
|
56
|
+
return 1
|