Note
Click here to download the full example code
MultiContainerInterface¶
This is a guide to creating custom API classes with the MultiContainerInterface class.
Introduction¶
The MultiContainerInterface class provides an easy
and convenient way to create standard methods for a container class that contains
a collection of containers of a specified type. For
example, let’s say you want to define a class MyContainerHolder that
contains a collection of MyContainer objects. By having MyContainerHolder
extend MultiContainerInterface
and specifying certain configuration settings
in the class, your MyContainerHolder class would be generated with:
an attribute for a labelled dictionary that holds
MyContainerobjectsan
__init__method to initializeMyContainerHolderwith a collection ofMyContainerobjectsa method to add
MyContainerobjects to the dictionaryaccess of items from the dictionary using
__getitem__(square bracket notation)a method to get
MyContainerobjects from the dictionary (optional)a method to create
MyContainerobjects and add them to the dictionary (optional)
Specifying the class configuration¶
To specify the class configuration for a
MultiContainerInterface subclass, define the variable
__clsconf__ in the new class. __clsconf__ should be set to a dictionary
with three required keys, 'attr', 'type', and 'add'.
The 'attr' key should map to a string value that is the name of the attribute that
will be created to hold the collection of container objects.
The 'type' key should map to a type or a tuple of types that says what objects
are allowed in this collection.
The 'add' key should map to a string value that is the name of the
method to be generated that allows users to add a container to the collection.
from hdmf.container import Container, MultiContainerInterface
class ContainerHolder(MultiContainerInterface):
__clsconf__ = {
'attr': 'containers',
'type': Container,
'add': 'add_container',
}
The above code will generate:
the attribute
containersas aLabelledDictthat holdsContainerobjectsthe
__init__method which accepts a collection ofContainerobjectsthe
add_containermethod that allows users to addContainerobjects to thecontainersdictionary.
Here is an example of instantiating the new ContainerHolder class and
using the generated add method.
obj1 = Container('obj1')
obj2 = Container('obj2')
holder1 = ContainerHolder()
holder1.add_container(obj1)
holder1.add_container(obj2)
holder1.containers # this is a LabelledDict where the keys are the name of the container
# i.e., {'obj1': obj1, 'obj2': obj2}
Constructor options¶
The constructor accepts a dict/list/tuple of Container objects, a single
Container object, or None. If a dict is passed, only the dict values are used.
You can specify the argument as a keyword argument with the attribute name as
the keyword argument key.
holder2 = ContainerHolder(obj1)
holder3 = ContainerHolder([obj1, obj2])
holder4 = ContainerHolder({'unused_key1': obj1, 'unused_key2': obj2})
holder5 = ContainerHolder(containers=obj1)
By default, the new class has the ‘name’ attribute set to the name of the class, but a user-specified name can be provided in the constructor.
named_holder = ContainerHolder(name='My Holder')
Adding containers to the collection¶
Similar to the constructor, the generated add method accepts a dict/list/tuple
of Container objects or a single Container object. If a dict is passed,
only the dict values are used.
holder6 = ContainerHolder()
holder6.add_container(obj1)
holder7 = ContainerHolder()
holder7.add_container([obj1, obj2])
holder8 = ContainerHolder()
holder8.add_container({'unused_key1': obj1, 'unused_key2': obj2})
holder9 = ContainerHolder()
holder9.add_container(containers=obj1)
Getting items from the collection¶
You can access a container in the collection by using the name of the container within square brackets. As a convenience, if there is only one item in the collection, you can use None within square brackets.
holder10 = ContainerHolder(obj1)
holder10['obj1']
holder10[None]
Getting items from the collection using a custom getter¶
You can use the 'get' key in __clsconf__ to generate a getter method as
an alternative to using the square bracket notation for accessing items from
the collection. Like the square bracket notation, if there is only one item
in the collection, you can omit the name or pass None to the getter method.
The 'get' key should map to a string value that is the name of the getter
method to be generated. The 'get' key in __clsconf__ is optional.
class ContainerHolderWithGet(MultiContainerInterface):
__clsconf__ = {
'attr': 'containers',
'type': Container,
'add': 'add_container',
'get': 'get_container',
}
holder11 = ContainerHolderWithGet(obj1)
holder11.get_container('obj1')
holder11.get_container()
Creating and adding items to the collection using a custom create method¶
You can use the 'create' key in __clsconf__ to generate a create method
as a convenience method so that users do not need to initialize the
Container object and then add it to the collection. Those two steps are
combined into one line. The arguments to the custom create method are the
same as the arguments to the Container’s __init__ method, but the
__init__ method must be defined using docval.
The created object will be returned by the create method.
The 'create' key should map to a string value that is the name of the create
method to be generated. The 'create' key in __clsconf__ is optional.
class ContainerHolderWithCreate(MultiContainerInterface):
__clsconf__ = {
'attr': 'containers',
'type': Container,
'add': 'add_container',
'create': 'create_container',
}
holder12 = ContainerHolderWithCreate()
holder12.create_container('obj1')
Specifying multiple types allowed in the collection¶
The 'type' key in __clsconf__ allows specifying a single type or a
list/tuple of types.
You cannot specify the 'create' key in __clsconf__
when multiple types are allowed in the collection because it cannot be
determined which type to initialize.
from hdmf.container import Data
class ContainerHolderWithMultipleTypes(MultiContainerInterface):
__clsconf__ = {
'attr': 'things',
'type': (Container, Data),
'add': 'add_thing',
}
Specifying multiple collections¶
You can support multiple collections in your
MultiContainerInterface
subclass by setting the __clsconf__ variable to a list of dicts instead
of a single dict.
When specifying multiple collections, square bracket notation access of items
(i.e., calling __getitem__) is not supported, because it is not clear
which collection to get the desired item from.
from hdmf.container import Data
class MultiCollectionHolder(MultiContainerInterface):
__clsconf__ = [
{
'attr': 'containers',
'type': Container,
'add': 'add_container',
},
{
'attr': 'data',
'type': Data,
'add': 'add_data',
},
]
Managing container parents¶
If the parent of the container being added is not already set, then the parent will be set to the containing object.
obj3 = Container('obj3')
holder13 = ContainerHolder(obj3)
obj3.parent # this is holder13
LabelledDict objects support removal of an item using
the del operator or the pop
method. If the parent of the container being removed is the containing object,
then its parent will be reset to None.
del holder13.containers['obj3']
obj3.parent # this is back to None
Using a custom constructor¶
You can override the automatically generated constructor for your
MultiContainerInterface subclass.
class ContainerHolderWithCustomInit(MultiContainerInterface):
__clsconf__ = {
'attr': 'containers',
'type': Container,
'add': 'add_container',
}
def __init__(self, name, my_containers):
super().__init__(name=name)
self.containers = my_containers
self.add_container(Container('extra_container'))
holder14 = ContainerHolderWithCustomInit('my_name', [obj1, obj2])
holder14.containers # contains the 'extra_container' container