Changeset - 066f05ad7900
[Not reviewed]
default
0 5 0
drewp@bigasterisk.com - 20 months ago 2023-05-19 19:11:26
drewp@bigasterisk.com
collector: even stronger types; repair test code (some are failing)
5 files changed with 108 insertions and 159 deletions:
0 comments (0 inline, 0 general)
light9/collector/collector.py
Show inline comments
 
@@ -9,8 +9,8 @@ from light9.collector.device import reso
 
from light9.collector.output import Output as OutputInstance
 
from light9.collector.weblisteners import WebListeners
 
from light9.namespaces import L9, RDF
 
from light9.newtypes import (ClientSessionType, ClientType, DeviceAttr, DeviceClass, DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr, OutputRange, OutputUri,
 
                             OutputValue, UnixTime, typedValue)
 
from light9.newtypes import (ClientSessionType, ClientType, DeviceAttr, DeviceClass, DeviceSetting, DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr,
 
                             OutputRange, OutputUri, OutputValue, UnixTime, VTUnion, typedValue)
 

	
 
log = logging.getLogger('collector')
 

	
 
@@ -69,10 +69,10 @@ class Collector:
 
        self.graph.addHandler(self.rebuildOutputMap)
 

	
 
        # client : (session, time, {(dev,devattr): latestValue})
 
        self.lastRequest: Dict[Tuple[ClientType, ClientSessionType], Tuple[UnixTime, Dict[Tuple[DeviceUri, DeviceAttr], float]]] = {}
 
        self.lastRequest: Dict[Tuple[ClientType, ClientSessionType], Tuple[UnixTime, Dict[Tuple[DeviceUri, DeviceAttr], VTUnion]]] = {}
 

	
 
        # (dev, devAttr): value to use instead of 0
 
        self.stickyAttrs: Dict[Tuple[DeviceUri, DeviceAttr], float] = {}
 
        self.stickyAttrs: Dict[Tuple[DeviceUri, DeviceAttr], VTUnion] = {}
 

	
 
    def rebuildOutputMap(self):
 
        self.outputMap = outputMap(self.graph, self.outputs)
 
@@ -102,8 +102,8 @@ class Collector:
 
            del self.lastRequest[c]
 

	
 
    # todo: move to settings.py
 
    def resolvedSettingsDict(self, settingsList: List[Tuple[DeviceUri, DeviceAttr, float]]) -> Dict[Tuple[DeviceUri, DeviceAttr], float]:
 
        out: Dict[Tuple[DeviceUri, DeviceAttr], float] = {}
 
    def resolvedSettingsDict(self, settingsList: List[DeviceSetting]) -> Dict[Tuple[DeviceUri, DeviceAttr], VTUnion]:
 
        out: Dict[Tuple[DeviceUri, DeviceAttr], VTUnion] = {}
 
        for d, da, v in settingsList:
 
            if (d, da) in out:
 
                out[(d, da)] = resolve(d, da, [out[(d, da)], v])
 
@@ -118,12 +118,12 @@ class Collector:
 
            log.warn('collector.setAttrs from %s is running %.1fms after the request was made', client, requestLag * 1000)
 

	
 
    def _merge(self, lastRequests):
 
        deviceAttrs: Dict[DeviceUri, Dict[DeviceAttr, float]] = {}  # device: {deviceAttr: value}
 
        deviceAttrs: Dict[DeviceUri, Dict[DeviceAttr, VTUnion]] = {}  # device: {deviceAttr: value}
 
        for _, lastSettings in lastRequests:
 
            for (device, deviceAttr), value in lastSettings.items():
 
                if (device, deviceAttr) in self.remapOut:
 
                    start, end = self.remapOut[(device, deviceAttr)]
 
                    value = Literal(start + float(value) * (end - start))
 
                    value = start + float(value) * (end - start)
 

	
 
                attrs = deviceAttrs.setdefault(device, {})
 
                if deviceAttr in attrs:
 
@@ -143,7 +143,7 @@ class Collector:
 

	
 
        return deviceAttrs
 

	
 
    def setAttrs(self, client: ClientType, clientSession: ClientSessionType, settings: List[Tuple[DeviceUri, DeviceAttr, float]], sendTime: UnixTime):
 
    def setAttrs(self, client: ClientType, clientSession: ClientSessionType, settings: List[DeviceSetting], sendTime: UnixTime):
 
        """
 
        settings is a list of (device, attr, value). These attrs are
 
        device attrs. We resolve conflicting values, process them into
 
@@ -178,7 +178,7 @@ class Collector:
 
            except Exception as e:
 
                log.error('failing toOutputAttrs on %s: %r', d, e)
 

	
 
        pendingOut: Dict[OutputUri, Tuple[OutputInstance, bytearray]] = {}
 
        pendingOut = cast(Dict[OutputUri, Tuple[OutputInstance, bytearray]], {})
 
        for out in self.outputs:
 
            pendingOut[OutputUri(out.uri)] = (out, bytearray(512))
 

	
light9/collector/collector_test.py
Show inline comments
 
import unittest
 
import datetime, time
 
from freezegun import freeze_time
 
from light9.collector.output import Output
 
from light9.collector.weblisteners import WebListeners
 
from rdflib import Namespace
 

	
 
from light9.namespaces import L9, DEV
 
from light9.collector.collector import Collector, outputMap
 
from rdfdb.mock_syncedgraph import MockSyncedGraph
 
from light9.mock_syncedgraph import MockSyncedGraph
 
from light9.newtypes import ClientSessionType, ClientType, DeviceAttr, DeviceUri, HexColor, UnixTime
 

	
 
UDMX = Namespace('http://light9.bigasterisk.com/output/udmx/')
 
DMX0 = Namespace('http://light9.bigasterisk.com/output/dmx0/')
 
@@ -35,10 +38,18 @@ THEATER = '''
 

	
 
'''
 

	
 
t0 = 0  # time
 
t0 = UnixTime(0)
 
client1 = ClientType('client1')
 
client2 = ClientType('client2')
 
session1 = ClientSessionType('sess1')
 
session2 = ClientSessionType('sess2')
 
colorStrip = DeviceUri(DEV['colorStrip'])
 
color = DeviceAttr(L9['color'])
 
brightness = DeviceAttr(L9['brightness'])
 
inst1 = DeviceUri(DEV['inst1'])
 

	
 

	
 
class MockOutput(object):
 
class MockOutput(Output):
 

	
 
    def __init__(self, uri, connections):
 
        self.connections = connections
 
@@ -46,54 +57,14 @@ class MockOutput(object):
 
        self.uri = uri
 
        self.numChannels = 4
 

	
 
    def allConnections(self):
 
        return self.connections
 

	
 
    def update(self, values):
 
        self.updates.append(values)
 

	
 
    def flush(self):
 
        self.updates.append('flush')
 
        self.updates.append(list(values[:self.numChannels]))
 

	
 

	
 
@unittest.skip("outputMap got rewritten and mostly doesn't raise on these cases"
 
              )
 
class TestOutputMap(unittest.TestCase):
 

	
 
    def testWorking(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        m = outputMap(
 
            MockSyncedGraph(PREFIX + '''
 
          dmx0:c1 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 
        self.assertEqual({(DEV['inst1'], L9['brightness']): (out0, 0)}, m)
 
class MockWebListeners(WebListeners):
 

	
 
    def testMissingOutput(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(
 
            KeyError, outputMap,
 
            MockSyncedGraph(PREFIX + '''
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 
    def testMissingOutputConnection(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(
 
            ValueError, outputMap,
 
            MockSyncedGraph(PREFIX + '''
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 
    def testMultipleOutputConnections(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(
 
            ValueError, outputMap,
 
            MockSyncedGraph(PREFIX + '''
 
          dmx0:c1 :connectedTo dev:inst1Brightness .
 
          dmx0:c2 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 
    def __init__(self):
 
        "do not init"
 

	
 

	
 
class TestCollector(unittest.TestCase):
 
@@ -113,73 +84,58 @@ class TestCollector(unittest.TestCase):
 
          :level dev:inst1Brightness .
 
        ''')
 

	
 
        self.dmx0 = MockOutput(DMX0[None], [(0, DMX0['c1'])])
 
        self.udmx = MockOutput(UDMX[None], [(0, UDMX['c1']), (1, UDMX['c2']),
 
                                            (2, UDMX['c3']), (3, UDMX['c4'])])
 
        self.dmx0 = MockOutput(DMX0, [(0, DMX0['c1'])])
 
        self.udmx = MockOutput(UDMX, [(0, UDMX['c1']), (1, UDMX['c2']), (2, UDMX['c3']), (3, UDMX['c4'])])
 

	
 
    def testRoutesColorOutput(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 

	
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#00ff00'))], t0)
 

	
 
        c.setAttrs('client', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 

	
 
        self.assertEqual([[215, 0, 255, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush'], self.dmx0.updates)
 
        self.assertEqual([
 
            [215, 0, 255, 0],
 
        ], self.udmx.updates)
 
        self.assertEqual([
 
            [0, 0, 0, 0],
 
        ], self.dmx0.updates)
 

	
 
    def testOutputMaxOfTwoClients(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')], t0)
 
        c.setAttrs('client2', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#333333')], t0)
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#ff0000'))], t0)
 
        c.setAttrs(client2, session1, [(colorStrip, color, HexColor('#333333'))], t0)
 

	
 
        self.assertEqual(
 
            [[215, 255, 0, 0], 'flush', [215, 255, 51, 51], 'flush'],
 
            self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 
        self.assertEqual([[215, 255, 0, 0], [215, 255, 51, 51]], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], [0, 0, 0, 0]], self.dmx0.updates)
 

	
 
    def testClientOnSameOutputIsRememberedOverCalls(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#080000')], t0)
 
        c.setAttrs('client2', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#060000')], t0)
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#050000')], t0)
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#080000'))], t0)
 
        c.setAttrs(client2, session1, [(colorStrip, color, HexColor('#060000'))], t0)
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#050000'))], t0)
 

	
 
        self.assertEqual([[215, 8, 0, 0], 'flush', [215, 8, 0, 0], 'flush',
 
                          [215, 6, 0, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[215, 8, 0, 0], [215, 8, 0, 0], [215, 6, 0, 0]], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], self.dmx0.updates)
 

	
 
    def testClientsOnDifferentOutputs(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#aa0000')], t0)
 
        c.setAttrs('client2', 'sess1', [(DEV['inst1'], L9['brightness'], .5)],
 
                   t0)
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#aa0000'))], t0)
 
        c.setAttrs(client2, session1, [(inst1, brightness, .5)], t0)
 

	
 
        # ok that udmx is flushed twice- it can screen out its own duplicates
 
        self.assertEqual([[215, 170, 0, 0], 'flush', [215, 170, 0, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 
        self.assertEqual([[215, 170, 0, 0], [215, 170, 0, 0]], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], [127, 0, 0, 0]], self.dmx0.updates)
 

	
 
    def testNewSessionReplacesPreviousOutput(self):
 
        # ..as opposed to getting max'd with it
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 

	
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .8)],
 
                   t0)
 
        c.setAttrs('client1', 'sess2', [(DEV['inst1'], L9['brightness'], .5)],
 
                   t0)
 
        c.setAttrs(client1, session1, [(inst1, brightness, .8)], t0)
 
        c.setAttrs(client1, session2, [(inst1, brightness, .5)], t0)
 

	
 
        self.assertEqual([[204, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 
        self.assertEqual([[204, 0, 0, 0], [127, 0, 0, 0]], self.dmx0.updates)
 

	
 
    def testNewSessionDropsPreviousSettingsOfOtherAttrs(self):
 
        c = Collector(MockSyncedGraph(PREFIX + THEATER + '''
 
@@ -195,60 +151,48 @@ class TestCollector(unittest.TestCase):
 
          :dmxUniverse dmx0:; :dmxBase 0;
 
          :level dev:inst1Brightness .
 
        '''),
 
                      outputs=[self.dmx0, self.udmx])
 
                      outputs=[self.dmx0, self.udmx],
 
                      listeners=MockWebListeners())
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')], t0)
 
        c.setAttrs('client1', 'sess2',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#ff0000'))], t0)
 
        c.setAttrs(client1, session2, [(colorStrip, color, HexColor('#00ff00'))], t0)
 

	
 
        self.assertEqual([[215, 255, 0, 0], 'flush', [215, 0, 255, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([[215, 255, 0, 0], [215, 0, 255, 0]], self.udmx.updates)
 

	
 
    def testClientIsForgottenAfterAWhile(self):
 
        with freeze_time(datetime.datetime.now()) as ft:
 
            c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
            c.setAttrs('cli1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)],
 
                       time.time())
 
            c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 
            c.setAttrs(client1, session1, [(inst1, brightness, .5)], UnixTime(time.time()))
 
            ft.tick(delta=datetime.timedelta(seconds=1))
 
            # this max's with cli1's value so we still see .5
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .2)],
 
                       time.time())
 
            c.setAttrs(client2, session1, [(inst1, brightness, .2)], UnixTime(time.time()))
 
            ft.tick(delta=datetime.timedelta(seconds=9.1))
 
            # now cli1 is forgotten, so our value appears
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .4)],
 
                       time.time())
 
            self.assertEqual([[127, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush',
 
                              [102, 0, 0, 0], 'flush'], self.dmx0.updates)
 
            c.setAttrs(client2, session1, [(inst1, brightness, .4)], UnixTime(time.time()))
 
            self.assertEqual([[127, 0, 0, 0], [127, 0, 0, 0], [102, 0, 0, 0]], self.dmx0.updates)
 

	
 
    def testClientUpdatesAreNotMerged(self):
 
        # second call to setAttrs forgets the first
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        t0 = time.time()
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)],
 
                   t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], 1)],
 
                   t0)
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 
        t0 = UnixTime(time.time())
 
        c.setAttrs(client1, session1, [(inst1, brightness, .5)], t0)
 
        c.setAttrs(client1, session1, [(inst1, brightness, 1)], t0)
 
        c.setAttrs(client1, session1, [(colorStrip, color, HexColor('#00ff00'))], t0)
 

	
 
        self.assertEqual([[215, 0, 0, 0], 'flush', [215, 0, 0, 0], 'flush',
 
                          [215, 0, 255, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[127, 0, 0, 0], 'flush', [255, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[215, 0, 0, 0], [215, 0, 0, 0], [215, 0, 255, 0]], self.udmx.updates)
 
        self.assertEqual([[127, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0]], self.dmx0.updates)
 

	
 
    def testRepeatedAttributesInOneRequestGetResolved(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx], listeners=MockWebListeners())
 

	
 
        c.setAttrs('client1', 'sess1', [
 
            (DEV['inst1'], L9['brightness'], .5),
 
            (DEV['inst1'], L9['brightness'], .3),
 
        c.setAttrs(client1, session1, [
 
            (inst1, brightness, .5),
 
            (inst1, brightness, .3),
 
        ], t0)
 
        self.assertEqual([[127, 0, 0, 0], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[127, 0, 0, 0]], self.dmx0.updates)
 

	
 
        c.setAttrs('client1', 'sess1', [
 
            (DEV['inst1'], L9['brightness'], .3),
 
            (DEV['inst1'], L9['brightness'], .5),
 
        c.setAttrs(client1, session1, [
 
            (inst1, brightness, .3),
 
            (inst1, brightness, .5),
 
        ], t0)
 
        self.assertEqual([[127, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 
        self.assertEqual([[127, 0, 0, 0], [127, 0, 0, 0]], self.dmx0.updates)
light9/collector/device.py
Show inline comments
 
@@ -5,7 +5,7 @@ from rdflib import Literal, URIRef
 
from webcolors import hex_to_rgb, rgb_to_hex
 
from colormath.color_objects import sRGBColor, CMYColor
 
import colormath.color_conversions
 
from light9.newtypes import OutputAttr, OutputValue, DeviceUri, DeviceAttr
 
from light9.newtypes import VT, HexColor, OutputAttr, OutputValue, DeviceUri, DeviceAttr, VTUnion
 

	
 
log = logging.getLogger('device')
 

	
 
@@ -44,19 +44,16 @@ def _8bit(f):
 
    return clamp255(int(f * 255))
 

	
 

	
 
def _maxColor(values: List[str]) -> str:
 
def _maxColor(values: List[HexColor]) -> HexColor:
 
    rgbs = [hex_to_rgb(v) for v in values]
 
    maxes = [max(component) for component in zip(*rgbs)]
 
    return rgb_to_hex(tuple(maxes))
 

	
 

	
 
VT = TypeVar('VT', float, int, str)
 
    return cast(HexColor, rgb_to_hex(tuple(maxes)))
 

	
 

	
 
def resolve(
 
        deviceType: DeviceUri,  # should be DeviceClass?
 
        deviceAttr: DeviceAttr,
 
        values: List[VT]) -> Any:  # todo: return should be VT
 
        values: List[VTUnion]) -> VTUnion:  # todo: return should be VT
 
    """
 
    return one value to use for this attr, given a set of them that
 
    have come in simultaneously. len(values) >= 1.
 
@@ -66,7 +63,7 @@ def resolve(
 
    if len(values) == 1:
 
        return values[0]
 
    if deviceAttr == DeviceAttr(L9['color']):
 
        return _maxColor(cast(List[str], values))
 
        return _maxColor(cast(List[HexColor], values))
 
    # incomplete. how-to-resolve should be on the DeviceAttr defs in the graph.
 
    if deviceAttr in map(DeviceAttr, [L9['rx'], L9['ry'], L9['zoom'], L9['focus'], L9['iris']]):
 
        floatVals = []
 
@@ -79,7 +76,7 @@ def resolve(
 
                raise TypeError(repr(v))
 

	
 
        # averaging with zeros? not so good
 
        return Literal(sum(floatVals) / len(floatVals))
 
        return sum(floatVals) / len(floatVals)
 
    return max(values)
 

	
 

	
light9/effect/settings_test.py
Show inline comments
 
import unittest
 
from rdflib import Literal
 
from rdfdb.patch import Patch
 
from rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.localsyncedgraph import LocalSyncedGraph
 
from light9.namespaces import L9, DEV
 
from light9.effect.settings import DeviceSettings
 

	
 
@@ -87,7 +87,7 @@ class TestDeviceSettings(unittest.TestCa
 
            (L9['light1'], L9['attr2'], 0.3),
 
            (L9['light1'], L9['attr1'], 0.5),
 
        ]
 
        self.assertItemsEqual(sets, DeviceSettings(self.graph, sets).asList())
 
        self.assertCountEqual(sets, DeviceSettings(self.graph, sets).asList())
 

	
 
    def testDevices(self):
 
        s = DeviceSettings(self.graph, [
 
@@ -95,7 +95,7 @@ class TestDeviceSettings(unittest.TestCa
 
            (DEV['aura2'], L9['rx'], 0.1),
 
        ])
 
        # aura1 is all defaults (zeros), so it doesn't get listed
 
        self.assertItemsEqual([DEV['aura2']], s.devices())
 
        self.assertCountEqual([DEV['aura2']], s.devices())
 

	
 
    def testAddStatements(self):
 
        s = DeviceSettings(self.graph, [
 
@@ -103,11 +103,12 @@ class TestDeviceSettings(unittest.TestCa
 
        ])
 
        stmts = s.statements(L9['foo'], L9['ctx1'], L9['s_'], set())
 
        self.maxDiff = None
 
        self.assertItemsEqual([
 
            (L9['foo'], L9['setting'], L9['s_set4350023'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['device'], DEV['aura2'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['deviceAttr'], L9['rx'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['value'], Literal(0.1), L9['ctx1']),
 
        setting = sorted(stmts)[-1][0]
 
        self.assertCountEqual([
 
            (L9['foo'], L9['setting'], setting, L9['ctx1']),
 
            (setting, L9['device'], DEV['aura2'], L9['ctx1']),
 
            (setting, L9['deviceAttr'], L9['rx'], L9['ctx1']),
 
            (setting, L9['value'], Literal(0.1), L9['ctx1']),
 
        ], stmts)
 

	
 
    def testDistanceTo(self):
light9/newtypes.py
Show inline comments
 
from typing import Tuple, NewType, Type, TypeVar, cast
 
from typing import Tuple, NewType, Type, TypeVar, Union, cast
 
from rdflib import URIRef
 
from rdflib.term import Node
 

	
 
@@ -17,6 +17,13 @@ OutputValue = NewType('OutputValue', int
 
Song = NewType('Song', URIRef)
 
UnixTime = NewType('UnixTime', float)
 

	
 
VT = TypeVar('VT', float, int, str)  # remove
 
HexColor = NewType('HexColor', str)
 
VTUnion = Union[float, int, HexColor]  # rename to ValueType
 
DeviceSetting = Tuple[DeviceUri, DeviceAttr,
 
                      # currently, floats and hex color strings
 
                      VTUnion]
 

	
 
# Alternate output range for a device. Instead of outputting 0.0 to
 
# 1.0, you can map that range into, say, 0.2 to 0.7
 
OutputRange = NewType('OutputRange', Tuple[float, float])
0 comments (0 inline, 0 general)