from warnings import warn
from .. import register_map
from ..device import Device, DeviceModel
from .core import NWBContainerMapper
[docs]
@register_map(Device)
class DeviceMapper(NWBContainerMapper):
"""
Custom mapper for Device objects to handle known schema conflicts between core schema and extensions.
This mapper detects when extensions define Device.model as a string attribute instead of
a link to DeviceModel, or when extensions define their own DeviceModel type.
"""
[docs]
@NWBContainerMapper.constructor_arg("model")
def model_carg(self, builder, manager):
"""
Handle different model mapping strategies based on detected schema conflicts.
Args:
builder: The GroupBuilder for the Device
manager: The BuildManager
Returns:
The appropriate model object or value based on the mapping strategy
"""
model_builder = builder.get('model')
if isinstance(model_builder, str):
warn(
'Device.model was detected as a string, but NWB 2.9 specifies Device.model as a link to a DeviceModel. '
f'Remapping "{model_builder}" to a new DeviceModel.',
stacklevel=3)
# replace the model string with a DeviceModel object using the model name and device attributes
device_model_attributes = dict(name=model_builder,
description=builder.attributes.get('description'),
manufacturer=builder.attributes.get('manufacturer', ''),
model_number=builder.attributes.get('model_number'))
model = DeviceModel(**device_model_attributes)
return model
return None
def __new_container__(self, cls, container_source, parent, object_id, **kwargs):
# Override ObjectMapper.__new_container__ to handle the case where the Device.model argument
# is not a DeviceModel, which can happen in extensions written to be compatible with NWB<2.9.
# The original Device.model object will be accessible under a new attribute name based on the
# extension namespace.
model = kwargs.get('model', None)
if model is None or isinstance(model, DeviceModel):
device_obj = super().__new_container__(cls, container_source, parent, object_id, **kwargs)
else:
# create device object without model
kwargs.pop('model')
device_obj = super().__new_container__(cls, container_source, parent, object_id, **kwargs)
# add the conflicting Device.model object as a new attribute on Device
# e.g. Device.model in the file -> Device.ndx_optogenetics_model in the python object
warn(f'The model attribute of the Device "{device_obj.name}" was detected as a non-DeviceModel '
f'object. Data associated with this object can be accessed at '
f'"nwbfile.devices["{device_obj.name}"].{model.namespace.replace("-", "_")}_model"',
stacklevel=2)
setattr(device_obj, f"{model.namespace.replace('-', '_')}_model", model)
return device_obj