From 59ba7154c5918745ac894ea03503667d6473c760 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Thu, 7 Jan 2016 10:54:17 +0000 Subject: [PATCH] Move exceptions to their own sub-module This removes the circular dependency introduced in PR#137. This also fixes up an issue in the base meta-class which meant it wasn't working in Python 3 (only Python 2), and adds a bit to the meta-class to allow docstrings to be inherited (taken from the rest-docs branch). --- gpiozero/__init__.py | 8 ++++--- gpiozero/boards.py | 5 +++-- gpiozero/devices.py | 43 +++++++++++++++++++++++--------------- gpiozero/exc.py | 19 +++++++++++++++++ gpiozero/input_devices.py | 13 ++---------- gpiozero/output_devices.py | 14 ++----------- 6 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 gpiozero/exc.py diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index 0682763..0323548 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -5,13 +5,16 @@ from __future__ import ( division, ) -from .devices import ( +from .exc import ( GPIODeviceClosed, GPIODeviceError, + InputDeviceError, + OutputDeviceError, +) +from .devices import ( GPIODevice, ) from .input_devices import ( - InputDeviceError, InputDevice, Button, LineSensor, @@ -22,7 +25,6 @@ from .input_devices import ( MCP3004, ) from .output_devices import ( - OutputDeviceError, OutputDevice, PWMOutputDevice, PWMLED, diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 349a8f0..44f0ab4 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -12,8 +12,9 @@ except ImportError: from time import sleep from collections import namedtuple -from .input_devices import InputDeviceError, Button -from .output_devices import OutputDeviceError, LED, PWMLED, Buzzer, Motor +from .exc import InputDeviceError, OutputDeviceError +from .input_devices import Button +from .output_devices import LED, PWMLED, Buzzer, Motor from .devices import CompositeDevice, SourceMixin diff --git a/gpiozero/devices.py b/gpiozero/devices.py index f2f8986..c6d98f3 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -4,15 +4,18 @@ from __future__ import ( absolute_import, division, ) +nstr = str +str = type('') import atexit import weakref from threading import Thread, Event, RLock from collections import deque +from types import FunctionType from RPi import GPIO -from .input_devices import InputDeviceError +from .exc import GPIODeviceError, GPIODeviceClosed, InputDeviceError _GPIO_THREADS = set() _GPIO_PINS = set() @@ -35,21 +38,28 @@ GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) -class GPIODeviceError(Exception): - pass - - -class GPIODeviceClosed(GPIODeviceError): - pass - - -class GPIOFixedAttrs(type): +class GPIOMeta(type): # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. - def __call__(cls, *args, **kwargs): - # Construct the class as normal and ensure it's a subclass of GPIOBase - # (defined below with a custom __setattrs__) - result = super(GPIOFixedAttrs, cls).__call__(*args, **kwargs) + def __new__(mcls, name, bases, cls_dict): + # Construct the class as normal + cls = super(GPIOMeta, mcls).__new__(mcls, name, bases, cls_dict) + for attr_name, attr in cls_dict.items(): + # If there's a method in the class which has no docstring, search + # the base classes recursively for a docstring to copy + if isinstance(attr, FunctionType) and not attr.__doc__: + for base_cls in cls.__mro__: + if hasattr(base_cls, attr_name): + base_fn = getattr(base_cls, attr_name) + if base_fn.__doc__: + attr.__doc__ = base_fn.__doc__ + break + return cls + + def __call__(mcls, *args, **kwargs): + # Construct the instance as normal and ensure it's an instance of + # GPIOBase (defined below with a custom __setattrs__) + result = super(GPIOMeta, mcls).__call__(*args, **kwargs) assert isinstance(result, GPIOBase) # At this point __new__ and __init__ have all been run. We now fix the # set of attributes on the class by dir'ing the instance and creating a @@ -59,9 +69,8 @@ class GPIOFixedAttrs(type): return result -class GPIOBase(object): - __metaclass__ = GPIOFixedAttrs - +# Cross-version compatible method of using a metaclass +class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})): def __setattr__(self, name, value): # This overridden __setattr__ simply ensures that additional attributes # cannot be set on the class after construction (it manages this in diff --git a/gpiozero/exc.py b/gpiozero/exc.py new file mode 100644 index 0000000..b3b4e0e --- /dev/null +++ b/gpiozero/exc.py @@ -0,0 +1,19 @@ +from __future__ import ( + unicode_literals, + print_function, + absolute_import, + division, +) + +class GPIODeviceError(Exception): + pass + +class GPIODeviceClosed(GPIODeviceError): + pass + +class InputDeviceError(GPIODeviceError): + pass + +class OutputDeviceError(GPIODeviceError): + pass + diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 7e60ad5..01ba7c2 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -14,17 +14,8 @@ from threading import Event from RPi import GPIO from spidev import SpiDev -from .devices import ( - GPIODeviceError, - GPIODeviceClosed, - GPIODevice, - CompositeDevice, - GPIOQueue, -) - - -class InputDeviceError(GPIODeviceError): - pass +from .exc import InputDeviceError, GPIODeviceError, GPIODeviceClosed +from .devices import GPIODevice, CompositeDevice, GPIOQueue class InputDevice(GPIODevice): diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 0e385a0..2b58933 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -12,18 +12,8 @@ from itertools import repeat from RPi import GPIO -from .devices import ( - GPIODeviceError, - GPIODeviceClosed, - GPIODevice, - GPIOThread, - CompositeDevice, - SourceMixin, -) - - -class OutputDeviceError(GPIODeviceError): - pass +from .exc import OutputDeviceError, GPIODeviceError, GPIODeviceClosed +from .devices import GPIODevice, GPIOThread, CompositeDevice, SourceMixin class OutputDevice(SourceMixin, GPIODevice):