Skip to content

Commit 2f73bbe

Browse files
authored
Add import error details for optional dependencies (#606)
1 parent 0da019f commit 2f73bbe

File tree

13 files changed

+81
-29
lines changed

13 files changed

+81
-29
lines changed

pylabrobot/audio/audio.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
from IPython.display import Audio, display
1010

1111
USE_AUDIO = True
12-
except ImportError:
12+
except ImportError as e:
1313
USE_AUDIO = False
14+
_AUDIO_IMPORT_ERROR = e
1415

1516

1617
def _audio_check(func):
1718
@functools.wraps(func)
1819
def wrapper(*args, **kwargs):
1920
if not USE_AUDIO:
20-
return
21+
raise RuntimeError(
22+
f"Audio functionality requires IPython.display. Import error: {_AUDIO_IMPORT_ERROR}"
23+
)
2124
return func(*args, **kwargs)
2225

2326
return wrapper

pylabrobot/io/ftdi.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from pylibftdi import Device
1010

1111
HAS_PYLIBFTDI = True
12-
except ImportError:
12+
except ImportError as e:
1313
HAS_PYLIBFTDI = False
14+
_FTDI_IMPORT_ERROR = e
1415

1516
from pylabrobot.io.capture import CaptureReader, Command, capturer, get_capture_or_validation_active
1617
from pylabrobot.io.errors import ValidationError
@@ -38,7 +39,7 @@ def __init__(self, device_id: Optional[str] = None):
3839

3940
async def setup(self):
4041
if not HAS_PYLIBFTDI:
41-
raise RuntimeError("pylibftdi not installed.")
42+
raise RuntimeError(f"pylibftdi not installed. Import error: {_FTDI_IMPORT_ERROR}")
4243
self._dev.open()
4344
self._executor = ThreadPoolExecutor(max_workers=1)
4445

pylabrobot/io/hid.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
import hid # type: ignore
1313

1414
USE_HID = True
15-
except ImportError:
15+
except ImportError as e:
1616
USE_HID = False
17+
_HID_IMPORT_ERROR = e
1718

1819

1920
logger = logging.getLogger(__name__)
@@ -40,7 +41,9 @@ def __init__(self, vid=0x03EB, pid=0x2023, serial_number: Optional[str] = None):
4041

4142
async def setup(self):
4243
if not USE_HID:
43-
raise RuntimeError("This backend requires the `hid` package to be installed")
44+
raise RuntimeError(
45+
f"This backend requires the `hid` package to be installed. Import error: {_HID_IMPORT_ERROR}"
46+
)
4447
self.device = hid.Device(vid=self.vid, pid=self.pid, serial=self.serial_number)
4548
self._executor = ThreadPoolExecutor(max_workers=1)
4649
logger.log(LOG_LEVEL_IO, "Opened HID device %s", self._unique_id)

pylabrobot/io/serial.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
import serial
1212

1313
HAS_SERIAL = True
14-
except ImportError:
14+
except ImportError as e:
1515
HAS_SERIAL = False
16+
_SERIAL_IMPORT_ERROR = e
1617

1718
from pylabrobot.io.capture import CaptureReader, Command, capturer, get_capture_or_validation_active
1819
from pylabrobot.io.validation_utils import LOG_LEVEL_IO, align_sequences
@@ -63,7 +64,7 @@ def port(self) -> str:
6364

6465
async def setup(self):
6566
if not HAS_SERIAL:
66-
raise RuntimeError("pyserial not installed.")
67+
raise RuntimeError(f"pyserial not installed. Import error: {_SERIAL_IMPORT_ERROR}")
6768
loop = asyncio.get_running_loop()
6869
self._executor = ThreadPoolExecutor(max_workers=1)
6970

pylabrobot/liquid_handling/backends/http.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import requests
1111

1212
HAS_REQUESTS = True
13-
except ImportError:
13+
except ImportError as e:
1414
HAS_REQUESTS = False
15+
_REQUESTS_IMPORT_ERROR = e
1516

1617

1718
class HTTPBackend(SerializingBackend):
@@ -44,7 +45,9 @@ def __init__(
4445
"""
4546

4647
if not HAS_REQUESTS:
47-
raise RuntimeError("The http backend requires the requests module.")
48+
raise RuntimeError(
49+
f"The http backend requires the requests module. Import error: {_REQUESTS_IMPORT_ERROR}"
50+
)
4851

4952
super().__init__(num_channels=num_channels)
5053
self.session: Optional[requests.Session] = None

pylabrobot/liquid_handling/backends/opentrons_backend.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@
4343
from requests import HTTPError
4444

4545
USE_OT = True
46-
except ImportError:
46+
except ImportError as e:
4747
USE_OT = False
48+
_OT_IMPORT_ERROR = e
4849
else:
4950
USE_OT = False
5051

@@ -79,6 +80,7 @@ def __init__(self, host: str, port: int = 31950):
7980
if not USE_OT:
8081
raise RuntimeError(
8182
"Opentrons is not installed. Please run pip install pylabrobot[opentrons]."
83+
f" Import error: {_OT_IMPORT_ERROR}."
8284
" Only supported on Python 3.10 and below."
8385
)
8486

pylabrobot/liquid_handling/backends/websocket.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
import websockets.legacy.server
1313

1414
HAS_WEBSOCKETS = True
15-
except ImportError:
15+
except ImportError as e:
1616
HAS_WEBSOCKETS = False
17+
_WEBSOCKETS_IMPORT_ERROR = e
1718

1819
from pylabrobot.__version__ import STANDARD_FORM_JSON_VERSION
1920
from pylabrobot.liquid_handling.backends.serializing_backend import (
@@ -46,7 +47,9 @@ def __init__(
4647
"""
4748

4849
if not HAS_WEBSOCKETS:
49-
raise RuntimeError("The WebSocketBackend requires websockets to be installed.")
50+
raise RuntimeError(
51+
f"The WebSocketBackend requires websockets to be installed. Import error: {_WEBSOCKETS_IMPORT_ERROR}"
52+
)
5053

5154
super().__init__(num_channels=num_channels)
5255
self._websocket: Optional["websockets.legacy.server.WebSocketServerProtocol"] = None
@@ -252,7 +255,9 @@ async def setup(self):
252255
"""Start the websocket server. This will run in a separate thread."""
253256

254257
if not HAS_WEBSOCKETS:
255-
raise RuntimeError("The WebSocketBackend requires websockets to be installed.")
258+
raise RuntimeError(
259+
f"The WebSocketBackend requires websockets to be installed. Import error: {_WEBSOCKETS_IMPORT_ERROR}"
260+
)
256261

257262
async def run_server():
258263
self._stop_ = self.loop.create_future()

pylabrobot/plate_reading/biotek_backend.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,31 @@
99

1010
try:
1111
import cv2 # type: ignore
12-
except ImportError:
12+
13+
CV2_AVAILABLE = True
14+
except ImportError as e:
1315
cv2 = None # type: ignore
16+
CV2_AVAILABLE = False
17+
_CV2_IMPORT_ERROR = e
1418

1519
from pylabrobot.resources.plate import Plate
1620

1721
try:
1822
import numpy as np # type: ignore
1923

2024
USE_NUMPY = True
21-
except ImportError:
25+
except ImportError as e:
2226
USE_NUMPY = False
27+
_NUMPY_IMPORT_ERROR = e
2328

2429
try:
2530
import PySpin # type: ignore
2631

2732
# can be downloaded from https://www.teledynevisionsolutions.com/products/spinnaker-sdk/
2833
USE_PYSPIN = True
29-
except ImportError:
34+
except ImportError as e:
3035
USE_PYSPIN = False
36+
_PYSPIN_IMPORT_ERROR = e
3137

3238
from pylabrobot.io.ftdi import FTDI
3339
from pylabrobot.plate_reading.backend import ImageReaderBackend
@@ -149,7 +155,10 @@ async def setup(self, use_cam: bool = False) -> None:
149155

150156
if use_cam:
151157
if not USE_PYSPIN:
152-
raise RuntimeError("PySpin is not installed. Please follow the imaging setup instructions.")
158+
raise RuntimeError(
159+
"PySpin is not installed. Please follow the imaging setup instructions. "
160+
f"Import error: {_PYSPIN_IMPORT_ERROR}"
161+
)
153162
if self.imaging_config is None:
154163
raise RuntimeError("Imaging configuration is not set.")
155164

@@ -866,7 +875,10 @@ async def auto_focus(self, timeout: float = 30):
866875
raise RuntimeError("Row and column not set. Run select() first.")
867876
if not USE_NUMPY:
868877
# This is strange, because Spinnaker requires numpy
869-
raise RuntimeError("numpy is not installed. See Cytation5 installation instructions.")
878+
raise RuntimeError(
879+
"numpy is not installed. See Cytation5 installation instructions. "
880+
f"Import error: {_NUMPY_IMPORT_ERROR}"
881+
)
870882

871883
# objective function: variance of laplacian
872884
async def evaluate_focus(focus_value):
@@ -882,8 +894,10 @@ async def evaluate_focus(focus_value):
882894
)
883895
image = images[0] # self.capture returns List now
884896

885-
if cv2 is None:
886-
raise RuntimeError("cv2 needs to be installed for auto focus")
897+
if not CV2_AVAILABLE:
898+
raise RuntimeError(
899+
f"cv2 needs to be installed for auto focus. Import error: {_CV2_IMPORT_ERROR}"
900+
)
887901

888902
# NVMG: Normalized Variance of the Gradient Magnitude
889903
# Chat invented this i think

pylabrobot/pumps/agrowpumps/agrowdosepump_backend.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
try:
88
from pymodbus.client import AsyncModbusSerialClient # type: ignore
9-
except ImportError:
9+
10+
_MODBUS_IMPORT_ERROR = None
11+
except ImportError as e:
1012
AsyncModbusSerialClient = None # type: ignore
13+
_MODBUS_IMPORT_ERROR = e
1114

1215
from pylabrobot.pumps.backend import PumpArrayBackend
1316

@@ -117,7 +120,10 @@ async def setup(self):
117120

118121
async def _setup_modbus(self):
119122
if AsyncModbusSerialClient is None:
120-
raise RuntimeError("pymodbus is not installed. Please install it with 'pip install pymodbus'")
123+
raise RuntimeError(
124+
"pymodbus is not installed. Please install it with 'pip install pymodbus'."
125+
f" Import error: {_MODBUS_IMPORT_ERROR}"
126+
)
121127
self._modbus = AsyncModbusSerialClient(
122128
port=self.port,
123129
baudrate=115200,

pylabrobot/resources/opentrons/load.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import opentrons_shared_data.labware
77

88
USE_OT = True
9-
except ImportError:
9+
except ImportError as e:
1010
USE_OT = False
11+
_OT_IMPORT_ERROR = e
1112

1213
from pylabrobot.resources.coordinate import Coordinate
1314
from pylabrobot.resources.plate import Plate
@@ -35,7 +36,9 @@ def ot_definition_to_resource(
3536

3637
if not USE_OT:
3738
raise ImportError(
38-
"opentrons_shared_data is not installed. " "run `pip install opentrons_shared_data`"
39+
"opentrons_shared_data is not installed. "
40+
f"Import error: {_OT_IMPORT_ERROR}. "
41+
"run `pip install opentrons_shared_data`"
3942
)
4043

4144
display_category = data["metadata"]["displayCategory"]

0 commit comments

Comments
 (0)