Skip to content
Snippets Groups Projects
Commit ac3ad703 authored by Corné Lukken's avatar Corné Lukken
Browse files

Merge branch 'L2SS-551-multi-meta' into 'master'

L2SS-551: Do not combine ABCMeta with Tango DeviceMeta

Closes L2SS-551

See merge request !206
parents 4975e485 be637079
No related branches found
No related tags found
1 merge request!206L2SS-551: Do not combine ABCMeta with Tango DeviceMeta
# -*- 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
"""
import logging
from tango.server import DeviceMeta
logger = logging.getLogger()
# TODO(Corne): Fix combining metaclasses by iterating over their variables and
# methods. https://support.astron.nl/jira/browse/L2SS-551
# class AbstractDeviceMetas(DeviceMeta, ABCMeta):
class AbstractDeviceMetas(DeviceMeta):
"""Collects meta classes to allow lofar_device to be both a Device and an ABC. """
def __new__(mcs, name, bases, namespace, **kwargs):
cls = DeviceMeta.__new__(mcs, name, bases, namespace, **kwargs)
# temp_cls = ABCMeta.__new__(mcs, name, bases, namespace)
# setattr(cls, '__abstractmethods__', temp_cls.__abstractmethods__)
# setattr(cls, '_abc_impl', temp_cls._abc_impl)
return cls
...@@ -11,10 +11,8 @@ ...@@ -11,10 +11,8 @@
""" """
from abc import abstractmethod
# PyTango imports # PyTango imports
from tango.server import Device, command, attribute from tango.server import attribute, command, Device, DeviceMeta
from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy
import time import time
import math import math
...@@ -23,7 +21,6 @@ import math ...@@ -23,7 +21,6 @@ import math
from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
from tangostationcontrol.common.lofar_logging import log_exceptions from tangostationcontrol.common.lofar_logging import log_exceptions
from tangostationcontrol.common.lofar_version import get_version from tangostationcontrol.common.lofar_version import get_version
from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas
from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error
...@@ -32,7 +29,8 @@ __all__ = ["lofar_device"] ...@@ -32,7 +29,8 @@ __all__ = ["lofar_device"]
import logging import logging
logger = logging.getLogger() logger = logging.getLogger()
class lofar_device(Device, metaclass=AbstractDeviceMetas):
class lofar_device(Device, metaclass=DeviceMeta):
""" """
**Properties:** **Properties:**
...@@ -217,14 +215,12 @@ class lofar_device(Device, metaclass=AbstractDeviceMetas): ...@@ -217,14 +215,12 @@ class lofar_device(Device, metaclass=AbstractDeviceMetas):
def configure_for_fault(self): def configure_for_fault(self):
pass pass
@abstractmethod
def configure_for_off(self): def configure_for_off(self):
pass pass
def configure_for_on(self): def configure_for_on(self):
pass pass
@abstractmethod
def configure_for_initialise(self): def configure_for_initialise(self):
pass pass
......
...@@ -24,7 +24,8 @@ from tangostationcontrol.devices.lofar_device import lofar_device ...@@ -24,7 +24,8 @@ from tangostationcontrol.devices.lofar_device import lofar_device
import logging import logging
logger = logging.getLogger() logger = logging.getLogger()
__all__ = ["opcua_device", "main"] __all__ = ["opcua_device"]
class opcua_device(lofar_device): class opcua_device(lofar_device):
""" """
......
...@@ -11,8 +11,6 @@ ...@@ -11,8 +11,6 @@
""" """
from abc import abstractmethod
# PyTango imports # PyTango imports
from tango.server import device_property from tango.server import device_property
...@@ -32,15 +30,13 @@ import numpy ...@@ -32,15 +30,13 @@ import numpy
__all__ = ["Statistics"] __all__ = ["Statistics"]
# TODO(Corne): Make Statistics use ABCMeta again when L2SS-551 is fixed
# https://support.astron.nl/jira/browse/L2SS-551
class Statistics(opcua_device): class Statistics(opcua_device):
# In derived classes, set this to a subclass of StatisticsCollector # In derived classes, set this to a subclass of StatisticsCollector
@property @property
@abstractmethod
def STATISTICS_COLLECTOR_CLASS(self): def STATISTICS_COLLECTOR_CLASS(self):
pass raise NotImplementedError
# ----------------- # -----------------
# Device Properties # Device Properties
......
...@@ -28,6 +28,7 @@ import numpy ...@@ -28,6 +28,7 @@ import numpy
__all__ = ["XST", "main"] __all__ = ["XST", "main"]
class XST(Statistics): class XST(Statistics):
STATISTICS_COLLECTOR_CLASS = XSTCollector STATISTICS_COLLECTOR_CLASS = XSTCollector
......
# -*- 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 server
from tango.server import attribute
from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas
from tangostationcontrol.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, RuntimeError)
#
# 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)
@mock.patch.object(server, 'get_worker')
@mock.patch.object(server, 'LatestDeviceImpl')
def test_isinstance(self, m_worker, m_implement):
m_device = self.TestHardwareDevice(mock.Mock(), mock.Mock())
self.assertFalse(isinstance(m_device, AbstractDeviceMetas))
# -*- 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 mock
from tango import server
from tango.test_context import DeviceTestContext
from tangostationcontrol.test import base
class TestStatisticsDevice(base.TestCase):
def setUp(self):
super(TestStatisticsDevice, self).setUp()
def test_python_bug(self):
"""Python is bugged and super() calls in ctypes fail
This is the same type of issues that prevents combining metaclasses
DeviceMeta and ABCMeta
https://github.com/waveform80/picamera/issues/355
https://bugs.python.org/issue29270
"""
import ctypes as ct
try:
class TestSubclass(ct.c_uint32):
def __repr__(self):
return super().__repr__()
except Exception as e:
self.assertIsInstance(e, TypeError)
@mock.patch.object(server, 'get_worker')
def test_instance_statistics(self, m_worker):
"""Test that we can import and create a statistics device
The import of Statistics fails when both ABCMeta and DeviceMeta are
combined. Leave this test to prevent regressions when reattempting our
combined metaclass.
"""
from tangostationcontrol.devices.sdp.statistics import Statistics
with DeviceTestContext(Statistics, process=True) as proxy:
self.assertTrue(proxy.ping() > 0)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment