Source code for qarbon.signal
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# This file is part of qarbon (http://qarbon.rtfd.org/)
#
# Copyright (c) 2013 European Synchrotron Radiation Facility, Grenoble, France
#
# Distributed under the terms of the GNU Lesser General Public License,
# either version 3 of the License, or (at your option) any later version.
# See LICENSE.txt for more info.
# ----------------------------------------------------------------------------
"""Simple implementation of signal/slot pattern."""
__all__ = ["Signal"]
import weakref
from . import log
from .util import callable_weakref
[docs]class Signal(object):
"""
Represents typical Signal pattern with connect, disconnect and
emit. Can be used as a descriptor. Example::
class Car(object):
temperatureChanged = Signal(float)
def set_temperature(self, temp):
self.__temp = temp
self.temperatureChanged.emit(temp)
car = Car()
def on_temp_changed(temp):
print("Car temperature changed to {0}".format(temp))
car.temperatureChanged.connect(on_temp_changed)
car.set_temperature(13.4)
"""
def __init__(self, *args, **kwargs):
self.__args = args
self.__name = kwargs.pop('name', '')
self.__emit_on_connect = kwargs.pop('emit_on_connect', True)
self.__slots = []
self.__cache = None
def __on_slot_deleted(self, slot_ref):
self.__disconnect(slot_ref)
def __disconnect(self, slot_ref):
try:
self.__slots.remove(slot_ref)
return True
except ValueError:
slot = slot_ref()
if slot is None:
log.debug("attempting to disconnect deleted slot")
else:
log.debug("slot '%s' is not connected to signal",
slot.__name__)
except:
slot = slot_ref()
if slot is None:
log.error("Exception trying to disconnect unbound slot "
"from signal", exc_info='debug')
else:
log.error("Exception trying to disconnect slot '%s' "
"from signal", slot.__name__, exc_info='debug')
return False
def __emit(self, slot, args, kwargs):
"""Emit signal to a single slot"""
try:
slot(*args, **kwargs)
except:
sname = slot.__name__
log.error("Exception emitting signal to slot '%s'",
sname, exc_info='debug')
[docs] def set_cache(self, *args, **kwargs):
"""
Fills the cache without actually emitting the signal.
Not part of the API. It is a helper method for signal owners to
use as necessary.
"""
self.__cache = args, kwargs
# -- Descriptor API -------------------------------------------------------
def __get__(self, obj, objtype=None):
self_ref = weakref.ref(self)
try:
signals = obj._qarbon_signals__
except AttributeError:
obj._qarbon_signals__ = signals = {}
try:
signal = signals[self_ref]
except KeyError:
signal = Signal(*self.__args, name=self.__name,
emit_on_connect=self.__emit_on_connect)
signals[self_ref] = signal
return signal
def __set__(self, obj, value):
raise AttributeError
def __delete__(self, obj):
try:
del obj._qarbon_signals__[weakref.ref(self)]
except KeyError:
pass
except AttributeError:
pass
# -- API ------------------------------------------------------------------
[docs] def slots(self):
"""Returns the list of connected slots."""
slots, all_slots = [], self.__slots
for slot in all_slots:
slot = slot()
if slot is not None:
slots.append(slot)
return slots
[docs] def connect(self, slot):
"""Connect a slot to this signal."""
slot_ref = callable_weakref(slot, self.__on_slot_deleted)
self.__slots.append(slot_ref)
if self.__emit_on_connect and self.__cache is not None:
self.__emit(slot, *self.__cache)
[docs] def disconnect(self, slot):
"""Disconnect the slot from this signal."""
slot_ref = callable_weakref(slot)
self.__disconnect(slot_ref)
[docs] def emit(self, *args, **kwargs):
"""emit signal."""
self.set_cache(*args, **kwargs)
slots = self.__slots
for slot in slots:
self.__emit(slot(), args, kwargs)