diff --git a/devices/devices/abstract_device.py b/devices/devices/abstract_device.py new file mode 100644 index 0000000000000000000000000000000000000000..5b65c3a6c02fd487920e02efed2394de275f1a9d --- /dev/null +++ b/devices/devices/abstract_device.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the XXX project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +"""Abstract Device Meta for LOFAR2.0 + +""" + +from abc import ABCMeta +import logging + +from tango.server import DeviceMeta + +logger = logging.getLogger() + + +class AbstractDeviceMetas(DeviceMeta, ABCMeta): + """Collects meta classes to allow hardware_device to be both a Device and an ABC. """ + + def __new__(mcs, name, bases, namespace, **kwargs): + cls = ABCMeta.__new__(mcs, name, bases, namespace, **kwargs) + cls = DeviceMeta.__new__(type(cls), name, bases, namespace) + return cls diff --git a/devices/devices/hardware_device.py b/devices/devices/hardware_device.py index 589eaa7cf9b06ce4b0a4d3d068d8eb17dd7e9eb8..f8f6ca50d7e02f5a8694c2ec4f9135dd874cd516 100644 --- a/devices/devices/hardware_device.py +++ b/devices/devices/hardware_device.py @@ -20,18 +20,15 @@ from tango import DevState, DebugIt, Attribute, DeviceProxy from clients.attribute_wrapper import attribute_wrapper from common.lofar_logging import log_exceptions -import logging +from devices.abstract_device import AbstractDeviceMetas +from devices.device_decorators import only_in_states, fault_on_error +import logging __all__ = ["hardware_device"] -from devices.device_decorators import only_in_states, fault_on_error - logger = logging.getLogger() -class AbstractDeviceMetas(DeviceMeta, ABCMeta): - ''' Collects meta classes to allow hardware_device to be both a Device and an ABC. ''' - pass #@log_exceptions() class hardware_device(Device, metaclass=AbstractDeviceMetas): diff --git a/devices/test/devices/test_abstract_device.py b/devices/test/devices/test_abstract_device.py new file mode 100644 index 0000000000000000000000000000000000000000..f54383c9a1b85f5c9e442f51d7f04d061951f772 --- /dev/null +++ b/devices/test/devices/test_abstract_device.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import abc +from unittest import mock + +from tango import DevFailed +from tango import server +from tango.server import attribute + +from tango.test_context import DeviceTestContext + +from devices.abstract_device import AbstractDeviceMetas + +from test import base + + +class TestAbstractDevice(base.TestCase): + + class AbstractExample(object, metaclass=abc.ABCMeta): + """A pure abc.ABCMeta metaclass with an abstract method + + This is an abstract class that inherits object with the abc.ABCMeta as + metaclass + """ + + @abc.abstractmethod + def example_method(self): + raise NotImplementedError + + class TestHardwareDevice(server.Device, metaclass=AbstractDeviceMetas): + """This is your overarching abstract class with a combined metaclass + + Device is an object with DeviceMeta as metaclass + We use HardwareDeviceMetas as metaclass + + Our metaclass contract is now fulfilled. + """ + + @attribute(dtype=float) + def call_example_method(self): + return self.example_method() + + @abc.abstractmethod + def example_method(self): + raise NotImplementedError + + class ConcreteHardwareDevice(TestHardwareDevice): + + def example_method(self): + return 12 + + def setUp(self): + super(TestAbstractDevice, self).setUp() + + def test_instance_tango(self): + + try: + with DeviceTestContext(self.TestHardwareDevice, process=True) as proxy: + # Calling this method raises the NotImplementedError exception + proxy.call_example_method() + except Exception as e: + self.assertIsInstance(e, DevFailed) + + with DeviceTestContext(self.ConcreteHardwareDevice, process=True) as proxy: + self.assertEqual(12, proxy.call_example_method) + + @mock.patch.object(server, 'get_worker') + @mock.patch.object(server, 'LatestDeviceImpl') + def test_instance_error(self, m_worker, m_implement): + # Creating this object should raise a type error but it does not + # combining metaclasses in this way does not have the desired result. + # This is a known limitation of this approach + m_device = self.TestHardwareDevice(mock.Mock(), mock.Mock()) + + # Raising the NotImplementedError works as expected, however. + self.assertRaises(NotImplementedError, m_device.example_method) + + # Creating this object of a class that has a pure metaclass does raise + # the expected error. + self.assertRaises(TypeError, self.AbstractExample) + +