Building API classes

After you have written an extension, you will need a Pythonic way to interact with the data model. To do this, you will need to write some classes that represent the data you defined in your specificiation extensions. The pynwb.core module has various tools to make it easier to write classes that behave like the rest of the PyNWB API.

The pynwb.core defines two base classes that represent the primitive structures supported by the schema. NWBData represents datasets and NWBContainer represents groups. Additionally, pynwb.core offers subclasses of these two classes for writing classes that come with more functionality.

register_class

When defining a class that represents a neurodata_type (i.e. anything that has a neurodata_type_def) from your extension, you can tell PyNWB which neurodata_type it represents using the function register_class. This class can be called on its own, or used as a class decorator. The first argument should be the neurodata_type and the second argument should be the namespace name.

The following example demonstrates how to register a class as the Python class reprsentation of the neurodata_type “MyContainer” from the namespace “my_ns”.

from pynwb import register_class
from pynwb.core import NWBContainer

class MyContainer(NWBContainer):
    ...

regitser_class('MyContainer', 'my_ns', MyContainer)

Alternatively, you can use register_class as a decorator.

from pynwb import register_class
from pynwb.core import NWBContainer

@regitser_class('MyContainer', 'my_ns')
class MyContainer(NWBContainer):
    ...

register_class is used with NWBData the same way it is used with NWBContainer.

__nwbfields__

When subclassing NWBData or NWBContainer, you might want to define some properties on your class. This can be done using the __nwbfields__ class property. This class property should be a tuple of strings that name the properties. Adding a property using this functionality will create a property than can be set only once.

For example, the following class definition will create the MyContainer class that has the properties foo and bar.

from pynwb import register_class
from pynwb.core import NWBContainer


class MyContainer(NWBContainer):

    __nwbfields__ = ('foo', 'bar')

    ...

NWBData

NWBData should be used to represent datasets with a neurodata_type_def. This section
will discuss the available NWBData subclasses for representing common dataset specifications.

NWBTable

If your specification extension contains a table definition i.e. a dataset with a compound data type, you should use the NWBTable class to represent this specification. Since NWBTable subclasses NWBData you can still use __nwbfields__. In addition, you can use the __columns__ class property to specify the columns of the table. __columns__ should be a list or a tuple of docval-like dictionaries.

The following example demonstrates how to define a table with the columns foo and bar that are of type str and int, respectively. We also register the class as the reppresentation of the neurodata_type “MyTable” from the namespace “my_ns”.

from pynwb import register_class
from pynwb.core import NWBTable


@register_class('MyTable', 'my_ns')
class MyTable(NWBTable):

    __columns__ = [
        {'name': 'foo', 'type': str, 'doc': 'the foo column'},
        {'name': 'bar', 'type': int, 'doc': 'the bar column'},
    ]

    ...

NWBTableRegion

NWBTableRegion should be used to represent datasets that store a region reference. The constructor for NWBTableRegion. When subclassing this class, make sure you provide a way to pass in the required arguments for the NWBTableRegion constructor–the name of the dataset, the table that the region applies to, and the region itself.

NWBContainer

NWBContainer should be used to represent groups with a neurodata_type_def. This section will discuss the available NWBContainer subclasses for representing common group specifications.

NWBDataInterface

The NWB schema users the neurodata type NWBDataInterface for specifying containers that contain data that is not considered metadata. For example, NWBDataInterface is a parent neurodata type to ElectricalSeries data, but not a parent to ElectrodeGroup.

There are no requirements for using NWBDataInterface in addition to those inherited from NWBContainer.

MultiContainerInterface

Throughout the NWB schema, there are multiple NWBDataInterface specifications that include one or more or zero or more of a certain neurodata type. For example, the LFP neurodata type contains one or more ElectricalSeries. If your extension follows this pattern, you can use MultiContainerInterface for defining the representative class.

MultiContainerInterface provides a way of automatically generating setters, getters, and properties for your class. These methods are autogenerated based on a configuration provided using the class property __clsconf__. __clsconf__ should be a dict or a list of dicts. A single dict should be used if your specification contains a single neurodata type. A list of dicts should be used if your specification contains multiple neurodata types that will exist as one or more or zero or more. The contents of the dict are described in the following table.

Key Attribute Required?
type the type of the Container Yes
attr the property name that holds the Containers Yes
add the name of the method for adding a Container Yes
create the name of the method for creating a Container No
get the name of the method for getting a Container by name Yes

The type key provides a way for the setters to check for type. The property under the name given by the. attr key will be a LabelledDict. If your class uses a single dict, a __getitem__ method will be autogenerated for indexing into this LabelledDict. Finally, a constructor will also be autogenerated if you do not provide one in the class definition.

The following code block demonstrates using MultiContainerInterface to build a class that represents the neurodata type “MyDataInterface” from the namespace “my_ns”. It contains one or more containers with neurodata type “MyContainer”.

from pynwb import register_class
from pynwb.core import MultiContainerInterface


@register_class("MyDataInterface", "my_ns")
class MyDataInterface(MultiContainerInterface):

    __clsconf__ = {
        'type': MyContainer,
        'attr': 'containers',
        'add': 'add_container',
        'create': 'create_container',
        'get': 'get_container',
    }
    ...

This class will have the methods add_container, create_container, and get_container. It will also have the property containers. The add_container method will check to make sure that either an object of type MyContainer or a list/dict/tuple of objects of type MyContainer is passed in. create_container will accept the exact same arguments that the MyContainer class constructor accepts.