Skip to content

Commit fce5a6e

Browse files
authored
Merge pull request #117 from robberwick/not-connected-exception
feat: implement backend connection checks and add no_backend_required…
2 parents e402922 + c06b5c1 commit fce5a6e

File tree

4 files changed

+56
-1
lines changed

4 files changed

+56
-1
lines changed

src/blinkstick/clients/blinkstick.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
remap_rgb_value_reverse,
1313
ColorFormat,
1414
)
15-
from blinkstick.enums import BlinkStickVariant
15+
from blinkstick.decorators import no_backend_required
1616
from blinkstick.devices import BlinkStickDevice
17+
from blinkstick.enums import BlinkStickVariant
18+
from blinkstick.exceptions import NotConnected
1719
from blinkstick.utilities import string_to_info_block_data
1820

1921
if sys.platform == "win32":
@@ -63,6 +65,19 @@ def __init__(
6365
self.backend = USBBackend(device)
6466
self.bs_serial = self.get_serial()
6567

68+
def __getattribute__(self, name):
69+
"""Default all callables to require a backend unless they have the no_backend_required attribute"""
70+
attr = object.__getattribute__(self, name)
71+
if callable(attr) and not getattr(attr, "no_backend_required", False):
72+
73+
def wrapper(*args, **kwargs):
74+
if self.backend is None:
75+
raise NotConnected("No backend set")
76+
return attr(*args, **kwargs)
77+
78+
return wrapper
79+
return attr
80+
6681
def get_serial(self) -> str:
6782
"""
6883
Returns the serial number of backend.::

src/blinkstick/decorators.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
from functools import wraps
4+
5+
6+
def no_backend_required(func):
7+
"""no-op decorator to mark a function as requiring a backend. See BlinkStick.__getattribute__ for usage."""
8+
9+
func.no_backend_required = True
10+
11+
@wraps(func)
12+
def wrapper(self, *args, **kwargs):
13+
return func(self, *args, **kwargs)
14+
15+
return wrapper

src/blinkstick/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1+
from __future__ import annotations
2+
3+
14
class BlinkStickException(Exception):
25
pass
6+
7+
8+
class NotConnected(BlinkStickException):
9+
pass

tests/clients/test_blinkstick.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,32 @@
77
from blinkstick.clients.blinkstick import BlinkStick
88
from pytest_mock import MockFixture
99

10+
from blinkstick.exceptions import NotConnected
1011
from tests.conftest import make_blinkstick
1112

1213

1314
def test_instantiate():
15+
"""Test that we can instantiate a BlinkStick object."""
1416
bs = BlinkStick()
1517
assert bs is not None
1618

1719

20+
def test_all_methods_require_backend(make_blinkstick):
21+
"""Test that all methods require a backend."""
22+
bs = make_blinkstick()
23+
bs.backend = None # noqa
24+
25+
class_methods = (
26+
method
27+
for method in dir(BlinkStick)
28+
if callable(getattr(bs, method)) and not method.startswith("__")
29+
)
30+
for method_name in class_methods:
31+
method = getattr(bs, method_name)
32+
with pytest.raises(NotConnected):
33+
method()
34+
35+
1836
@pytest.mark.parametrize(
1937
"serial, version_attribute, expected_variant, expected_variant_value",
2038
[

0 commit comments

Comments
 (0)