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
       
boopak.pinfo
boopak.pload
boodle.sample
boopak.sparse
sys
types
boopak.version

 
Classes
       
Arg
ArgExtra
ArgList
SequenceOf
ListOf
TupleOf
Wrapped
exceptions.ValueError(exceptions.StandardError)
ArgDefError

 
class Arg
    Arg: represents one argument in an ArgList.
 
Arg(name=..., index=...,
    type=..., default=..., optional=...,
    description=...) -- constructor
 
All of these values are optional. Note that if you construct an
ArgList with Args in it, their index or name values will be set
automatically.
 
If you specify a default, that implies optional=True. It also implies
type=infer_type(defaultvalue). However, both of these may be
overridden by explicit values.
 
Static methods:
 
from_node() -- construct an Arg from an S-expression
 
Fields: (any of these may be None to indicate an unspecified value)
 
name -- str or unicode
index -- int
type -- a type object (int, str, etc.), or a class object (Sample or
    Agent), or a Wrapped instance.
optional -- bool
description -- str or unicode
default -- anything
hasdefault -- bool; this exists to distinguish a None default from
    an unspecified one.
 
Public methods:
 
clone() -- construct an Arg identical to this one
to_node() -- construct an S-expression from this Arg
absorb() -- merge the attributes of another Arg into this one
 
  Methods defined here:
__init__(self, name=None, index=None, type=None, default=<object object>, optional=None, description=None)
__repr__(self)
absorb(self, arg)
absorb(arg) -> None
 
Merge the attributes of another Arg into this one. This modifies
self, but not the arg argument.
 
The merge algorithm is somewhat ad-hoc. It is intended for a
particular purpose: merging the _args ArgList of an Agent
class with the from_argspec() ArgList.
 
If both the Args have a name attribute, they must be identical;
otherwise ArgDefError is raised. The same goes for index.
 
The optional attribute is always taken from the second Arg.
 
For all other attributes, if the two Args conflict, the first one's
attribute takes precedence.
clone(self)
clone() -> Arg
 
Construct an Arg identical to this one.
to_node(self)
to_node() -> Tree
 
Construct an S-expression from this Arg.

Static methods defined here:
from_node(node)
from_node(node) -> Arg
 
Construct an Arg from an S-expression. The Tree passed in should
be one generated by the to_node() method.

 
class ArgDefError(exceptions.ValueError)
    ArgDefError: represents an error constructing an ArgList.
 
 
Method resolution order:
ArgDefError
exceptions.ValueError
exceptions.StandardError
exceptions.Exception
exceptions.BaseException
__builtin__.object

Data descriptors defined here:
__weakref__
list of weak references to the object (if defined)

Methods inherited from exceptions.ValueError:
__init__(...)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Data and other attributes inherited from exceptions.ValueError:
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T

Methods inherited from exceptions.BaseException:
__delattr__(...)
x.__delattr__('name') <==> del x.name
__getattribute__(...)
x.__getattribute__('name') <==> x.name
__getitem__(...)
x.__getitem__(y) <==> x[y]
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]
 
Use of negative indices is not supported.
__reduce__(...)
__repr__(...)
x.__repr__() <==> repr(x)
__setattr__(...)
x.__setattr__('name', value) <==> x.name = value
__setstate__(...)
__str__(...)
x.__str__() <==> str(x)

Data descriptors inherited from exceptions.BaseException:
__dict__
args
message
exception message

 
class ArgExtra
    ArgExtra: represents extra positional arguments, when constructing
an ArgList.
 
  Methods defined here:
__init__(self, type=<type 'list'>)

 
class ArgList
    ArgList: represents the argument structure of a function. This
includes the number of arguments, their names, and their types.
 
Actually, arguments can be specified by position *or* by name,
although both is more common. The ArgList can also specify
extra positional arguments -- describing the f(*ls) Python
syntax. (It cannot currently describe the f(**dic) syntax.)
 
ArgList(...) -- constructor
 
You can construct the ArgList with positional arguments:
 
    ArgList(Arg(...), Arg(...), ...)
 
...or with named arguments:
 
    ArgList(x=Arg(...), y=Arg(...), ...)
 
The former is equivalent to specifying the index= value in the Arg
constructor; the latter, the name= value.
 
You can also include an ArgExtra object in the arguments:
 
    ArgList(ArgExtra(...))
 
This defines the type of extra positional arguments. Note that the
ArgExtra must be included before any named arguments.
 
Static methods:
 
from_argspec() -- construct an ArgList from a Python function
merge() -- construct an ArgList by merging two ArgLists
from_node() -- construct an ArgList from an S-expression
 
Internal methods:
 
sort_args() -- finish constructing the ArgList
 
Public methods:
 
get_index() -- return the Arg with the given index number
get_name() -- return the Arg with the given name
clone() -- construct an ArgList identical to this one
to_node() -- construct an S-expression from this ArgList
dump() -- print out the ArgList
max_accepted() -- return the maximum number of positional arguments
min_accepted() -- return the minimum number of positional arguments
resolve() -- match a Tree of argument values against the ArgList
 
  Methods defined here:
__init__(self, *ls, **dic)
__len__(self)
__nonzero__(self)
__repr__(self)
clone(self)
clone() -> ArgList
 
Construct an ArgList identical to this one.
dump(self, fl=<open file '<stdout>', mode 'w'>)
dump(fl=sys.stdout) -> None
 
Print out the ArgList to stdout, or another stream. This method
prints in a human-readable form; it is intended for debugging.
get_index(self, val)
get_index(val) -> Arg
 
Return the Arg with the given index number. If there isn't one,
returns None.
get_name(self, val)
get_name(val) -> Arg
 
Return the Arg with the given name. If there isn't one,
returns None.
max_accepted(self)
max_accepted() -> int
 
Return the maximum number of positional arguments accepted by
the ArgList. If it accepts extra positional arguments, this
returns None.
 
### this could take listtype.max into account. necessary?
min_accepted(self)
min_accepted() -> int
 
Return the minimum number of positional arguments accepted by
the ArgList.
resolve(self, node)
resolve(node) -> (list, dict)
 
Match a Tree of argument values against the ArgList. Convert each
value to the appropriate type, fill in any necessary default
values, and return the values needed to call the function that
the ArgList represents.
 
The Tree must be a List with at least one (positional) value.
This first value is ignored -- it is assumed to be the name
of the function that this ArgList came from.
 
Raises ArgDefError if the arguments fail to match in any way.
(Too few, too many, can't be converted to the right type...)
 
The return values are a list and dict, such as you might expect
to use in the form f(*ls, **dic). However, you wouldn't actually
do that, because the contents of the list and dict are wrapped
values. (See the ArgWrapper class.) You could use the resolve()
function this way:
 
    (ls, dic) = arglist.resolve(tree)
    clas = ArgClassWrapper(f, ls, dic)
    clas()
sort_args(self)
sort_args() -> None
 
Finish constructing the ArgList. This puts the arguments in a
consistent order. It raises ArgDefError if there are any
inconsistencies (like two arguments with the same name).
 
This is an internal method. It should only be called by methods
that construct ArgLists.
to_node(self)
to_node() -> Tree
 
Construct an S-expression from this ArgList.

Static methods defined here:
from_argspec(args, varargs, varkw, defaults)
from_argspec(args, varargs, varkw, defaults) -> ArgList
 
Construct an ArgList from a Python function. The four arguments
are those returned by inspect.getargspec() -- see the inspect module
in the Python standard library.
 
This uses the names and positions of the function arguments
to define an ArgList. If an argument has a default value, its
value and type are used as well.
 
If the function has extra positional arguments -- f(*ls) --
they are taken to be an arbitrary number of untyped arguments.
 
If the function has extra named arguments -- f(**dic) --
then ArgDefError is raised.
from_node(node)
from_node(node) -> ArgList
 
Construct an ArgList from an S-expression. The Tree passed in should
be one generated by the to_node() method.
merge(arglist1, arglist2=None)
merge(arglist1, arglist2=None) -> ArgList
 
Construct an ArgList by merging two ArgLists. This does not
modify the arguments; it creates a new ArgList.
 
The merge algorithm is somewhat ad-hoc. It is intended for a
particular purpose: merging the _args ArgList of an Agent
class with the from_argspec() ArgList.
 
If the second list is None, this returns (a clone of) the first list.
 
Otherwise, each argument in the second list is considered. If it
exists in the first list (same index or name), their characteristics
are merged. If not, the argument is appended to the first list.
 
When arguments are merged, the characteristics in the first list
take precedence -- except for the optional flag, which is always
taken from the second list. (It makes sense, honest.)

 
class ListOf(SequenceOf)
    ListOf: represents a list type, with additional information about
the types of the list elements.
 
You can use a ListOf instance (not the class!) as a type value in
an Arg.
 
ListOf(type, type, ..., min=0, max=None, repeat=...) -- constructor
 
The common case is to give one type: e.g., ListOf(int). This represents
a list of integers -- any length, from zero on up.
 
You can give several types: ListOf(int, str, bool). The types in
the list will be required to repeat, in that order. A list satisfying
this example might be [5, 'x', True, -5, 'y'].
 
If you simply say ListOf(), this is equivalent to ListOf(None): a
list of values of unspecified type and length. These are both
equivalent to simply specifying list as a type argument.
 
The optional min and max arguments constrain the length of the list.
(The default max=None means a list with no upper limit.)
 
If you specify a repeat argument, it determines how many of the
types in the list will repeat (if more values are given than the
ListOf has types). The default is "all of them". A type specified
as ListOf(int, str, bool, repeat=1) would be satisfied by
[1, 'x', True, False, True].
 
Public method:
 
to_node() -- construct an S-expression from this ListOf
 
  Methods defined here:
default_setup(self, notypes)

Data and other attributes defined here:
classname = 'ListOf'

Methods inherited from SequenceOf:
__init__(self, *types, **opts)
__repr__(self)
to_node(self)
to_node() -> Tree
 
Construct an S-expression from this type object.

 
class SequenceOf
    SequenceOf: base class for ListOf and TupleOf.
 
You cannot instantiate this class directly.
 
  Methods defined here:
__init__(self, *types, **opts)
__repr__(self)
to_node(self)
to_node() -> Tree
 
Construct an S-expression from this type object.

Data and other attributes defined here:
classname = None

 
class TupleOf(SequenceOf)
    TupleOf: represents a tuple type, with additional information about
the types of the tuple elements.
 
You can use a TupleOf instance (not the class!) as a type value in
an Arg.
 
TupleOf(type, type, ..., min=..., max=..., repeat=...) -- constructor
 
The common case is to give several types: e.g., TupleOf(int, str, bool).
This represents a tuple of the given types, in that order.
 
You can give several types: TupleOf(int, str, bool). The types in
the list will be required to repeat, in that order. A list satisfying
this example might be [5, 'x', True, -5, 'y'].
 
If you simply say TupleOf(), this is a tuple of values of unspecified
type and length. This is equivalent to simply specifying tuple as a
type argument. (It is not, however, equivalent to TupleOf(None) --
that means exactly one value of unspecified type.)
 
The optional min and max arguments constrain the length of the tuple.
(The default is that both min and max are equal to the number of types
given.)
 
If you specify a repeat argument, it determines how many of the
types in the tuple will repeat (if more values are given than the
TupleOf has types). The default is "all of them". A type specified
as TupleOf(int, str, bool, repeat=1, max=5) would be satisfied by
(1, 'x', True, False, True).
 
Public method:
 
to_node() -- construct an S-expression from this TupleOf
 
  Methods defined here:
default_setup(self, notypes)

Data and other attributes defined here:
classname = 'TupleOf'

Methods inherited from SequenceOf:
__init__(self, *types, **opts)
__repr__(self)
to_node(self)
to_node() -> Tree
 
Construct an S-expression from this type object.

 
class Wrapped
    Wrapped: represents a type which needs to be instantiated lazily.
Whenever you are creating an ArgList, you can use Wrapped(type)
instead of type.
 
The difference is that when the ArgList is resolved, values that
correspond with a type are instantiated immediately. Values that
correspond with a Wrapped type are left as placeholders, and can
be instantiated later -- as many times as desired.
 
Let me explain further. The usual pattern for resolving an ArgList
looks like:
 
    (ls, dic) = arglist.resolve(tree)
    clas = ArgClassWrapper(f, ls, dic)
    clas()
 
Plain types are instantiated in the resolve() method. Wrapped types
are instantiated in the clas() invocation. If clas() is invoked
a second time, the wrapped types get a second instantiation,
independent of the first.
 
For immutable types (int, float, str, tuple) this makes no difference.
You never need to wrap those types. But for mutable types, the
difference is important. If the ArgList contains an Agent type,
an Agent instance will be created at resolve() time, and *shared*
between all clas() calls. If it instead contains a Wrapped(Agent)
type, each clas() call will get a *new* Agent instance.
 
Wrapped(type) -- constructor
 
The type must be a plain type (int, float, str, bool), or one of
Boodler's core classes (Agent or Sample), or a sequence placeholder
(a ListOf or TupleOf instance). Using long or unicode is equivalent
to int or str, respectively. Using list is equivalent to ListOf();
tuple is equivalent to TupleOf(). None indicates an unconstrained
value or list of values. Any other type argument will raise
ArgDefError.
 
  Methods defined here:
__init__(self, type)

 
Data
        __all__ = ['Arg', 'ArgDefError', 'ArgExtra', 'ArgList', 'ListOf', 'SequenceOf', 'TupleOf', 'Wrapped']