Python: module boopak.argdef

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.py " 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.py " 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.py " 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']