boopak.argdef | index boopak/argdef.py |
argdef: A module which serializes and unserializes the arguments of an
Agent.
Boodler allows you to start up a complex arrangement of soundscapes
directly from the command line. For example:
boodler " org.boodler.manage/Simultaneous
(com.eblong.zarf.heartbeat/OneBeat 1.5)
(com.eblong.zarf.heartbeat/OneBeat 0.9) "
This nested argument string takes the form of an S-expression (or
rather, an extended S-expression, as defined in the sparse module).
Parsing it into a tree of nodes is easily done. However, interpreting
that tree into Python values is trickier. How do we know whether to
interpret the substring "(com.eblong.zarf.heartbeat/OneBeat 1.5)" as a
list of strings, a tuple of (string, float), or -- the intended
outcome -- a Boodler agent? We need to know what kind of arguments the
Simultaneous agent is expecting. That is the task of this module.
-- Overview
The work begins by creating an ArgList object. This contains all the
information about the agent's arguments -- how many arguments, their
names, their types, their default values. (Note that the ArgList type
information can be quite detailed. An argument can be specified not
just as "a list", but "a list of up to ten Agents", or "a list
containing an Agent followed by at least three sound samples".)
Normally, an agent's ArgList information is worked out when the
agent's module is packaged for distribution. The ArgList class can
infer most of this information itself (by inspecting the agent's
init() method). The agent's author can provide additional details in
the agent class's _args field. The ArgList is then serialized (as an
S-expression) in the package metadata.
When Boodler loads an agent from a module, it retrieves this ArgList
from the metadata. It then uses it to interpret the command-line data
that the user provided. This allows it to construct an Agent instance,
using valid arguments, and then start the agent playing.
(Note that in the above example, the ArgList of the Simultaneous
agent says that it wants a list of Agents as arguments. But the
ArgList of the OneBeat agent is also consulted; it says that it
accepts a float argument. The argument-constructor is recursive
for Agent arguments.)
We have, therefore, two distinct unserialization operations. First,
the ArgList itself is built from a metadata S-expression. (This is
the ArgList.from_node() static method, which makes use of the
node_to_type() helper function.) Then, this ArgList turns the user's
command-line S-expression into a bunch of argument values. (This is
the ArgList.resolve() method, which makes use of the node_to_value()
helper function.)
(It's actually a little more complex than that. The ArgList contains
default values for arguments. So that first stage itself has to call
node_to_value() to recreate those values.)
The two converse serialization operations are also available.
ArgList.to_node() turns an ArgList into an S-expression tree, using
the type_to_node() and value_to_node() helper functions.
-- The wrapping problem
One more wrinkle needs to be managed. In the example:
boodler " org.boodler.manage/Simultaneous
(com.eblong.zarf.heartbeat/OneBeat 1.5)
(com.eblong.zarf.heartbeat/OneBeat 0.9) "
...the Simultaneous agent can be constructed with two OneBeat agents
as arguments. Its task is to start up each one, which is
straightforward.
However, consider the example:
boodler " org.boodler.manage/Sequential
30 60 2
(com.eblong.zarf.heartbeat/OneBeat 1.5)
(com.eblong.zarf.heartbeat/OneBeat 0.9) "
The Sequential agent alternates between two (or more) agents. (The
three numeric arguments define how frequently it alternates.) So it
will start up the first agent, wait a while, start up the second
agent, wait some more, and then... we have a problem. The first agent
may still be in use! Even if it isn't, it will already have run, so
restarting it may not do anything useful.
(Restarting a heartbeat sound is trivial, since heartbeats are so
repetitive. But in general, you can't restart a soundscape and expect
it to behave the way it did the first time.)
To solve this, the Sequential agent's ArgList says that its arguments
are, not Agents, but *wrapped* Agents. When the command-line data is
parsed, the "(com.eblong.zarf.heartbeat/OneBeat 1.5)" and
"(com.eblong.zarf.heartbeat/OneBeat 0.9)" parts are not fully
constructed as Agent instances. Instead, they are kept in a
partially-constructed state (as ArgWrapper objects). The Sequential
agent receives these two objects. It invokes the first one to get a
OneBeat instance -- correctly instantiated with its 1.5 argument. Then
it invokes the second one to get another OneBeat instance (with 0.9).
Then it invokes the first one again, getting a *new* OneBeat(1.5). And
so on. Each wrapped object can be invoked as often as desired, each
time instantiating a new, fresh Agent instance.
(Other mutable types, such as lists, can also be wrapped in this way.
There is no point in wrapping an immutable type such as int or str,
because it doesn't matter whether you get the same instance twice or
two identical instances.)
-- Contents
Classes:
ArgList -- represents the argument structure of a function
Arg -- represents one argument in an ArgList
ArgExtra -- represents extra positional arguments in an ArgList
ArgDefError -- represents an error constructing an ArgList
SequenceOf -- base class for ListOf and TupleOf
ListOf -- represents a list type
TupleOf -- represents a tuple type
Wrapped -- represents a type which needs to be instantiated lazily
Utility functions:
infer_type() -- given a data value, return an object representing its type
check_valid_type() -- make sure that type is a valid type for an ArgList entry
type_to_node() -- construct an S-expression from a type
node_to_type() -- construct a type from an S-expression
value_to_node() -- construct an S-expression from a value, matching a type
node_to_value() -- construct a value from an S-expression, matching a type
Internal classes:
ArgWrapper -- base class for wrapped value classes
ArgClassWrapper -- represents a wrapped class instance
ArgListWrapper -- represents a wrapped list
ArgTupleWrapper -- represents a wrapped tuple
Internal functions:
seq_value_to_node() -- construct an S-expression from a sequence value
node_to_seq_value() -- construct a sequence value from an S-expression
find_resource_ref() -- work out the representation of a resource
resolve_value() -- resolve a value or wrapped value
Modules | ||||||
|
Classes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Data | ||
__all__ = ['Arg', 'ArgDefError', 'ArgExtra', 'ArgList', 'ListOf', 'SequenceOf', 'TupleOf', 'Wrapped'] |