Skip to content

Commit 1f2140a

Browse files
committed
Work on gpiozero#87
Implement a test suite, including Travis-CI integration
1 parent 29bcada commit 1f2140a

22 files changed

+1003
-78
lines changed

.travis.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
language: python
2+
python:
3+
- "3.5"
4+
- "3.4"
5+
- "3.3"
6+
- "3.2"
7+
- "2.7"
8+
- "pypy"
9+
- "pypy3"
10+
install: "pip install -e .[test]"
11+
script: make test

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ develop: tags
9898
@# These have to be done separately to avoid a cockup...
9999
$(PIP) install -U setuptools
100100
$(PIP) install -U pip
101-
$(PIP) install -e .
101+
$(PIP) install -e .[doc,test]
102102

103103
test:
104104
$(COVERAGE) run -m $(PYTEST) tests -v

coverage.cfg

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[run]
2+
branch = True
3+
include = gpiozero/*
4+
;omit = */bar.py,*/baz.py
5+
6+
[report]
7+
ignore_errors = True
8+
show_missing = True
9+
exclude_lines =
10+
pragma: no cover
11+
def __repr__
12+
if self\.debug
13+
raise AssertionError
14+
raise NotImplementedError
15+
if 0:
16+
if __name__ == .__main__.:
17+
18+
[html]
19+
directory = coverage
20+

docs/api_exc.rst

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
==========
2+
Exceptions
3+
==========
4+
5+
.. currentmodule:: gpiozero
6+
7+
The following exceptions are defined by GPIO Zero. Please note that multiple
8+
inheritance is heavily used in the exception hierarchy to make testing for
9+
exceptions easier. For example, to capture any exception generated by GPIO
10+
Zero's code::
11+
12+
from gpiozero import *
13+
14+
led = PWMLED(17)
15+
try:
16+
led.value = 2
17+
except GPIOZeroError:
18+
print('A GPIO Zero error occurred')
19+
20+
Since all GPIO Zero's exceptions descend from :exc:`GPIOZeroError`, this will
21+
work. However, certain specific errors have multiple parents. For example, in
22+
the case that an out of range value is passed to :attr:`OutputDevice.value` you
23+
would expect a :exc:`ValueError` to be raised. In fact, a
24+
:exc:`OutputDeviceBadValue` error will be raised. However, note that this
25+
descends from both :exc:`GPIOZeroError` (indirectly) and from :exc:`ValueError`
26+
so you can still do::
27+
28+
from gpiozero import *
29+
30+
led = PWMLED(17)
31+
try:
32+
led.value = 2
33+
except ValueError:
34+
print('Bad value specified')
35+
36+
37+
.. autoexception:: GPIOZeroError
38+
39+
.. autoexception:: CompositeDeviceError
40+
41+
.. autoexception:: GPIODeviceError
42+
43+
.. autoexception:: GPIODeviceClosed
44+
45+
.. autoexception:: GPIOPinInUse
46+
47+
.. autoexception:: GPIOPinMissing
48+
49+
.. autoexception:: GPIOBadQueueLen
50+
51+
.. autoexception:: InputDeviceError
52+
53+
.. autoexception:: OutputDeviceError
54+
55+
.. autoexception:: OutputDeviceBadValue
56+
57+
.. autoexception:: PinError
58+
59+
.. autoexception:: PinFixedFunction
60+
61+
.. autoexception:: PinInvalidFunction
62+
63+
.. autoexception:: PinInvalidState
64+
65+
.. autoexception:: PinInvalidPull
66+
67+
.. autoexception:: PinInvalidEdges
68+
69+
.. autoexception:: PinSetInput
70+
71+
.. autoexception:: PinFixedPull
72+
73+
.. autoexception:: PinEdgeDetectUnsupported
74+
75+
.. autoexception:: PinPWMError
76+
77+
.. autoexception:: PinPWMUnsupported
78+
79+
.. autoexception:: PinPWMFixedValue
80+

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ Table of Contents
1313
api_boards
1414
api_generic
1515
api_pins
16+
api_exc
1617
changelog
1718
license

gpiozero/__init__.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,20 @@
55
division,
66
)
77

8-
from .pins.exc import (
8+
from .pins import (
9+
Pin,
10+
)
11+
from .exc import (
12+
GPIOZeroError,
13+
CompositeDeviceError,
14+
GPIODeviceError,
15+
GPIODeviceClosed,
16+
GPIOPinInUse,
17+
GPIOPinMissing,
18+
GPIOBadQueueLen,
19+
InputDeviceError,
20+
OutputDeviceError,
21+
OutputDeviceBadValue,
922
PinError,
1023
PinFixedFunction,
1124
PinInvalidFunction,
@@ -19,15 +32,6 @@
1932
PinPWMUnsupported,
2033
PinPWMFixedValue,
2134
)
22-
from .pins import (
23-
Pin,
24-
)
25-
from .exc import (
26-
GPIODeviceClosed,
27-
GPIODeviceError,
28-
InputDeviceError,
29-
OutputDeviceError,
30-
)
3135
from .devices import (
3236
GPIODevice,
3337
CompositeDevice,

gpiozero/compat.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from __future__ import (
2+
unicode_literals,
3+
absolute_import,
4+
print_function,
5+
division,
6+
)
7+
str = type('')
8+
9+
import cmath
10+
11+
12+
# Back-ported from python 3.5; see
13+
# github.com/PythonCHB/close_pep/blob/master/is_close.py for original
14+
# implementation
15+
def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
16+
if rel_tol < 0.0 or abs_tol < 0.0:
17+
raise ValueError('error tolerances must be non-negative')
18+
if a == b: # fast-path for exact equality
19+
return True
20+
if cmath.isinf(a) or cmath.isinf(b):
21+
return False
22+
diff = abs(b - a)
23+
return (
24+
(diff <= abs(rel_tol * b)) or
25+
(diff <= abs(rel_tol * a)) or
26+
(diff <= abs_tol)
27+
)

gpiozero/devices.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
from collections import deque
1414
from types import FunctionType
1515

16-
from .exc import GPIODeviceError, GPIODeviceClosed, InputDeviceError
16+
from .exc import (
17+
GPIOPinMissing,
18+
GPIOPinInUse,
19+
GPIODeviceClosed,
20+
GPIOBadQueueLen,
21+
)
1722

1823
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
1924
# as it supports PWM, and all Pi revisions. If no third-party libraries are
@@ -203,7 +208,8 @@ class GPIODevice(ValuesMixin, GPIOBase):
203208
204209
:param int pin:
205210
The GPIO pin (in BCM numbering) that the device is connected to. If
206-
this is ``None`` a :exc:`GPIODeviceError` will be raised.
211+
this is ``None``, :exc:`GPIOPinMissing` will be raised. If the pin is
212+
already in use by another device, :exc:`GPIOPinInUse` will be raised.
207213
"""
208214
def __init__(self, pin=None):
209215
super(GPIODevice, self).__init__()
@@ -212,12 +218,12 @@ def __init__(self, pin=None):
212218
# value of pin until we've verified that it isn't already allocated
213219
self._pin = None
214220
if pin is None:
215-
raise GPIODeviceError('No pin given')
221+
raise GPIOPinMissing('No pin given')
216222
if isinstance(pin, int):
217223
pin = DefaultPin(pin)
218224
with _PINS_LOCK:
219225
if pin in _PINS:
220-
raise GPIODeviceError(
226+
raise GPIOPinInUse(
221227
'pin %r is already in use by another gpiozero object' % pin
222228
)
223229
_PINS.add(pin)
@@ -342,7 +348,7 @@ def __init__(self, parent, queue_len=5, sample_wait=0.0, partial=False):
342348
assert isinstance(parent, GPIODevice)
343349
super(GPIOQueue, self).__init__(target=self.fill)
344350
if queue_len < 1:
345-
raise InputDeviceError('queue_len must be at least one')
351+
raise GPIOBadQueueLen('queue_len must be at least one')
346352
self.queue = deque(maxlen=queue_len)
347353
self.partial = partial
348354
self.sample_wait = sample_wait

gpiozero/exc.py

+61-5
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,72 @@
44
absolute_import,
55
division,
66
)
7+
str = type('')
78

8-
class GPIODeviceError(Exception):
9-
pass
9+
10+
class GPIOZeroError(Exception):
11+
"Base class for all exceptions in GPIO Zero"
12+
13+
class CompositeDeviceError(GPIOZeroError):
14+
"Base class for errors specific to the CompositeDevice hierarchy"
15+
16+
class GPIODeviceError(GPIOZeroError):
17+
"Base class for errors specific to the GPIODevice hierarchy"
1018

1119
class GPIODeviceClosed(GPIODeviceError):
12-
pass
20+
"Error raised when an operation is attempted on a closed device"
21+
22+
class GPIOPinInUse(GPIODeviceError):
23+
"Error raised when attempting to use a pin already in use by another device"
24+
25+
class GPIOPinMissing(GPIODeviceError, ValueError):
26+
"Error raised when a pin number is not specified"
27+
28+
class GPIOBadQueueLen(GPIODeviceError, ValueError):
29+
"Error raised when non-positive queue length is specified"
1330

1431
class InputDeviceError(GPIODeviceError):
15-
pass
32+
"Base class for errors specific to the InputDevice hierarchy"
1633

1734
class OutputDeviceError(GPIODeviceError):
18-
pass
35+
"Base class for errors specified to the OutputDevice hierarchy"
36+
37+
class OutputDeviceBadValue(OutputDeviceError, ValueError):
38+
"Error raised when ``value`` is set to an invalid value"
39+
40+
class PinError(GPIOZeroError):
41+
"Base class for errors related to pin implementations"
42+
43+
class PinFixedFunction(PinError, AttributeError):
44+
"Error raised when attempting to change the function of a fixed type pin"
45+
46+
class PinInvalidFunction(PinError, ValueError):
47+
"Error raised when attempting to change the function of a pin to an invalid value"
48+
49+
class PinInvalidState(PinError, ValueError):
50+
"Error raised when attempting to assign an invalid state to a pin"
51+
52+
class PinInvalidPull(PinError, ValueError):
53+
"Error raised when attempting to assign an invalid pull-up to a pin"
54+
55+
class PinInvalidEdges(PinError, ValueError):
56+
"Error raised when attempting to assign an invalid edge detection to a pin"
57+
58+
class PinSetInput(PinError, AttributeError):
59+
"Error raised when attempting to set a read-only pin"
60+
61+
class PinFixedPull(PinError, AttributeError):
62+
"Error raised when attempting to set the pull of a pin with fixed pull-up"
63+
64+
class PinEdgeDetectUnsupported(PinError, AttributeError):
65+
"Error raised when attempting to use edge detection on unsupported pins"
66+
67+
class PinPWMError(PinError):
68+
"Base class for errors related to PWM implementations"
69+
70+
class PinPWMUnsupported(PinPWMError, AttributeError):
71+
"Error raised when attempting to activate PWM on unsupported pins"
72+
73+
class PinPWMFixedValue(PinPWMError, AttributeError):
74+
"Error raised when attempting to initialize PWM on an input pin"
1975

gpiozero/output_devices.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from threading import Lock
1111
from itertools import repeat, cycle, chain
1212

13-
from .exc import OutputDeviceError, GPIODeviceError, GPIODeviceClosed
13+
from .exc import OutputDeviceBadValue, GPIOPinMissing, GPIODeviceClosed
1414
from .devices import GPIODevice, GPIOThread, CompositeDevice, SourceMixin
1515

1616

@@ -24,7 +24,7 @@ class OutputDevice(SourceMixin, GPIODevice):
2424
2525
:param int pin:
2626
The GPIO pin (in BCM numbering) that the device is connected to. If
27-
this is ``None`` a :exc:`GPIODeviceError` will be raised.
27+
this is ``None`` a :exc:`GPIOPinMissing` will be raised.
2828
2929
:param bool active_high:
3030
If ``True`` (the default), the :meth:`on` method will set the GPIO to
@@ -54,7 +54,7 @@ def _write(self, value):
5454
value = not value
5555
try:
5656
self.pin.state = bool(value)
57-
except ValueError:
57+
except AttributeError:
5858
self._check_open()
5959
raise
6060

@@ -269,7 +269,7 @@ class PWMOutputDevice(OutputDevice):
269269
def __init__(self, pin=None, active_high=True, initial_value=0, frequency=100):
270270
self._blink_thread = None
271271
if not 0 <= initial_value <= 1:
272-
raise OutputDeviceError("initial_value must be between 0 and 1")
272+
raise OutputDeviceBadValue("initial_value must be between 0 and 1")
273273
super(PWMOutputDevice, self).__init__(pin, active_high)
274274
try:
275275
# XXX need a way of setting these together
@@ -299,7 +299,7 @@ def _write(self, value):
299299
if not self.active_high:
300300
value = 1 - value
301301
if not 0 <= value <= 1:
302-
raise OutputDeviceError("PWM value must be between 0 and 1")
302+
raise OutputDeviceBadValue("PWM value must be between 0 and 1")
303303
try:
304304
self.pin.state = value
305305
except AttributeError:
@@ -505,7 +505,7 @@ def __init__(
505505
self._leds = ()
506506
self._blink_thread = None
507507
if not all([red, green, blue]):
508-
raise OutputDeviceError('red, green, and blue pins must be provided')
508+
raise GPIOPinMissing('red, green, and blue pins must be provided')
509509
super(RGBLED, self).__init__()
510510
self._leds = tuple(PWMLED(pin, active_high) for pin in (red, green, blue))
511511
self.value = initial_value
@@ -680,7 +680,7 @@ class Motor(SourceMixin, CompositeDevice):
680680
"""
681681
def __init__(self, forward=None, backward=None):
682682
if not all([forward, backward]):
683-
raise OutputDeviceError(
683+
raise GPIOPinMissing(
684684
'forward and backward pins must be provided'
685685
)
686686
super(Motor, self).__init__()
@@ -722,7 +722,7 @@ def value(self):
722722
@value.setter
723723
def value(self, value):
724724
if not -1 <= value <= 1:
725-
raise OutputDeviceError("Motor value must be between -1 and 1")
725+
raise OutputDeviceBadValue("Motor value must be between -1 and 1")
726726
if value > 0:
727727
self.forward(value)
728728
elif value < 0:

0 commit comments

Comments
 (0)