yarp API¶
Value and Event types¶
At the core of the yarp API are the Value and Event types, defined below.
- class yarp.Value(initial_value=NoValue, inputs=(), get_value=None)[source]¶
represents a value that changes over time
- Parameters:
- initial_value
the value of this object on construction
- inputsIterable[Reactive]
other reactive objects (
ValueorEvent) whose changes cause the value of this object to be updated, either throughget_value, or assignment tovalueinside theiron_value_changedorEvent.on_eventcallbacks- get_valueCallable[[], Any]
a callback which when called with no arguments returns a new value for this object
If provided, this is called during construction (overriding
initial_value), and once after all objects specified ininputshave finished updating in a transaction.This may return
NoChange, in which casevaluewill not be updated.
- property value¶
the current value of this object
If not yet set (either in the constructor or by assigning to this property), this will be
NoValue.Setting this property will call the
on_value_changedcallbacks.
- on_value_changed(cb)[source]¶
Registers
cbas a callback function to be called when this value changes.The callback function will be called with a single argument: the value now held by this object.
Note
There is no way to remove callbacks. For the moment this is an intentional restriction: if this causes you difficulties this is a good sign what you’re doing is ‘serious’ enough that
yarpis not for you.This function returns the callback passed to it making it possible to use it as a decorator if desired.
- class yarp.Event(inputs=(), on_inputs_done=None)[source]¶
represents a value that is only known at some points in time
this is really just a list of callbacks (added with
on_event), which can be called by callingemit.- inputsIterable[Reactive]
other reactive objects (
ValueorEvent) whose changes (value changes or events) cause events to be emitted from this object, either throughon_inputs_done, or callingemitinside theiron_value_changedorEvent.on_eventcallbacks- on_inputs_doneCallable[[Callable[[Any]], None], Any]
a callback which when called with a single callable argument, calls that argument with a single parameter to emit values from this object
If provided, this is called once after all objects specified in
inputshave finished updating in a transaction.
- class yarp.Reactive(inputs)[source]¶
base class for reactive types (
ValueandEvent)this only exists to handle dependency tracking and transactions
- add_input(input: Reactive)[source]¶
register a new input to this value
Normally inputs should be specified in the constructor, but this is needed when the logical structure of dependencies (but not the actual dependencies) is circular. This can happen when using asyncio (which breaks loops by running callbacks asynchronously) – see for example the implementation of functions like
rate_limit.
- yarp.NoValue = NoValue¶
A special value indicating that a
yarpvalue has not been assigned a value.
- yarp.NoChange = NoChange¶
A value returned by callbacks indicating that no change should be made to a value, or no event should be emitted.
Aggregate Values¶
The yarp API provides a limited set of convenience functions which which
turn certain native Python data structures into Values which
update whenever the underlying Values do.
- yarp.value_list(list_of_values)[source]¶
Returns a
Valueconsisting of a fixed list of otherValues. The returnedValuewill change whenever one of its members does.- Parameters:
- list_of_values: [:py:class:`Value`, …]
A fixed list of
Values. Thevalueof this object will be an array of the underlying values. Callbacks will be raised whenever a value in the list changes.It is not possible to modify the list or set the contained values directly from this object.
For instantaneous list members, the instantaneous value will be present in the version of this list passed to registered callbacks but otherwise not retained. (Typically the instantaneous values will be represented by
NoValueinvalueor in callbacks resulting from otherValues changing.
- yarp.value_tuple(tuple_of_values)[source]¶
A
Valueconsisting of a tuple of otherValues.- Parameters:
- tuple_of_values: (:py:class:`Value`, …)
A fixed tuple of
Values. Thevalueof this object will be a tuple of the underlying values. Callbacks will be raised whenever a value in the tuple changes.It is not possible to modify the tuple or set the contained values directly from this object.
For instantaneous tuple members, the instantaneous value will be present in the version of this tuple passed to registered callbacks but otherwise not retained. (Typically the instantaneous values will be represented by
NoValueinvalueor in callbacks resulting from otherValues changing.
- yarp.value_dict(dict_of_values)[source]¶
A
Valueconsisting of a dictionary where the values (but not keys) areValues.- Parameters:
- dict_of_values: {key: :py:class:`Value`, …}
A fixed dictionary of
Values. Thevalueof this object will be a dictionary of the underlying values. Callbacks will be raised whenever a value in the dictionary changes.It is not possible to modify the set of keys in the dictionary nor directly change the values of its elements from this object.
For instantaneous dictionary members, the instantaneous value will be present in the version of this dict passed to registered callbacks but otherwise not retained. (Typically the instantaneous values will be represented by
NoValueinvalueor in callbacks resulting from otherValues changing.
Value casting¶
The following low-level functions are provided for creating and casting
Value and Event objects.
- yarp.ensure_value(value)[source]¶
Ensure a variable is a
Valueobject, wrapping it accordingly if not.If already a
Value, returns unmodified.If a list, tuple or dict, applies
ensure_value()to all contained values and returns avalue_list,value_tupleorvalue_dictrespectively.If any other type, wraps the variable in a continous
Valuewith the initial value set to the defined value.
Value Operators¶
The Value and Event classes also supports many (but not all) of the native
Python operations, producing corresponding Value or Event objects. These
operations support the mixing of Value, Event and other suitable Python
objects, following the same rules as fn. The following operators are
supported:
- Arithmetic
a + ba - ba * ba @ ba / ba // ba % bdivmod(a, b)a ** b
- Bit-wise
a << ba >> ba & ba | ba ^ b
- Unary
-a+aabs(a)~a
- Comparison
a < ba <= ba == ba != ba >= ba > b
- Container operators
a[key]
- Numerical conversions
complex(a)int(a)float(a)round(a)
- Python object/function usage
a(...)will call the value as a function and return aValueorEventcontaining the result. This will be updated by re-calling the function whenever the input changes. Likefn(), arguments may beValueorEventobjects and these will be unwrapped before the function is called and will also cause the function to be re-evaluated whenever they change. Do not use this to call functions with side effects.a.nameequivalent toyarp.getattr(a, "name")
Unfortunately this list doesn’t include boolean operators (i.e. not,
and, or and bool). This is due to a limitation of the Python data
model which means that bool may only return an actual boolean value, not
some other type of object. As a workaround you can substitute:
bool(a)fora == True(works in most cases)a and bfora & b(works for boolean values but produces numbers)a or bfora | b(works for boolean values but produces numbers)
For a similar reasons, the len and in operators are also not supported.
This list also doesn’t include mutating operators, for example a[key] = b.
This is because the Python objects within a Value are treated as
being immutable.
Python builtins¶
The yarp API provides versions of a number of
Python builtins and functions from the standard library which work with Value
and Event:
- Builtins
bool(a)any(a)all(a)min(a)max(a)sum(a)map(a)sorted(a)str(a)repr(a)str_format(a, ...)(equivalent toa.format(...))oct(a)hex(a)zip(a)len(a)getattr(object, name[, default])
Most non-mutating, non-underscore prefixed functions from the
operatormodule.
As above, these follow the same rules as fn: the result will be an Event if
any of the inputs are Events, otherwise Value.
Function wrappers¶
The primary mode of interaction with yarp Values and Events is
intended to be via simple Python functions wrapped with fn, defined below.
- yarp.fn(f)[source]¶
Wrap a function operating on plain values so that it can accept Value/Event arguments and produces a Value/Event result.
If the function is called with only Value (or non-reactive) arguments, the result will be a Value, the result of calling the function, which updates whenever any of the inputs change. See the README example.
If the function is called with any Event values, then the result will be an Event, which emits once each time any input Event emits, with the result of calling the wrapped function with the value emitted by the event and the latest version of any Value inputs. Value changes do not cause the resulting Event to emit. For example:
>>> @fn ... def passthrough(*args): ... return args
>>> a = Value(1) >>> b = Event() >>> res = passthrough(a, b) >>> res.on_event(print) <...>
>>> b.emit(2) (1, 2) >>> a.value = 3 # nothing >>> b.emit(4) (3, 4)
If multiple Events are passed to the wrapped function, the “one output per input event” rule still holds, and the non-firing event inputs are replaced with NoValue. For example:
>>> @fn ... def passthrough(*args): ... return args
>>> a = Event() >>> b = Event() >>> res = passthrough(a, b) >>> res.on_event(print) <...>
>>> a.emit(1) (1, NoValue) >>> b.emit(2) (NoValue, 2)
This happens even if two events occur at the same time (within one transaction):
>>> res = passthrough(a, a) >>> res.on_event(print) <...>
>>> a.emit(1) (1, NoValue) (NoValue, 1)
If the function returns NoChange, then the resulting Event will not emit, or the Value will not change.
Notes
event behaviour: It would be possible instead to only call the function once for events which occur in the same transaction, and only produce one result.
This isn’t done because it’s possible (though maybe it shouldn’t be) for an Event to emit more than once in a transaction. This isn’t a niche issue – think about something that turns high-level commands into multiple lower-level ones. What should be done then?
We could turn the values into a list, but that’s either inconsistent (if singular events are not wrapped) or messy (if they are always wrapped). It seems bad to force users to think about this annoyance in the transaction mechanism.
We could call it once per event, but what if multiple input events emit more than once? I don’t think there’s a good answer to this.
Or, we could ignore all but the last event, but that’s definitely bad.
This behaviour is chosen, even if it isn’t perfect for all uses, because it’s consistent and doesn’t force the user to understand how it interacts with the transaction processing.
The major downside is the inability to use some overloads on events (e.g.
e ** 2 + e) – just use a fn for that kind of thing.If a different behaviour is needed, either write it manually, write something which merges events in the way you need (then process the result with fn), or add options to this to pick a different behaviour.
General Value manipulation¶
The following utility functions are defined.
- yarp.replace_novalue(source_value, replacement_if_novalue=None)[source]¶
If the
source_valueisNoValue, returnreplacement_if_novalueinstead.
- yarp.window(source_value: Value | Event, num_values: int | Value) Value[source]¶
make a Value with the num_values most recent values or events from source_value
If num_values is decreased then the return value will be cropped. If it is increased, then the return value will lengthen gradually with new events or changes.
- yarp.filter(source, rule=NoValue)[source]¶
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 a Value, the old value will remain unchanged when a value change is not passed on. If the initial value does not pass the test, the initial value of the result will be NoValue.
If the filter rule is
None, non-truthy values andNoValuewill be filtered out. If the filter rule isNoValue(the default) onlyNoValuewill be filtered out.
Temporal Value manipulation¶
The following utility functions are used to modify or observe how Values
and Events change over time. These all use asyncio internally and
require that a asyncio.BaseEventLoop be running.
- yarp.delay(source, delay_seconds)[source]¶
Produce a time-delayed version of a
ValueorEvent.For
Values, the initial value is set immediately.The
delay_secondsargument may be a constant or a Value giving the number of seconds to delay value changes. If it is increased, previously delayed values will be delayed further. If it is decreased, values which should already have been output will be output rapidly one after another.
- yarp.time_window(source, duration_seconds)[source]¶
Produce a moving window over the historical values of a Value or the events of an Event within a given time period.
duration_secondsmay be a constant or a Value giving the window duration as a number of seconds. The duration should be a number of seconds greater than zero and never beNoValue. If the value is reduced, previously inserted values will be expired earlier, possibly immediately if they should already have expired. If the value is increased, previously inserted values will have an increased timeout.
- yarp.rate_limit(source, min_interval=0.1)[source]¶
Prevent changes occurring above a particular rate, dropping or postponing changes if necessary.
sourcemay be a Value or Event.The
min_intervalargument may be a constant or aValue. If this value is decreased, currently delayed values will be output early (or immediately if the value would have been output previously). If increased, the current delay will be increased.
- yarp.emit_at(time: Value | float | int | None) Event[source]¶
emit an event at the times given in time
Time can be None (no events), a time in seconds as given by loop.time() (emit None at the given time), or a tuple containing the time and the value to emit.
Whenever time changes the timer is reloaded, so if the timer for the previous value has not fired, it never will. This also means that if the time changes to be before the current time, there will be one event per change.
This is mostly useful in cases where you can calculate the next time that something should happen from some Value.
The resulting Event does not depend on
time, because it is always emitted in an asyncio callback (never synchronously ontimechanges, even iftimeis in the past). This makes it safe to modifytime(or one of its inputs) in a callback attached to the result.
File-backed Values¶
The following function can be used to make very persistent
Values
- yarp.file_backed_value(filename, initial_value=NoValue) Value[source]¶
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.
Time Values¶
The following function can be used to get the (continously changing) date and time:
- yarp.now(interval=1.0, tz=None, loop=None) Value[source]¶
Returns a
Valuecontaining adatetime.datetimeobject holding the current time, refreshed everyintervalseconds.The
intervalargument may be a constant or aValuegiving the number of seconds to wait between updates. If the Value changes, the time until the next update will be reset starting from that moment in time.The
tzargument is passed on todatetime.datetime.now(). This must be a constant.The
loopargument should be anasyncio.BaseEventLoopin which the delays will be scheduled. IfNone, the default loop is used.