2020-03-23 23:56:05 +01:00
|
|
|
from abc import ABC, abstractmethod
|
2020-03-21 22:40:39 +01:00
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Subscription(object):
|
2020-03-23 23:56:05 +01:00
|
|
|
def __init__(self, subscriptee, name, subscriber):
|
2020-03-21 22:40:39 +01:00
|
|
|
self.subscriptee = subscriptee
|
2020-03-23 23:56:05 +01:00
|
|
|
self.name = name
|
2020-03-21 22:40:39 +01:00
|
|
|
self.subscriber = subscriber
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def getName(self):
|
|
|
|
|
return self.name
|
|
|
|
|
|
2020-03-21 22:40:39 +01:00
|
|
|
def call(self, *args, **kwargs):
|
|
|
|
|
self.subscriber(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def cancel(self):
|
|
|
|
|
self.subscriptee.unwire(self)
|
|
|
|
|
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
class PropertyManager(ABC):
|
|
|
|
|
def __init__(self):
|
2020-03-21 22:40:39 +01:00
|
|
|
self.subscribers = []
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
@abstractmethod
|
|
|
|
|
def __getitem__(self, item):
|
|
|
|
|
pass
|
2020-03-21 22:40:39 +01:00
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
@abstractmethod
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def __contains__(self, item):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def __dict__(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def collect(self, *props):
|
|
|
|
|
return PropertyFilter(self, *props)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
|
|
|
|
def wire(self, callback):
|
2020-03-23 23:56:05 +01:00
|
|
|
sub = Subscription(self, None, callback)
|
2020-03-21 22:40:39 +01:00
|
|
|
self.subscribers.append(sub)
|
2020-03-23 23:56:05 +01:00
|
|
|
return sub
|
|
|
|
|
|
|
|
|
|
def wireProperty(self, name, callback):
|
|
|
|
|
sub = Subscription(self, name, callback)
|
|
|
|
|
self.subscribers.append(sub)
|
|
|
|
|
if name in self:
|
|
|
|
|
sub.call(self[name])
|
2020-03-21 22:40:39 +01:00
|
|
|
return sub
|
|
|
|
|
|
|
|
|
|
def unwire(self, sub):
|
|
|
|
|
try:
|
|
|
|
|
self.subscribers.remove(sub)
|
|
|
|
|
except ValueError:
|
|
|
|
|
# happens when already removed before
|
|
|
|
|
pass
|
|
|
|
|
return self
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def _fireCallbacks(self, name, value):
|
|
|
|
|
for c in self.subscribers:
|
|
|
|
|
try:
|
|
|
|
|
if c.getName() is None:
|
|
|
|
|
c.call(name, value)
|
|
|
|
|
elif c.getName() == name:
|
|
|
|
|
c.call(value)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.exception(e)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
class PropertyLayer(PropertyManager):
|
2020-03-21 22:40:39 +01:00
|
|
|
def __init__(self, properties=None):
|
2020-03-23 23:56:05 +01:00
|
|
|
super().__init__()
|
2020-03-21 22:40:39 +01:00
|
|
|
self.properties = {}
|
|
|
|
|
if properties is not None:
|
|
|
|
|
for (name, prop) in properties.items():
|
2020-03-23 23:56:05 +01:00
|
|
|
self._add(name, prop)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def _add(self, name, prop):
|
2020-03-21 22:40:39 +01:00
|
|
|
self.properties[name] = prop
|
2020-03-23 23:56:05 +01:00
|
|
|
self._fireCallbacks(name, prop.getValue())
|
2020-03-21 22:40:39 +01:00
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __contains__(self, name):
|
2020-03-23 23:56:05 +01:00
|
|
|
return name in self.properties
|
2020-03-21 22:40:39 +01:00
|
|
|
|
|
|
|
|
def __getitem__(self, name):
|
2020-03-23 23:56:05 +01:00
|
|
|
return self.properties[name]
|
2020-03-21 22:40:39 +01:00
|
|
|
|
|
|
|
|
def __setitem__(self, name, value):
|
2020-03-23 23:56:05 +01:00
|
|
|
logger.debug("property change: %s => %s", name, value)
|
|
|
|
|
self.properties[name] = value
|
|
|
|
|
self._fireCallbacks(name, value)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
|
|
|
|
def __dict__(self):
|
2020-03-23 23:56:05 +01:00
|
|
|
return {k: v for k, v in self.properties.items()}
|
2020-03-21 22:40:39 +01:00
|
|
|
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
class PropertyFilter(PropertyManager):
|
|
|
|
|
def __init__(self, pm: PropertyManager, *props: str):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.pm = pm
|
|
|
|
|
self.props = props
|
|
|
|
|
self.pm.wire(self.receiveEvent)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def receiveEvent(self, name, value):
|
|
|
|
|
if name not in self.props:
|
|
|
|
|
return
|
|
|
|
|
self._fireCallbacks(name, value)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def __getitem__(self, item):
|
|
|
|
|
if item not in self.props:
|
|
|
|
|
raise KeyError(item)
|
|
|
|
|
return self.pm.__getitem__(item)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def __setitem__(self, key, value):
|
|
|
|
|
if key not in self.props:
|
|
|
|
|
raise KeyError(key)
|
|
|
|
|
return self.pm.__setitem__(key, value)
|
2020-03-21 22:40:39 +01:00
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def __contains__(self, item):
|
|
|
|
|
if item not in self.props:
|
|
|
|
|
return False
|
|
|
|
|
return self.pm.__contains__(item)
|
|
|
|
|
|
|
|
|
|
def __dict__(self):
|
|
|
|
|
return {k: v for k, v in self.pm.__dict__().items() if k in self.props}
|
2020-03-22 21:51:49 +01:00
|
|
|
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
class PropertyStack(PropertyManager):
|
2020-03-22 21:51:49 +01:00
|
|
|
def __init__(self):
|
2020-03-23 23:56:05 +01:00
|
|
|
super().__init__()
|
2020-03-22 21:51:49 +01:00
|
|
|
self.layers = []
|
|
|
|
|
|
|
|
|
|
def addLayer(self, priority: int, pm: PropertyManager):
|
|
|
|
|
"""
|
|
|
|
|
highest priority = 0
|
|
|
|
|
"""
|
|
|
|
|
self.layers.append({"priority": priority, "props": pm})
|
|
|
|
|
|
|
|
|
|
def removeLayer(self, pm: PropertyManager):
|
|
|
|
|
for layer in self.layers:
|
|
|
|
|
if layer["props"] == pm:
|
|
|
|
|
self.layers.remove(layer)
|
|
|
|
|
|
2020-03-23 23:56:05 +01:00
|
|
|
def _getLayer(self, item):
|
2020-03-22 21:51:49 +01:00
|
|
|
layers = [la["props"] for la in sorted(self.layers, key=lambda l: l["priority"])]
|
|
|
|
|
for m in layers:
|
|
|
|
|
if item in m:
|
2020-03-23 23:56:05 +01:00
|
|
|
return m
|
|
|
|
|
# return top layer by default
|
|
|
|
|
return layers[0]
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, item):
|
|
|
|
|
layer = self._getLayer(item)
|
|
|
|
|
return layer.__getitem__(item)
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
|
layer = self._getLayer(key)
|
|
|
|
|
return layer.__setitem__(key, value)
|
|
|
|
|
|
|
|
|
|
def __contains__(self, item):
|
|
|
|
|
layer = self._getLayer(item)
|
|
|
|
|
return layer.__contains__(item)
|
|
|
|
|
|
|
|
|
|
def __dict__(self):
|
|
|
|
|
keys = [key for l in self.layers for key in l["props"].__dict__().keys()]
|
|
|
|
|
return {k: self.__getitem__(k) for k in keys}
|