Source code for labtronyx.bases.resource

"""
Getting started
---------------

All resources are subclasses of the :class:`labtronyx.ResourceBase` class. Creating a resource is simple::

    import labtronyx

    class RESOURCE_CLASS_NAME(labtronyx.ResourceBase):
        pass

Attributes
----------

Resources have some attributes that should be defined:

   * `interfaceName` - str that names the interface that owns the resource. Should match the `interfaceName`
     attribute of the corresponding interface.

Errors
------

Labtronyx has a number of build-in exception types that can be raised from resources. For more information about these
exceptions and the proper times to raise them, see :doc:`../api/exceptions`

Usage Model
-----------

When resources are created, they should default to the closed state. This is to prevent any locking issues if multiple
instances of Labtronyx are open. Resources should typically only be open if they are actively in use.

When a driver is loaded, the resource object acts as a proxy to access the driver methods. All driver functions are
accessed directly from the resource object as if they were a single object. Some things to note:

   * If there is a naming conflict between a method in the resource and a method in the driver, the resource method
     will take priority
   * Driver functions are inaccessible and cannot be called if the resource is closed

.. note::

   In order to support proper operation using Remote Resources and Instruments, some limitations should be imposed to
   ensure maximum compatibility. All methods within a resource or driver must return serializable data.
   Serializable data types include:

       * str
       * unicode
       * int
       * long
       * float
       * bool
       * list
       * tuple
       * dict
       * None

   If a method returns an object that is not serializable, an exception will be passed back to the remote host. If the
   method returns a non-serializable data type, the method should be prefixed with an underscore ('_') to mark it as a
   protected function that cannot be accessed remotely.

Resources may implement more functions than just those which are defined in the API below, see the interface and
resource class documentation to learn more.
"""
# Package relative imports
from ..common import events
from ..common.errors import *
from ..common.plugin import PluginBase, PluginAttribute

__all__ = ['ResourceBase']


[docs]class ResourceBase(PluginBase): """ Resource Base Class. Acts as a proxy for driver if one is loaded. :param manager: InstrumentManager instance :type manager: labtronyx.InstrumentManager :param interface: Reference to the interface instance :type interface: labtronyx.bases.interface.InterfaceBase :param resID: Resource Identifier :type resID: str :param logger: Logger instance :type logger: logging.Logger """ pluginType = 'resource' interfaceName = PluginAttribute(attrType=str, required=True) def __init__(self, manager, resID, **kwargs): super(ResourceBase, self).__init__(**kwargs) self._manager = manager self._resID = resID # Instance variables self._driver = None def __del__(self): try: self.close() except: pass def __getattr__(self, name): if self._driver is not None: if hasattr(self._driver, name): # Only call driver functions if the resource is open if self.isOpen(): return getattr(self._driver, name) else: # Driver functions are locked out if the resource is not open raise ResourceNotOpen("Unable to call driver function on closed resource") else: raise AttributeError else: raise AttributeError def __enter__(self): self.open() def __exit__(self, exc_type, exc_val, exc_tb): self.close() @property def manager(self): return self._manager @property def resID(self): return self._resID @property def driver(self): """ :rtype: labtronyx.bases.driver.DriverBase """ return self._driver
[docs] def getProperties(self): """ Get the property dictionary for the resource. If a driver is loaded, the driver properties will be merged and returned as well :rtype: dict[str:object] """ res_prop = PluginBase.getProperties(self) res_prop.update({ 'interfaceName': self.interfaceName, 'resourceID': self._resID }) if self._driver is not None: driver_prop = self._driver.getProperties() if hasattr(driver_prop, 'uuid'): del driver_prop['uuid'] driver_prop.setdefault('driver', self._driver.fqn) driver_prop.setdefault('deviceType', self._driver.deviceType) # Resource properties take precedence over driver properties driver_prop.update(res_prop) return driver_prop else: return res_prop
#=========================================================================== # Resource State #===========================================================================
[docs] def isOpen(self): """ Check if the resource is currently open :return: bool """ return False
[docs] def open(self): """ Open the resource. If a driver is loaded, the driver `open` method will also be called :returns: True if open was successful, False otherwise :raises: ResourceUnavailable """ if self._driver is not None: return self._driver.open() else: return True
[docs] def close(self): """ Close the resource :returns: True if close was successful, False otherwise """ if self._driver is not None: return self._driver.close() else: return True
#=========================================================================== # Driver Helpers #===========================================================================
[docs] def hasDriver(self): """ Check if the resource has a driver loaded :return: bool """ return self._driver is not None
[docs] def loadDriver(self, driverName, force=False): """ Load a Driver for a resource. A driver name can be specified, even if it may not be compatible with this resource. Existing driver is no unloaded unless the `force` parameter is set to True. :param driverName: Module name of the desired Model :type driverName: str :param force: Force load driver by unloading existing driver :returns: True if successful, False otherwise :raises: KeyError if driver class not found """ if self.hasDriver() and not force: return False if force: self.unloadDriver() self.logger.debug('Loading driver [%s] for resource [%s]', driverName, self._resID) # Instantiate driver try: self._driver = self.manager.plugin_manager.createPluginInstance(driverName, resource=self, logger=self.logger) # Signal the event self.manager._publishEvent(events.EventCodes.resource.driver_loaded, self.uuid, driverName) # Call the driver open if the resource is already open if self.isOpen(): self._driver.open() except KeyError: raise KeyError("Driver not found") except: self.logger.exception('Exception during driver load: %s', driverName) self.unloadDriver() return False return True
[docs] def unloadDriver(self): """ If a driver is currently loaded for the resource, unload it. This will close the resource as well. :returns: True if successful, False otherwise """ if self._driver is not None: try: self._driver.close() except ResourceNotOpen: pass except: self.logger.exception('Exception while unloading driver') return False finally: self.close() self.manager.plugin_manager.destroyPluginInstance(self._driver.uuid) self._driver = None self.logger.debug('Unloaded driver for resource [%s]', self._resID) # Signal the event self.manager._publishEvent(events.EventCodes.resource.driver_unloaded, self.uuid) return True