Ejemplo n.º 1
0
def file_backed_value(filename, initial_value=NoValue):
    """
    A persistent, file-backed value.
    
    Upon creation, the value will be loaded from the specified filename.
    Whenever the value is changed it will be rewritten to disk. Changes made to
    the file while your program is running will be ignored.
    
    If the file does not exist, it will be created and the value set to
    the value given by `initial_value`.
    
    The value must be pickleable.
    """
    try:
        with open(filename, "rb") as f:
            source_value = Value(pickle.load(f))
    except FileNotFoundError:
        # If the file doesn't exist, use the initial value
        source_value = Value(initial_value)
    except Exception:
        # If there's a pickling error or similar, show the error but continue
        # with the provided initial value.
        traceback.print_exc()
        source_value = Value(initial_value)
    
    # Store changes to disk
    @source_value.on_value_changed
    def on_value_changed(new_value):
        with open(filename, "wb") as f:
            pickle.dump(new_value, f)
    
    # Immediately trigger a store (incase the file did not exist yet)
    on_value_changed(source_value.value)
    
    return source_value
Ejemplo n.º 2
0
async def test_set_property_register(qth_client, event_loop):
    a = Value(123)
    qth_yarp.set_property("foo/bar",
                          a,
                          register=True,
                          description="Something",
                          qth_client=qth_client)

    # Allow asyncio functions to run...
    await asyncio.sleep(0.1)

    # Initial should have been sent to Qth
    qth_client.set_property.assert_called_once_with("foo/bar", 123)

    # Registration should have been sent
    qth_client.register.assert_called_once_with(
        "foo/bar",
        qth.PROPERTY_ONE_TO_MANY,
        "Something",
        delete_on_unregister=True,
    )

    # Setting the qth value should update Qth
    a.value = 321
    await asyncio.sleep(0.1)
    qth_client.set_property.assert_called_with("foo/bar", 321)

    # And again...
    a.value = 1234
    await asyncio.sleep(0.1)
    qth_client.set_property.assert_called_with("foo/bar", 1234)
Ejemplo n.º 3
0
def test_change_callback():
    m = Mock()

    v = Value()
    v.on_value_changed(m)

    v.value = 123
    m.assert_called_once_with(123)
Ejemplo n.º 4
0
def test_operator_wrapper_continous():
    # Only test addition since others are defined in exactly the same way
    a = Value(1)
    b = Value(2)

    a_add_b = add(a, b)
    assert a_add_b.value == 3

    a.value = 10
    assert a_add_b.value == 12
Ejemplo n.º 5
0
def test_ensure_value_dict():
    a = 123
    b = Value(456)

    v = ensure_value({"a": a, "b": b})
    assert isinstance(v, Value)
    assert v.value == {"a": 123, "b": 456}

    b.value = 789
    assert v.value == {"a": 123, "b": 789}
Ejemplo n.º 6
0
def test_ensure_value_tuple():
    a = 123
    b = Value(456)

    v = ensure_value((a, b))
    assert isinstance(v, Value)
    assert v.value == (123, 456)

    b.value = 789
    assert v.value == (123, 789)
Ejemplo n.º 7
0
def test_ensure_value_list():
    a = 123
    b = Value(456)

    v = ensure_value([a, b])
    assert isinstance(v, Value)
    assert v.value == [123, 456]

    b.value = 789
    assert v.value == [123, 789]
Ejemplo n.º 8
0
def test_value_dict_persistent():
    a = Value("a")
    b = Value("b")
    c = Value("c")

    dct = value_dict({"a": a, "b": b, "c": c})

    # Initial value should have passed through
    assert dct.value == {"a": "a", "b": "b", "c": "c"}

    m = Mock()
    dct.on_value_changed(m)

    # Changes should propagate through
    a.value = "A"
    assert dct.value == {"a": "A", "b": "b", "c": "c"}
    m.assert_called_once_with({"a": "A", "b": "b", "c": "c"})

    m.reset_mock()
    b.value = "B"
    assert dct.value == {"a": "A", "b": "B", "c": "c"}
    m.assert_called_once_with({"a": "A", "b": "B", "c": "c"})

    m.reset_mock()
    c.value = "C"
    assert dct.value == {"a": "A", "b": "B", "c": "C"}
    m.assert_called_once_with({"a": "A", "b": "B", "c": "C"})
Ejemplo n.º 9
0
def test_value_list_persistent():
    a = Value("a")
    b = Value("b")
    c = Value("c")

    lst = value_list([a, b, c])

    # Initial value should have passed through
    assert lst.value == ["a", "b", "c"]

    m = Mock()
    lst.on_value_changed(m)

    # Changes should propagate through
    a.value = "A"
    assert lst.value == ["A", "b", "c"]
    m.assert_called_once_with(["A", "b", "c"])

    m.reset_mock()
    b.value = "B"
    assert lst.value == ["A", "B", "c"]
    m.assert_called_once_with(["A", "B", "c"])

    m.reset_mock()
    c.value = "C"
    assert lst.value == ["A", "B", "C"]
    m.assert_called_once_with(["A", "B", "C"])
Ejemplo n.º 10
0
def test_value_tuple_persistent():
    a = Value("a")
    b = Value("b")
    c = Value("c")

    tup = value_tuple((a, b, c))

    # Initial value should have passed through
    assert tup.value == ("a", "b", "c")

    m = Mock()
    tup.on_value_changed(m)

    # Changes should propagate through
    a.value = "A"
    assert tup.value == ("A", "b", "c")
    m.assert_called_once_with(("A", "b", "c"))

    m.reset_mock()
    b.value = "B"
    assert tup.value == ("A", "B", "c")
    m.assert_called_once_with(("A", "B", "c"))

    m.reset_mock()
    c.value = "C"
    assert tup.value == ("A", "B", "C")
    m.assert_called_once_with(("A", "B", "C"))
Ejemplo n.º 11
0
def test_check_initial_value():
    # Initial value should also be filtered
    rule = lambda x: x == 123

    v = Value(123)
    fl = filter(v, rule)
    assert fl.value == 123

    v = Value(321)
    fl = filter(v, rule)
    assert fl.value is NoValue
Ejemplo n.º 12
0
def test_ensure_value_nested():
    a = Value(123)
    b = Value(456)
    c = Value(789)

    v = ensure_value({"a": a, "bc": [b, c]})
    assert isinstance(v, Value)
    assert v.value == {"a": 123, "bc": [456, 789]}

    b.value = 654
    assert v.value == {"a": 123, "bc": [654, 789]}
Ejemplo n.º 13
0
def test_make_instantaneous():
    v = Value(1)

    iv = make_instantaneous(v)
    m = Mock()
    iv.on_value_changed(m)

    assert iv.value is NoValue

    v.value = 2
    assert iv.value is NoValue
    m.assert_called_once_with(2)
Ejemplo n.º 14
0
def test_inst_positional_kwargs():
    m = Mock()
    
    @instantaneous_fn
    def example(*args, **kwargs):
        return (args, kwargs)
    
    a_value = Value()
    b_value = Value()
    
    # No value should be assigned
    result = example(a=a_value, b=b_value)
    result.on_value_changed(m)
    assert result.value is NoValue
    
    # Changes should propagate, callbacks should fire but no value should be
    # stored
    m.reset_mock()
    a_value.set_instantaneous_value(123)
    m.assert_called_once_with(((), {"a": 123, "b": NoValue}))
    assert result.value is NoValue
    
    m.reset_mock()
    b_value.set_instantaneous_value(123)
    m.assert_called_once_with(((), {"a": NoValue, "b": 123}))
    assert result.value is NoValue
Ejemplo n.º 15
0
def test_make_persistent():
    v = Value()

    # Initially no value to be found...
    pv = make_persistent(v)
    assert pv.value is NoValue

    m = Mock()
    pv.on_value_changed(m)

    assert pv.value is NoValue

    v.set_instantaneous_value(2)
    assert pv.value == 2
    m.assert_called_once_with(2)
Ejemplo n.º 16
0
def filter(source_value, rule=NoValue):
    """Filter change events.
    
    The filter rule should be a function which takes the new value as an
    argument and returns a boolean indicating if the value should be passed on
    or not.
    
    If the source value is persistent, the persistent value will remain
    unchanged when a value change is not passed on.
    
    If the filter rule is ``None``, non-truthy values and ``NoValue`` will be
    filtered out. If the filter rule is ``NoValue`` (the default) only
    ``NoValue`` will be filtered out.
    """
    source_value = ensure_value(source_value)
    output_value = Value(source_value.value if (
        source_value.value is not NoValue
        and _check_value(source_value.value, rule)) else NoValue)

    @source_value.on_value_changed
    def on_source_value_changed(new_value):
        if _check_value(new_value, rule):
            output_value._value = source_value.value
            output_value.set_instantaneous_value(new_value)

    return output_value
Ejemplo n.º 17
0
def window(source_value, num_values):
    """Produce a moving window over a :py:class:`Value`'s historical values.
    
    This function treats the Value it is passed as a persistent Value, even if
    it is instantaneous (since a window function doesn't really have any
    meaning for a instantaneous values).
    
    The ``num_values`` argument may be a (persistent) Value or a constant
    indicating the number of entries in the window. If this value later
    reduced, the contents of the window will be truncated immediately. If it is
    increaesd, any previously dropped values will not return.  ``num_values``
    is always assumed to be an integer greater than zero and never ``NoValue``.
    """
    source_value = ensure_value(source_value)
    output_value = Value([source_value.value])

    num_values = ensure_value(num_values)
    assert num_values.value >= 1

    @source_value.on_value_changed
    def on_source_value_changed(new_value):
        """Internal. Insert incoming Value into the window."""
        output_value.value = (output_value.value +
                              [new_value])[-num_values.value:]

    @num_values.on_value_changed
    def on_num_values_changed(_instantaneous_new_num_values):
        """Internal. Handle window size changes."""
        # Truncate the window data if required
        new_num_values = num_values.value
        assert new_num_values >= 1
        if len(output_value.value) > new_num_values:
            output_value.value = output_value.value[-new_num_values:]

    return output_value
Ejemplo n.º 18
0
def test_change_persistent():
    rule = lambda x: x < 10

    m = Mock()
    v = Value(1)
    fl = filter(v, rule)
    fl.on_value_changed(m)
    assert fl.value == 1

    v.value = 2
    assert fl.value == 2
    m.assert_called_once_with(2)

    # Above ten, shouldn't get through
    v.value = 100
    assert fl.value == 2
    m.assert_called_once_with(2)
Ejemplo n.º 19
0
    def instance_maker(*args, **kwargs):
        output_value = Value()

        def callback(*args, **kwargs):
            output_value.set_instantaneous_value(f(*args, **kwargs))

        _function_call_on_argument_value_change(False, callback, *args,
                                                **kwargs)
        return output_value
Ejemplo n.º 20
0
def test_change_persistent_initial_value_filtered():
    rule = lambda x: x < 10

    v = Value(123)
    fl = filter(v, rule)

    # Initial value should be rejected by the filter and thus not passed
    # through
    assert fl.value is NoValue
Ejemplo n.º 21
0
def test_no_repeat_persistent():
    v = Value(1)

    # Initial value should come through
    nrv = no_repeat(v)
    assert nrv.value == 1

    m = Mock()
    nrv.on_value_changed(m)

    # Same value doesn't pass through
    v.value = 1
    assert not m.called

    # New values do
    v.value = 2
    assert nrv.value == 2
    m.assert_called_once_with(2)
Ejemplo n.º 22
0
def test_getattr_value_name():
    # Special attention since this is important
    m = Mock()
    m.foo = "FOO!"
    m.bar = "BAR!"
    v = Value(m)

    name_v = Value("foo")
    attr_v = getattr(v, name_v)

    assert attr_v.value == "FOO!"

    log = []
    attr_v.on_value_changed(log.append)

    name_v.value = "bar"

    assert attr_v.value == "BAR!"
    assert log == ["BAR!"]
Ejemplo n.º 23
0
    async def test_instantaneous(self, event_loop):
        value = Value()

        delayed_value = delay(value, 0.1, loop=event_loop)
        assert delayed_value.value is NoValue

        # Monitor changes
        evt = asyncio.Event(loop=event_loop)
        m = Mock(side_effect=lambda *_: evt.set())
        delayed_value.on_value_changed(m)

        # Trigger a change for later...
        before = time.time()
        value.set_instantaneous_value(123)
        assert delayed_value.value is NoValue
        assert not m.mock_calls
        await evt.wait()
        assert time.time() - before >= 0.1
        m.assert_called_once_with(123)
        assert delayed_value.value is NoValue
Ejemplo n.º 24
0
def test_getattr_string_name():
    # Special attention since this is important
    m = Mock()
    m.foo = "bar"
    v = Value(m)

    foo_v = getattr(v, "foo")

    assert isinstance(foo_v, Value)
    assert foo_v.value == "bar"

    log = []
    foo_v.on_value_changed(log.append)

    m2 = Mock()
    m2.foo = "baz"
    v.value = m2

    assert foo_v.value == "baz"
    assert log == ["baz"]
Ejemplo n.º 25
0
def test_str_format_operator():
    # Special attention due for str_format as its an almost custom function(!)
    a = Value(0xAB)
    b = Value("hi")
    fmt = Value("{}, {}")

    stringified = str_format(fmt, a, b)
    assert stringified.value == "171, hi"

    a.value = 0xBC
    assert stringified.value == "188, hi"

    b.value = "foo"
    assert stringified.value == "188, foo"

    fmt.value = "0x{:04X}: {!r}"
    assert stringified.value == "0x00BC: 'foo'"
Ejemplo n.º 26
0
def test_positional_kwargs():
    m = Mock()
    
    @fn
    def example(a, b):
        return a - b
    
    a_value = Value(10)
    b_value = Value(5)
    
    # Initial value should pass through
    result = example(a=a_value, b=b_value)
    result.on_value_changed(m)
    assert result.value == 5
    
    # Changes should propagate, callbacks should fire
    m.reset_mock()
    a_value.value = 20
    m.assert_called_once_with(15)
    assert result.value == 15
    
    m.reset_mock()
    b_value.value = -5
    m.assert_called_once_with(25)
    assert result.value == 25
Ejemplo n.º 27
0
async def test_send_event_register(qth_client, event_loop):
    a = Value()
    qth_yarp.send_event("foo/bar",
                        a,
                        register=True,
                        description="Something",
                        qth_client=qth_client)

    # Allow asyncio functions to run...
    await asyncio.sleep(0.1)

    # No initial value should have been sent to Qth
    assert len(qth_client.send_event.mock_calls) == 0

    # No registration should be made
    # Registration should have been sent
    qth_client.register.assert_called_once_with(
        "foo/bar",
        qth.EVENT_ONE_TO_MANY,
        "Something",
    )

    # Setting the qth value should update Qth
    a.set_instantaneous_value(321)
    await asyncio.sleep(0.1)
    qth_client.send_event.assert_called_with("foo/bar", 321)

    # And again...
    a.set_instantaneous_value(1234)
    await asyncio.sleep(0.1)
    qth_client.send_event.assert_called_with("foo/bar", 1234)
Ejemplo n.º 28
0
async def test_set_property(qth_client, event_loop):
    a = Value(123)
    qth_yarp.set_property("foo/bar", a, qth_client=qth_client)

    # Allow asyncio functions to run...
    await asyncio.sleep(0.1)

    # Initial should have been sent to Qth
    qth_client.set_property.assert_called_once_with("foo/bar", 123)

    # No registration should be made
    assert len(qth_client.register.mock_calls) == 0

    # Setting the qth value should update Qth
    a.value = 321
    await asyncio.sleep(0.1)
    qth_client.set_property.assert_called_with("foo/bar", 321)

    # And again...
    a.value = 1234
    await asyncio.sleep(0.1)
    qth_client.set_property.assert_called_with("foo/bar", 1234)
Ejemplo n.º 29
0
def test_change_callback_only():
    m = Mock()

    v = Value()
    v.on_value_changed(m)

    v.set_instantaneous_value(123)
    m.assert_called_once_with(123)
    assert v.value is NoValue
Ejemplo n.º 30
0
    def instance_maker(*args, **kwargs):
        output_value = Value()
        first_call = True

        def callback(*args, **kwargs):
            nonlocal first_call
            if first_call:
                first_call = False
                output_value._value = f(*args, **kwargs)
            else:
                output_value.value = f(*args, **kwargs)

        _function_call_on_argument_value_change(True, callback, *args,
                                                **kwargs)
        return output_value