rewrite property engine
Property class is gone; logic is now done with Layers, Stack and Filter
This commit is contained in:
parent
7562dc8ecb
commit
c83d8580ba
@ -1,4 +1,4 @@
|
||||
from owrx.property import PropertyManager
|
||||
from owrx.property import PropertyLayer
|
||||
import importlib.util
|
||||
import os
|
||||
import logging
|
||||
@ -24,7 +24,7 @@ class Config:
|
||||
|
||||
@staticmethod
|
||||
def _loadConfig():
|
||||
pm = PropertyManager()
|
||||
pm = PropertyLayer()
|
||||
for file in ["/etc/openwebrx/config_webrx.py", "./config_webrx.py"]:
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location("config_webrx", file)
|
||||
@ -55,7 +55,7 @@ class Config:
|
||||
return [e for e in errors if e is not None]
|
||||
|
||||
@staticmethod
|
||||
def checkTempDirectory(pm: PropertyManager):
|
||||
def checkTempDirectory(pm: PropertyLayer):
|
||||
key = "temporary_directory"
|
||||
if key not in pm or pm[key] is None:
|
||||
return ConfigError(key, "temporary directory is not set")
|
||||
|
@ -10,6 +10,7 @@ from owrx.bands import Bandplan
|
||||
from owrx.bookmarks import Bookmarks
|
||||
from owrx.map import Map
|
||||
from owrx.locator import Locator
|
||||
from owrx.property import PropertyStack
|
||||
from multiprocessing import Queue
|
||||
from queue import Full
|
||||
import json
|
||||
@ -195,14 +196,14 @@ class OpenWebRxReceiverClient(Client):
|
||||
# send initial config
|
||||
self.setDspProperties(self.connectionProperties)
|
||||
|
||||
configProps = (
|
||||
self.sdr.getProps()
|
||||
.collect(*OpenWebRxReceiverClient.config_keys)
|
||||
.defaults(Config.get())
|
||||
)
|
||||
stack = PropertyStack()
|
||||
stack.addLayer(0, self.sdr.getProps())
|
||||
stack.addLayer(1, Config.get())
|
||||
configProps = stack.collect(*OpenWebRxReceiverClient.config_keys)
|
||||
|
||||
def sendConfig(key, value):
|
||||
config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys)
|
||||
#config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys)
|
||||
config = configProps.__dict__()
|
||||
# TODO mathematical properties? hmmmm
|
||||
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
|
||||
# TODO this is a hack to support multiple sdrs
|
||||
@ -253,11 +254,10 @@ class OpenWebRxReceiverClient(Client):
|
||||
if not keys:
|
||||
return
|
||||
# only the keys in the protected property manager can be overridden from the web
|
||||
protected = (
|
||||
self.sdr.getProps()
|
||||
.collect(*keys)
|
||||
.defaults(config)
|
||||
)
|
||||
stack = PropertyStack()
|
||||
stack.addLayer(0, self.sdr.getProps())
|
||||
stack.addLayer(1, config)
|
||||
protected = stack.collect(*keys)
|
||||
for key, value in params.items():
|
||||
protected[key] = value
|
||||
|
||||
|
74
owrx/dsp.py
74
owrx/dsp.py
@ -4,6 +4,7 @@ from owrx.wsjt import WsjtParser
|
||||
from owrx.aprs import AprsParser
|
||||
from owrx.pocsag import PocsagParser
|
||||
from owrx.source import SdrSource
|
||||
from owrx.property import PropertyStack
|
||||
from csdr import csdr
|
||||
import threading
|
||||
|
||||
@ -23,23 +24,30 @@ class DspManager(csdr.output):
|
||||
"pocsag_demod": PocsagParser(self.handler),
|
||||
}
|
||||
|
||||
self.localProps = (
|
||||
self.sdrSource.getProps()
|
||||
.collect(
|
||||
"audio_compression",
|
||||
"fft_compression",
|
||||
"digimodes_fft_size",
|
||||
"csdr_dynamic_bufsize",
|
||||
"csdr_print_bufsizes",
|
||||
"csdr_through",
|
||||
"digimodes_enable",
|
||||
"samp_rate",
|
||||
"digital_voice_unvoiced_quality",
|
||||
"dmr_filter",
|
||||
"temporary_directory",
|
||||
"center_freq",
|
||||
)
|
||||
.defaults(Config.get())
|
||||
stack = PropertyStack()
|
||||
stack.addLayer(0, self.sdrSource.getProps())
|
||||
stack.addLayer(1, Config.get())
|
||||
self.localProps = stack.collect(
|
||||
"audio_compression",
|
||||
"fft_compression",
|
||||
"digimodes_fft_size",
|
||||
"csdr_dynamic_bufsize",
|
||||
"csdr_print_bufsizes",
|
||||
"csdr_through",
|
||||
"digimodes_enable",
|
||||
"samp_rate",
|
||||
"digital_voice_unvoiced_quality",
|
||||
"dmr_filter",
|
||||
"temporary_directory",
|
||||
"center_freq",
|
||||
|
||||
# TODO: following properties are set from the client
|
||||
"output_rate",
|
||||
"squelch_level",
|
||||
"secondary_mod",
|
||||
"low_cut",
|
||||
"high_cut",
|
||||
"offset_freq",
|
||||
)
|
||||
|
||||
self.dsp = csdr.dsp(self)
|
||||
@ -61,19 +69,19 @@ class DspManager(csdr.output):
|
||||
parser.setDialFrequency(freq)
|
||||
|
||||
self.subscriptions = [
|
||||
self.localProps.getProperty("audio_compression").wire(self.dsp.set_audio_compression),
|
||||
self.localProps.getProperty("fft_compression").wire(self.dsp.set_fft_compression),
|
||||
self.localProps.getProperty("digimodes_fft_size").wire(self.dsp.set_secondary_fft_size),
|
||||
self.localProps.getProperty("samp_rate").wire(self.dsp.set_samp_rate),
|
||||
self.localProps.getProperty("output_rate").wire(self.dsp.set_output_rate),
|
||||
self.localProps.getProperty("offset_freq").wire(self.dsp.set_offset_freq),
|
||||
self.localProps.getProperty("squelch_level").wire(self.dsp.set_squelch_level),
|
||||
self.localProps.getProperty("low_cut").wire(set_low_cut),
|
||||
self.localProps.getProperty("high_cut").wire(set_high_cut),
|
||||
self.localProps.getProperty("mod").wire(self.dsp.set_demodulator),
|
||||
self.localProps.getProperty("digital_voice_unvoiced_quality").wire(self.dsp.set_unvoiced_quality),
|
||||
self.localProps.getProperty("dmr_filter").wire(self.dsp.set_dmr_filter),
|
||||
self.localProps.getProperty("temporary_directory").wire(self.dsp.set_temporary_directory),
|
||||
self.localProps.wireProperty("audio_compression", self.dsp.set_audio_compression),
|
||||
self.localProps.wireProperty("fft_compression", self.dsp.set_fft_compression),
|
||||
self.localProps.wireProperty("digimodes_fft_size", self.dsp.set_secondary_fft_size),
|
||||
self.localProps.wireProperty("samp_rate", self.dsp.set_samp_rate),
|
||||
self.localProps.wireProperty("output_rate", self.dsp.set_output_rate),
|
||||
self.localProps.wireProperty("offset_freq", self.dsp.set_offset_freq),
|
||||
self.localProps.wireProperty("squelch_level", self.dsp.set_squelch_level),
|
||||
self.localProps.wireProperty("low_cut", set_low_cut),
|
||||
self.localProps.wireProperty("high_cut", set_high_cut),
|
||||
self.localProps.wireProperty("mod", self.dsp.set_demodulator),
|
||||
self.localProps.wireProperty("digital_voice_unvoiced_quality", self.dsp.set_unvoiced_quality),
|
||||
self.localProps.wireProperty("dmr_filter", self.dsp.set_dmr_filter),
|
||||
self.localProps.wireProperty("temporary_directory", self.dsp.set_temporary_directory),
|
||||
self.localProps.collect("center_freq", "offset_freq").wire(set_dial_freq),
|
||||
]
|
||||
|
||||
@ -99,8 +107,8 @@ class DspManager(csdr.output):
|
||||
)
|
||||
|
||||
self.subscriptions += [
|
||||
self.localProps.getProperty("secondary_mod").wire(set_secondary_mod),
|
||||
self.localProps.getProperty("secondary_offset_freq").wire(self.dsp.set_secondary_offset_freq),
|
||||
self.localProps.collect("secondary_mod").wire(set_secondary_mod),
|
||||
self.localProps.collect("secondary_offset_freq").wire(self.dsp.set_secondary_offset_freq),
|
||||
]
|
||||
|
||||
self.sdrSource.addClient(self)
|
||||
@ -134,7 +142,7 @@ class DspManager(csdr.output):
|
||||
self.subscriptions = []
|
||||
|
||||
def setProperty(self, prop, value):
|
||||
self.localProps.getProperty(prop).setValue(value)
|
||||
self.localProps[prop] = value
|
||||
|
||||
def getClientClass(self):
|
||||
return SdrSource.CLIENT_USER
|
||||
|
18
owrx/fft.py
18
owrx/fft.py
@ -2,6 +2,7 @@ from owrx.config import Config
|
||||
from csdr import csdr
|
||||
import threading
|
||||
from owrx.source import SdrSource
|
||||
from owrx.property import PropertyStack
|
||||
|
||||
import logging
|
||||
|
||||
@ -13,7 +14,10 @@ class SpectrumThread(csdr.output):
|
||||
self.sdrSource = sdrSource
|
||||
super().__init__()
|
||||
|
||||
self.props = props = self.sdrSource.props.collect(
|
||||
stack = PropertyStack()
|
||||
stack.addLayer(0, self.sdrSource.props)
|
||||
stack.addLayer(1, Config.get())
|
||||
self.props = props = stack.collect(
|
||||
"samp_rate",
|
||||
"fft_size",
|
||||
"fft_fps",
|
||||
@ -23,7 +27,7 @@ class SpectrumThread(csdr.output):
|
||||
"csdr_print_bufsizes",
|
||||
"csdr_through",
|
||||
"temporary_directory",
|
||||
).defaults(Config.get())
|
||||
)
|
||||
|
||||
self.dsp = dsp = csdr.dsp(self)
|
||||
dsp.nc_port = self.sdrSource.getPort()
|
||||
@ -42,11 +46,11 @@ class SpectrumThread(csdr.output):
|
||||
)
|
||||
|
||||
self.subscriptions = [
|
||||
props.getProperty("samp_rate").wire(dsp.set_samp_rate),
|
||||
props.getProperty("fft_size").wire(dsp.set_fft_size),
|
||||
props.getProperty("fft_fps").wire(dsp.set_fft_fps),
|
||||
props.getProperty("fft_compression").wire(dsp.set_fft_compression),
|
||||
props.getProperty("temporary_directory").wire(dsp.set_temporary_directory),
|
||||
props.wireProperty("samp_rate", dsp.set_samp_rate),
|
||||
props.wireProperty("fft_size", dsp.set_fft_size),
|
||||
props.wireProperty("fft_fps", dsp.set_fft_fps),
|
||||
props.wireProperty("fft_compression", dsp.set_fft_compression),
|
||||
props.wireProperty("temporary_directory", dsp.set_temporary_directory),
|
||||
props.collect("samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor").wire(set_fft_averages),
|
||||
]
|
||||
|
||||
|
@ -1,13 +1,18 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Subscription(object):
|
||||
def __init__(self, subscriptee, subscriber):
|
||||
def __init__(self, subscriptee, name, subscriber):
|
||||
self.subscriptee = subscriptee
|
||||
self.name = name
|
||||
self.subscriber = subscriber
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
self.subscriber(*args, **kwargs)
|
||||
|
||||
@ -15,30 +20,39 @@ class Subscription(object):
|
||||
self.subscriptee.unwire(self)
|
||||
|
||||
|
||||
class Property(object):
|
||||
def __init__(self, value=None):
|
||||
self.value = value
|
||||
class PropertyManager(ABC):
|
||||
def __init__(self):
|
||||
self.subscribers = []
|
||||
|
||||
def getValue(self):
|
||||
return self.value
|
||||
@abstractmethod
|
||||
def __getitem__(self, item):
|
||||
pass
|
||||
|
||||
def setValue(self, value):
|
||||
if self.value == value:
|
||||
return self
|
||||
self.value = value
|
||||
for c in self.subscribers:
|
||||
try:
|
||||
c.call(self.value)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return self
|
||||
@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)
|
||||
|
||||
def wire(self, callback):
|
||||
sub = Subscription(self, callback)
|
||||
sub = Subscription(self, None, callback)
|
||||
self.subscribers.append(sub)
|
||||
if self.value is not None:
|
||||
sub.call(self.value)
|
||||
return sub
|
||||
|
||||
def wireProperty(self, name, callback):
|
||||
sub = Subscription(self, name, callback)
|
||||
self.subscribers.append(sub)
|
||||
if name in self:
|
||||
sub.call(self[name])
|
||||
return sub
|
||||
|
||||
def unwire(self, sub):
|
||||
@ -49,80 +63,79 @@ class Property(object):
|
||||
pass
|
||||
return self
|
||||
|
||||
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)
|
||||
|
||||
class PropertyManager(object):
|
||||
def collect(self, *props):
|
||||
return PropertyManager(
|
||||
{name: self.getProperty(name) if self.hasProperty(name) else Property() for name in props}
|
||||
)
|
||||
|
||||
class PropertyLayer(PropertyManager):
|
||||
def __init__(self, properties=None):
|
||||
super().__init__()
|
||||
self.properties = {}
|
||||
self.subscribers = []
|
||||
if properties is not None:
|
||||
for (name, prop) in properties.items():
|
||||
self.add(name, prop)
|
||||
self._add(name, prop)
|
||||
|
||||
def add(self, name, prop):
|
||||
def _add(self, name, prop):
|
||||
self.properties[name] = prop
|
||||
|
||||
def fireCallbacks(value):
|
||||
for c in self.subscribers:
|
||||
try:
|
||||
c.call(name, value)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
prop.wire(fireCallbacks)
|
||||
self._fireCallbacks(name, prop.getValue())
|
||||
return self
|
||||
|
||||
def __contains__(self, name):
|
||||
return self.hasProperty(name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.getPropertyValue(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
if not self.hasProperty(name):
|
||||
self.add(name, Property())
|
||||
self.getProperty(name).setValue(value)
|
||||
|
||||
def __dict__(self):
|
||||
return {k: v.getValue() for k, v in self.properties.items()}
|
||||
|
||||
def hasProperty(self, name):
|
||||
return name in self.properties
|
||||
|
||||
def getProperty(self, name):
|
||||
if not self.hasProperty(name):
|
||||
self.add(name, Property())
|
||||
def __getitem__(self, name):
|
||||
return self.properties[name]
|
||||
|
||||
def getPropertyValue(self, name):
|
||||
return self.getProperty(name).getValue()
|
||||
def __setitem__(self, name, value):
|
||||
logger.debug("property change: %s => %s", name, value)
|
||||
self.properties[name] = value
|
||||
self._fireCallbacks(name, value)
|
||||
|
||||
def wire(self, callback):
|
||||
sub = Subscription(self, callback)
|
||||
self.subscribers.append(sub)
|
||||
return sub
|
||||
|
||||
def unwire(self, sub):
|
||||
try:
|
||||
self.subscribers.remove(sub)
|
||||
except ValueError:
|
||||
# happens when already removed before
|
||||
pass
|
||||
return self
|
||||
|
||||
def defaults(self, other_pm):
|
||||
for (key, p) in self.properties.items():
|
||||
if p.getValue() is None:
|
||||
p.setValue(other_pm[key])
|
||||
return self
|
||||
def __dict__(self):
|
||||
return {k: v for k, v in self.properties.items()}
|
||||
|
||||
|
||||
class PropertyLayers(object):
|
||||
class PropertyFilter(PropertyManager):
|
||||
def __init__(self, pm: PropertyManager, *props: str):
|
||||
super().__init__()
|
||||
self.pm = pm
|
||||
self.props = props
|
||||
self.pm.wire(self.receiveEvent)
|
||||
|
||||
def receiveEvent(self, name, value):
|
||||
if name not in self.props:
|
||||
return
|
||||
self._fireCallbacks(name, value)
|
||||
|
||||
def __getitem__(self, item):
|
||||
if item not in self.props:
|
||||
raise KeyError(item)
|
||||
return self.pm.__getitem__(item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self.props:
|
||||
raise KeyError(key)
|
||||
return self.pm.__setitem__(key, value)
|
||||
|
||||
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}
|
||||
|
||||
|
||||
class PropertyStack(PropertyManager):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.layers = []
|
||||
|
||||
def addLayer(self, priority: int, pm: PropertyManager):
|
||||
@ -136,8 +149,26 @@ class PropertyLayers(object):
|
||||
if layer["props"] == pm:
|
||||
self.layers.remove(layer)
|
||||
|
||||
def __getitem__(self, item):
|
||||
def _getLayer(self, item):
|
||||
layers = [la["props"] for la in sorted(self.layers, key=lambda l: l["priority"])]
|
||||
for m in layers:
|
||||
if item in m:
|
||||
return m[item]
|
||||
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}
|
||||
|
@ -1,5 +1,5 @@
|
||||
from owrx.config import Config
|
||||
from owrx.property import PropertyManager
|
||||
from owrx.property import PropertyLayer
|
||||
from owrx.feature import FeatureDetector, UnknownFeatureException
|
||||
|
||||
import logging
|
||||
@ -19,7 +19,7 @@ class SdrService(object):
|
||||
featureDetector = FeatureDetector()
|
||||
|
||||
def loadIntoPropertyManager(dict: dict):
|
||||
propertyManager = PropertyManager()
|
||||
propertyManager = PropertyLayer()
|
||||
for (name, value) in dict.items():
|
||||
propertyManager[name] = value
|
||||
return propertyManager
|
||||
|
@ -8,7 +8,7 @@ from owrx.aprs import AprsParser
|
||||
from owrx.config import Config
|
||||
from owrx.source.resampler import Resampler
|
||||
from owrx.feature import FeatureDetector
|
||||
from owrx.property import PropertyManager
|
||||
from owrx.property import PropertyLayer
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from .schedule import ServiceScheduler
|
||||
|
||||
@ -164,7 +164,7 @@ class ServiceHandler(object):
|
||||
cf = (min + max) / 2
|
||||
bw = max - min
|
||||
logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw))
|
||||
resampler_props = PropertyManager()
|
||||
resampler_props = PropertyLayer()
|
||||
resampler_props["center_freq"] = cf
|
||||
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
|
||||
resampler_props["samp_rate"] = bw + 24000
|
||||
|
@ -9,6 +9,7 @@ import signal
|
||||
from abc import ABC, abstractmethod
|
||||
from owrx.command import CommandMapper
|
||||
from owrx.socket import getAvailablePort
|
||||
from owrx.property import PropertyStack
|
||||
|
||||
import logging
|
||||
|
||||
@ -35,7 +36,10 @@ class SdrSource(ABC):
|
||||
self.props = props
|
||||
self.profile_id = None
|
||||
self.activateProfile()
|
||||
self.rtlProps = self.props.collect(*self.getEventNames()).defaults(Config.get())
|
||||
stack = PropertyStack()
|
||||
stack.addLayer(0, self.props)
|
||||
stack.addLayer(1, Config.get())
|
||||
self.rtlProps = stack.collect(*self.getEventNames())
|
||||
self.wireEvents()
|
||||
self.commandMapper = None
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
from owrx.property import Property
|
||||
|
||||
|
||||
class PropertyTest(unittest.TestCase):
|
||||
def testValue(self):
|
||||
prop = Property("testvalue")
|
||||
self.assertEqual(prop.getValue(), "testvalue")
|
||||
|
||||
def testChangeValue(self):
|
||||
prop = Property("before")
|
||||
prop.setValue("after")
|
||||
self.assertEqual(prop.getValue(), "after")
|
||||
|
||||
def testInitialValueOnCallback(self):
|
||||
prop = Property("before")
|
||||
m = Mock()
|
||||
prop.wire(m.method)
|
||||
m.method.assert_called_once_with("before")
|
||||
|
||||
def testChangedValueOnCallback(self):
|
||||
prop = Property("before")
|
||||
m = Mock()
|
||||
prop.wire(m.method)
|
||||
m.reset_mock()
|
||||
prop.setValue("after")
|
||||
m.method.assert_called_with("after")
|
51
test/property/test_property_filter.py
Normal file
51
test/property/test_property_filter.py
Normal file
@ -0,0 +1,51 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
from owrx.property import PropertyLayer, PropertyFilter
|
||||
|
||||
|
||||
class PropertyFilterTest(TestCase):
|
||||
|
||||
def testPassesProperty(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "testvalue"
|
||||
pf = PropertyFilter(pm, "testkey")
|
||||
self.assertEqual(pf["testkey"], "testvalue")
|
||||
|
||||
def testMissesPropert(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "testvalue"
|
||||
pf = PropertyFilter(pm, "other_key")
|
||||
self.assertFalse("testkey" in pf)
|
||||
with self.assertRaises(KeyError):
|
||||
x = pf["testkey"]
|
||||
|
||||
def testForwardsEvent(self):
|
||||
pm = PropertyLayer()
|
||||
pf = PropertyFilter(pm, "testkey")
|
||||
mock = Mock()
|
||||
pf.wire(mock.method)
|
||||
pm["testkey"] = "testvalue"
|
||||
mock.method.assert_called_once_with("testkey", "testvalue")
|
||||
|
||||
def testForwardsPropertyEvent(self):
|
||||
pm = PropertyLayer()
|
||||
pf = PropertyFilter(pm, "testkey")
|
||||
mock = Mock()
|
||||
pf.wireProperty("testkey", mock.method)
|
||||
pm["testkey"] = "testvalue"
|
||||
mock.method.assert_called_once_with("testvalue")
|
||||
|
||||
def testForwardsWrite(self):
|
||||
pm = PropertyLayer()
|
||||
pf = PropertyFilter(pm, "testkey")
|
||||
pf["testkey"] = "testvalue"
|
||||
self.assertTrue("testkey" in pm)
|
||||
self.assertEqual(pm["testkey"], "testvalue")
|
||||
|
||||
def testOverwrite(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "old value"
|
||||
pf = PropertyFilter(pm, "testkey")
|
||||
pf["testkey"] = "new value"
|
||||
self.assertEqual(pm["testkey"], "new value")
|
||||
self.assertEqual(pf["testkey"], "new value")
|
52
test/property/test_property_layer.py
Normal file
52
test/property/test_property_layer.py
Normal file
@ -0,0 +1,52 @@
|
||||
from owrx.property import PropertyLayer
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
|
||||
|
||||
class PropertyLayerTest(TestCase):
|
||||
def testKeyIsset(self):
|
||||
pm = PropertyLayer()
|
||||
self.assertFalse("some_key" in pm)
|
||||
|
||||
def testKeyError(self):
|
||||
pm = PropertyLayer()
|
||||
with self.assertRaises(KeyError):
|
||||
x = pm["some_key"]
|
||||
|
||||
def testSubscription(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "before"
|
||||
mock = Mock()
|
||||
pm.wire(mock.method)
|
||||
pm["testkey"] = "after"
|
||||
mock.method.assert_called_once_with("testkey", "after")
|
||||
|
||||
def testUnsubscribe(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "before"
|
||||
mock = Mock()
|
||||
sub = pm.wire(mock.method)
|
||||
pm["testkey"] = "between"
|
||||
mock.method.assert_called_once_with("testkey", "between")
|
||||
|
||||
mock.reset_mock()
|
||||
pm.unwire(sub)
|
||||
pm["testkey"] = "after"
|
||||
mock.method.assert_not_called()
|
||||
|
||||
def testContains(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "value"
|
||||
self.assertTrue("testkey" in pm)
|
||||
|
||||
def testDoesNotContain(self):
|
||||
pm = PropertyLayer()
|
||||
self.assertFalse("testkey" in pm)
|
||||
|
||||
def testSubscribeBeforeSet(self):
|
||||
pm = PropertyLayer()
|
||||
mock = Mock()
|
||||
pm.wireProperty("testkey", mock.method)
|
||||
mock.method.assert_not_called()
|
||||
pm["testkey"] = "newvalue"
|
||||
mock.method.assert_called_once_with("newvalue")
|
@ -1,20 +1,20 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
from owrx.property import PropertyManager, PropertyLayers
|
||||
from owrx.property import PropertyLayer, PropertyStack
|
||||
|
||||
|
||||
class TestPropertyLayers(TestCase):
|
||||
class PropertyStackTest(TestCase):
|
||||
def testLayer(self):
|
||||
om = PropertyLayers()
|
||||
pm = PropertyManager()
|
||||
om = PropertyStack()
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "testvalue"
|
||||
om.addLayer(1, pm)
|
||||
self.assertEqual(om["testkey"], "testvalue")
|
||||
|
||||
def testHighPriority(self):
|
||||
om = PropertyLayers()
|
||||
low_pm = PropertyManager()
|
||||
high_pm = PropertyManager()
|
||||
om = PropertyStack()
|
||||
low_pm = PropertyLayer()
|
||||
high_pm = PropertyLayer()
|
||||
low_pm["testkey"] = "low value"
|
||||
high_pm["testkey"] = "high value"
|
||||
om.addLayer(1, low_pm)
|
||||
@ -22,18 +22,18 @@ class TestPropertyLayers(TestCase):
|
||||
self.assertEqual(om["testkey"], "high value")
|
||||
|
||||
def testPriorityFallback(self):
|
||||
om = PropertyLayers()
|
||||
low_pm = PropertyManager()
|
||||
high_pm = PropertyManager()
|
||||
om = PropertyStack()
|
||||
low_pm = PropertyLayer()
|
||||
high_pm = PropertyLayer()
|
||||
low_pm["testkey"] = "low value"
|
||||
om.addLayer(1, low_pm)
|
||||
om.addLayer(0, high_pm)
|
||||
self.assertEqual(om["testkey"], "low value")
|
||||
|
||||
def testLayerRemoval(self):
|
||||
om = PropertyLayers()
|
||||
low_pm = PropertyManager()
|
||||
high_pm = PropertyManager()
|
||||
om = PropertyStack()
|
||||
low_pm = PropertyLayer()
|
||||
high_pm = PropertyLayer()
|
||||
low_pm["testkey"] = "low value"
|
||||
high_pm["testkey"] = "high value"
|
||||
om.addLayer(1, low_pm)
|
Loading…
Reference in New Issue
Block a user