Note
Go to the end 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
MyContainer
objectsan
__init__
method to initializeMyContainerHolder
with a collection ofMyContainer
objectsa method to add
MyContainer
objects to the dictionaryaccess of items from the dictionary using
__getitem__
(square bracket notation)a method to get
MyContainer
objects from the dictionary (optional)a method to create
MyContainer
objects 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
containers
as aLabelledDict
that holdsContainer
objectsthe
__init__
method which accepts a collection ofContainer
objectsthe
add_container
method that allows users to addContainer
objects to thecontainers
dictionary.
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