Skip to content

Commit 6b2728a

Browse files
committed
add Machine class
1 parent 0b468a4 commit 6b2728a

File tree

8 files changed

+79
-79
lines changed

8 files changed

+79
-79
lines changed

pylabrobot/liquid_handling/backends/backend.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from abc import ABCMeta, abstractmethod
44
from typing import List, Type, Optional
55

6+
from pylabrobot.machine import MachineBackend
67
from pylabrobot.resources import Resource
78
from pylabrobot.liquid_handling.standard import (
89
Pickup,
@@ -17,7 +18,7 @@
1718
)
1819

1920

20-
class LiquidHandlerBackend(object, metaclass=ABCMeta):
21+
class LiquidHandlerBackend(MachineBackend, metaclass=ABCMeta):
2122
"""
2223
Abstract base class for liquid handling robot backends.
2324
@@ -28,15 +29,6 @@ class LiquidHandlerBackend(object, metaclass=ABCMeta):
2829
setup_finished: Whether the backend has been set up.
2930
"""
3031

31-
def __init__(self):
32-
self.setup_finished = False
33-
34-
async def setup(self):
35-
self.setup_finished = True
36-
37-
async def stop(self):
38-
self.setup_finished = False
39-
4032
async def assigned_resource_callback(self, resource: Resource):
4133
""" Called when a new resource was assigned to the robot.
4234

pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ def __init__(self):
180180
self.commands = []
181181

182182
async def setup(self) -> None:
183-
self.setup_finished = True
184183
self._num_channels = 8
185184
self.iswap_installed = True
186185
self.core96_head_installed = True

pylabrobot/liquid_handling/backends/saver_backend.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ async def setup(self):
1919
await super().setup()
2020
self.commands_received = []
2121

22+
async def stop(self):
23+
await super().stop()
24+
2225
async def send_command(self, command: str, data: Dict[str, Any]):
2326
self.commands_received.append({"command": command, "data": data})
2427

pylabrobot/liquid_handling/backends/serializing_backend.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ async def send_command(
4545

4646
async def setup(self):
4747
await self.send_command(command="setup")
48+
await super().setup()
4849

4950
async def stop(self):
5051
await self.send_command(command="stop")
52+
await super().stop()
5153

5254
async def assigned_resource_callback(self, resource: Resource):
5355
await self.send_command(command="resource_assigned", data={"resource": resource.serialize(),
@@ -172,10 +174,7 @@ class Command(TypedDict):
172174

173175
async def setup(self):
174176
self.sent_commands: List[SerializingSavingBackend.Command] = []
175-
self.setup_finished = True
176-
177-
async def stop(self):
178-
self.setup_finished = False
177+
await super().setup()
179178

180179
async def send_command(self, command: str, data: Optional[Dict[str, Any]] = None):
181180
self.sent_commands.append({"command": command, "data": data})

pylabrobot/liquid_handling/liquid_handler.py

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import asyncio
6-
import functools
76
import inspect
87
import json
98
import logging
@@ -13,6 +12,7 @@
1312
from typing import Any, Callable, Dict, Union, Optional, List, Sequence, Set
1413
import warnings
1514

15+
from pylabrobot.machine import MachineFrontend, need_setup_finished
1616
from pylabrobot.liquid_handling.strictness import Strictness, get_strictness
1717
from pylabrobot.plate_reading import PlateReader
1818
from pylabrobot.resources import (
@@ -52,33 +52,13 @@
5252
logger = logging.getLogger("pylabrobot")
5353

5454

55-
def need_setup_finished(func: Callable): # pylint: disable=no-self-argument
56-
""" Decorator for methods that require the liquid handler to be set up.
57-
58-
Checked by verifying `self.setup_finished` is `True`.
59-
60-
Raises:
61-
RuntimeError: If the liquid handler is not set up.
62-
"""
63-
64-
@functools.wraps(func)
65-
async def wrapper(self, *args, **kwargs):
66-
if not self.setup_finished:
67-
raise RuntimeError("The setup has not finished. See `LiquidHandler.setup`.")
68-
await func(self, *args, **kwargs) # pylint: disable=not-callable
69-
return wrapper
70-
71-
72-
class LiquidHandler:
55+
class LiquidHandler(MachineFrontend):
7356
"""
7457
Front end for liquid handlers.
7558
7659
This class is the front end for liquid handlers; it provides a high-level interface for
7760
interacting with liquid handlers. In the background, this class uses the low-level backend (
7861
defined in `pyhamilton.liquid_handling.backends`) to communicate with the liquid handler.
79-
80-
Attributes:
81-
setup_finished: Whether the liquid handler has been setup.
8262
"""
8363

8464
def __init__(self, backend: LiquidHandlerBackend, deck: Deck):
@@ -89,8 +69,9 @@ def __init__(self, backend: LiquidHandlerBackend, deck: Deck):
8969
deck: Deck to use.
9070
"""
9171

92-
self.backend = backend
93-
self.setup_finished = False
72+
super().__init__(backend=backend)
73+
74+
self.backend: LiquidHandlerBackend = backend
9475
self._picked_up_tips96: Optional[TipRack] = None # TODO: replace with tracker.
9576

9677
self.deck = deck
@@ -99,21 +80,23 @@ def __init__(self, backend: LiquidHandlerBackend, deck: Deck):
9980

10081
self.head: Dict[int, TipTracker] = {}
10182

83+
10284
async def setup(self):
10385
""" Prepare the robot for use. """
10486

10587
if self.setup_finished:
10688
raise RuntimeError("The setup has already finished. See `LiquidHandler.stop`.")
10789

10890
await self.backend.setup()
109-
self.setup_finished = True
11091

11192
self.head = {c: TipTracker() for c in range(self.backend.num_channels)}
11293

11394
self.resource_assigned_callback(self.deck)
11495
for resource in self.deck.children:
11596
self.resource_assigned_callback(resource)
11697

98+
super().setup()
99+
117100
def update_head_state(self, state: Dict[int, Optional[Tip]]):
118101
""" Update the state of the liquid handler head.
119102
@@ -139,10 +122,6 @@ def clear_head_state(self):
139122

140123
self.update_head_state({c: None for c in self.head.keys()})
141124

142-
async def stop(self):
143-
await self.backend.stop()
144-
self.setup_finished = False
145-
146125
def _run_async_in_thread(self, func, *args, **kwargs):
147126
def callback(*args, **kwargs):
148127
loop = asyncio.new_event_loop()

pylabrobot/machine.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC, abstractmethod
4+
import functools
5+
from typing import Callable
6+
7+
def need_setup_finished(func: Callable):
8+
""" Decorator for methods that require the liquid handler to be set up.
9+
10+
Checked by verifying `self.setup_finished` is `True`.
11+
12+
Raises:
13+
RuntimeError: If the liquid handler is not set up.
14+
"""
15+
16+
@functools.wraps(func)
17+
async def wrapper(self: MachineFrontend, *args, **kwargs):
18+
if not self.setup_finished:
19+
raise RuntimeError("The setup has not finished. See `setup`.")
20+
await func(self, *args, **kwargs)
21+
return wrapper
22+
23+
24+
class MachineBackend(ABC):
25+
""" Abstract class for machine backends. """
26+
27+
@abstractmethod
28+
async def setup(self):
29+
pass
30+
31+
@abstractmethod
32+
async def stop(self):
33+
pass
34+
35+
36+
class MachineFrontend(ABC):
37+
""" Abstract class for machine frontends. """
38+
39+
@abstractmethod
40+
def __init__(self, backend: MachineBackend):
41+
self.backend = backend
42+
self._setup_finished = False
43+
44+
@property
45+
def setup_finished(self) -> bool:
46+
return self._setup_finished
47+
48+
async def setup(self):
49+
self.backend.setup()
50+
51+
@need_setup_finished
52+
async def stop(self):
53+
self.backend.stop()

pylabrobot/plate_reading/backend.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
from __future__ import annotations
22

3-
from abc import ABC, abstractmethod
3+
from abc import ABCMeta, abstractmethod
44
import sys
55
from typing import List, Optional, Type
66

7+
from pylabrobot.machine import MachineBackend
8+
79
if sys.version_info >= (3, 8):
810
from typing import Literal
911
else:
1012
from typing_extensions import Literal
1113

1214

13-
class PlateReaderBackend(ABC):
15+
class PlateReaderBackend(MachineBackend, metaclass=ABCMeta):
1416
""" An abstract class for a plate reader. Plate readers are devices that can read luminescence,
1517
absorbance, or fluorescence from a plate. """
1618

pylabrobot/plate_reading/plate_reader.py

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import functools
21
import sys
3-
from typing import Callable, List, cast
2+
from typing import List, cast
43

4+
from pylabrobot.machine import MachineFrontend, need_setup_finished
55
from pylabrobot.resources import Coordinate, Resource, Plate
66
from pylabrobot.plate_reading.backend import PlateReaderBackend
77

@@ -15,26 +15,7 @@ class NoPlateError(Exception):
1515
pass
1616

1717

18-
# copied from LiquidHandler.py, maybe we need a shared base class?
19-
20-
def need_setup_finished(func: Callable): # pylint: disable=no-self-argument
21-
""" Decorator for methods that require the plate reader to be set up.
22-
23-
Checked by verifying `self.setup_finished` is `True`.
24-
25-
Raises:
26-
RuntimeError: If the liquid handler is not set up.
27-
"""
28-
29-
@functools.wraps(func)
30-
async def wrapper(self, *args, **kwargs):
31-
if not self.setup_finished:
32-
raise RuntimeError("The setup has not finished. See `PlateReader.setup`.")
33-
await func(self, *args, **kwargs) # pylint: disable=not-callable
34-
return wrapper
35-
36-
37-
class PlateReader(Resource):
18+
class PlateReader(Resource, MachineFrontend):
3819
""" The front end for plate readers. Plate readers are devices that can read luminescence,
3920
absorbance, or fluorescence from a plate.
4021
@@ -61,9 +42,9 @@ class PlateReader(Resource):
6142
"""
6243

6344
def __init__(self, name: str, backend: PlateReaderBackend) -> None:
64-
super().__init__(name=name, size_x=0, size_y=0, size_z=0, category="plate_reader")
65-
self.backend = backend
66-
self.setup_finished = False
45+
MachineFrontend.__init__(self, backend=backend)
46+
self.backend: PlateReaderBackend = backend
47+
Resource.__init__(self, name=name, size_x=0, size_y=0, size_z=0, category="plate_reader")
6748

6849
def assign_child_resource(self, resource):
6950
if len(self.children) >= 1:
@@ -77,14 +58,6 @@ def get_plate(self) -> Plate:
7758
raise NoPlateError("There is no plate in the plate reader.")
7859
return cast(Plate, self.children[0])
7960

80-
async def setup(self) -> None:
81-
await self.backend.setup()
82-
self.setup_finished = True
83-
84-
async def stop(self) -> None:
85-
await self.backend.stop()
86-
self.setup_finished = False
87-
8861
async def open(self) -> None:
8962
await self.backend.open()
9063

0 commit comments

Comments
 (0)