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 (
Value
orEvent
) whose changes cause the value of this object to be updated, either throughget_value
, or assignment tovalue
inside theiron_value_changed
orEvent.on_event
callbacks- 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 ininputs
have finished updating in a transaction.This may return
NoChange
, in which casevalue
will 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_changed
callbacks.
- on_value_changed(cb)[source]¶
Registers
cb
as 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
yarp
is 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 (
Value
orEvent
) whose changes (value changes or events) cause events to be emitted from this object, either throughon_inputs_done
, or callingemit
inside theiron_value_changed
orEvent.on_event
callbacks- 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
inputs
have finished updating in a transaction.
- class yarp.Reactive(inputs)[source]¶
base class for reactive types (
Value
andEvent
)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
yarp
value 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 Value
s which
update whenever the underlying Value
s do.
- yarp.value_list(list_of_values)[source]¶
Returns a
Value
consisting of a fixed list of otherValues
. The returnedValue
will change whenever one of its members does.- Parameters:
- list_of_values: [:py:class:`Value`, …]
A fixed list of
Value
s. Thevalue
of 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
NoValue
invalue
or in callbacks resulting from otherValue
s changing.
- yarp.value_tuple(tuple_of_values)[source]¶
A
Value
consisting of a tuple of otherValues
.- Parameters:
- tuple_of_values: (:py:class:`Value`, …)
A fixed tuple of
Value
s. Thevalue
of 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
NoValue
invalue
or in callbacks resulting from otherValue
s changing.
- yarp.value_dict(dict_of_values)[source]¶
A
Value
consisting of a dictionary where the values (but not keys) areValues
.- Parameters:
- dict_of_values: {key: :py:class:`Value`, …}
A fixed dictionary of
Value
s. Thevalue
of 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
NoValue
invalue
or in callbacks resulting from otherValue
s 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
Value
object, 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_tuple
orvalue_dict
respectively.If any other type, wraps the variable in a continous
Value
with 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 + b
a - b
a * b
a @ b
a / b
a // b
a % b
divmod(a, b)
a ** b
- Bit-wise
a << b
a >> b
a & b
a | b
a ^ b
- Unary
-a
+a
abs(a)
~a
- Comparison
a < b
a <= b
a == b
a != b
a >= b
a > 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 aValue
orEvent
containing the result. This will be updated by re-calling the function whenever the input changes. Likefn()
, arguments may beValue
orEvent
objects 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.name
equivalent 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 b
fora & b
(works for boolean values but produces numbers)a or b
fora | 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
operator
module.
As above, these follow the same rules as fn
: the result will be an Event
if
any of the inputs are Event
s, otherwise Value
.
Function wrappers¶
The primary mode of interaction with yarp
Value
s and Event
s 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_value
isNoValue
, returnreplacement_if_novalue
instead.
- 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 andNoValue
will be filtered out. If the filter rule isNoValue
(the default) onlyNoValue
will be filtered out.
Temporal Value manipulation¶
The following utility functions are used to modify or observe how Value
s
and Event
s 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
Value
orEvent
.For
Value
s, the initial value is set immediately.The
delay_seconds
argument 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_seconds
may 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.
source
may be a Value or Event.The
min_interval
argument 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 ontime
changes, even iftime
is 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
Value
s
- 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
Value
containing adatetime.datetime
object holding the current time, refreshed everyinterval
seconds.The
interval
argument may be a constant or aValue
giving 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
tz
argument is passed on todatetime.datetime.now()
. This must be a constant.The
loop
argument should be anasyncio.BaseEventLoop
in which the delays will be scheduled. IfNone
, the default loop is used.