123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- """
- This module implements generic container base class that all neo container
- object inherit from. It provides shared methods for all container types.
- :class:`Container` is derived from :class:`BaseNeo`
- """
- from copy import deepcopy
- from neo.core.baseneo import BaseNeo, _reference_name, _container_name
- def unique_objs(objs):
- """
- Return a list of objects in the list objs where all objects are unique
- using the "is" test.
- """
- seen = set()
- return [obj for obj in objs
- if id(obj) not in seen and not seen.add(id(obj))]
- def filterdata(data, targdict=None, objects=None, **kwargs):
- """
- Return a list of the objects in data matching *any* of the search terms
- in either their attributes or annotations. Search terms can be
- provided as keyword arguments or a dictionary, either as a positional
- argument after data or to the argument targdict. targdict can also
- be a list of dictionaries, in which case the filters are applied
- sequentially. If targdict and kwargs are both supplied, the
- targdict filters are applied first, followed by the kwarg filters.
- A targdict of None or {} and objects = None corresponds to no filters
- applied, therefore returning all child objects.
- Default targdict and objects is None.
- objects (optional) should be the name of a Neo object type,
- a neo object class, or a list of one or both of these. If specified,
- only these objects will be returned.
- """
- # if objects are specified, get the classes
- if objects:
- if hasattr(objects, 'lower') or isinstance(objects, type):
- objects = [objects]
- elif objects is not None:
- return []
- # handle cases with targdict
- if targdict is None:
- targdict = kwargs
- elif not kwargs:
- pass
- elif hasattr(targdict, 'keys'):
- targdict = [targdict, kwargs]
- else:
- targdict += [kwargs]
- if not targdict:
- results = data
- # if multiple dicts are provided, apply each filter sequentially
- elif not hasattr(targdict, 'keys'):
- # for performance reasons, only do the object filtering on the first
- # iteration
- results = filterdata(data, targdict=targdict[0], objects=objects)
- for targ in targdict[1:]:
- results = filterdata(results, targdict=targ)
- return results
- else:
- # do the actual filtering
- results = []
- for key, value in sorted(targdict.items()):
- for obj in data:
- if (hasattr(obj, key) and getattr(obj, key) == value and
- all([obj is not res for res in results])):
- results.append(obj)
- elif (key in obj.annotations and obj.annotations[key] == value and
- all([obj is not res for res in results])):
- results.append(obj)
- # keep only objects of the correct classes
- if objects:
- results = [result for result in results if
- result.__class__ in objects or
- result.__class__.__name__ in objects]
- return results
- class Container(BaseNeo):
- """
- This is the base class from which Neo container objects inherit. It
- derives from :class:`BaseNeo`.
- In addition to the setup :class:`BaseNeo` does, this class also
- automatically sets up the lists to hold the children of the object.
- Each class can define one or more of the following class attributes
- (in addition to those of BaseNeo):
- :_container_child_objects: Neo container objects that can be children
- of this object. This attribute is used in
- cases where the child can only have one
- parent of this type. An instance attribute
- named class.__name__.lower()+'s' will be
- automatically defined to hold this child and
- will be initialized to an empty list.
- :_data_child_objects: Neo data objects that can be children
- of this object. An instance attribute named
- class.__name__.lower()+'s' will be automatically
- defined to hold this child and will be
- initialized to an empty list.
- :_multi_child_objects: Neo container objects that can be children
- of this object. This attribute is used in
- cases where the child can have multiple
- parents of this type. An instance attribute
- named class.__name__.lower()+'s' will be
- automatically defined to hold this child and
- will be initialized to an empty list.
- :_child_properties: Properties that return sub-children of a particular
- type. These properties must still be defined.
- This is mostly used for generate_diagram.
- :_repr_pretty_containers: The names of containers attributes printed
- when pretty-printing using iPython.
- The following helper properties are available
- (in addition to those of BaseNeo):
- :_single_child_objects: All neo container objects that can be children
- of this object and where the child can only
- have one parent of this type.
- :_container_child_objects: +
- :_data_child_objects:
- :_child_objects: All child objects.
- :_single_child_objects: + :_multi_child_objects:
- :_container_child_containers: The names of the container attributes
- used to store :_container_child_objects:
- :_data_child_containers: The names of the container attributes used
- to store :_data_child_objects:
- :_single_child_containers: The names of the container attributes used
- to store :_single_child_objects:
- :_multi_child_containers: The names of the container attributes used
- to store :_multi_child_objects:
- :_child_containers: All child container attributes.
- :_single_child_containers: +
- :_multi_child_containers:
- :_single_children: All objects that are children of the current object
- where the child can only have one parent of
- this type.
- :_multi_children: All objects that are children of the current object
- where the child can have multiple parents of
- this type.
- :data_children: All data objects that are children of
- the current object.
- :container_children: All container objects that are children of
- the current object.
- :children: All Neo objects that are children of the current object.
- :data_children_recur: All data objects that are children of
- the current object or any of its children,
- any of its children's children, etc.
- :container_children_recur: All container objects that are children of
- the current object or any of its children,
- any of its children's children, etc.
- :children_recur: All Neo objects that are children of
- the current object or any of its children,
- any of its children's children, etc.
- The following "universal" methods are available
- (in addition to those of BaseNeo):
- :size: A dictionary where each key is an attribute storing child
- objects and the value is the number of objects stored in that
- attribute.
- :filter(**args): Retrieves children of the current object that
- have particular properties.
- :list_children_by_class(**args): Retrieves all children of the current
- object recursively that are of a
- particular class.
- :create_many_to_one_relationship(**args): For each child of the current
- object that can only have a
- single parent, set its parent
- to be the current object.
- :create_many_to_many_relationship(**args): For children of the current
- object that can have more
- than one parent of this
- type, put the current
- object in the parent list.
- :create_relationship(**args): Combines
- :create_many_to_one_relationship: and
- :create_many_to_many_relationship:
- :merge(**args): Annotations are merged based on the rules of
- :merge_annotations:. Child objects with the same name
- and a :merge: method are merged using that method.
- Other child objects are appended to the relevant
- container attribute. Parents attributes are NOT
- changed in this operation.
- Unlike :BaseNeo.merge:, this method implements
- all necessary merge rules for a container class.
- Each child class should:
- 0) call Container.__init__(self, name=name, description=description,
- file_origin=file_origin, **annotations)
- with the universal recommended arguments, plus optional annotations
- 1) process its required arguments in its __new__ or __init__ method
- 2) process its non-universal recommended arguments (in its __new__ or
- __init__ method
- """
- # Child objects that are a container and have a single parent
- _container_child_objects = ()
- # Child objects that have data and have a single parent
- _data_child_objects = ()
- # Child objects that can have multiple parents
- _multi_child_objects = ()
- # Properties returning children of children [of children...]
- _child_properties = ()
- # Containers that are listed when pretty-printing
- _repr_pretty_containers = ()
- def __init__(self, name=None, description=None, file_origin=None,
- **annotations):
- """
- Initalize a new :class:`Container` instance.
- """
- super().__init__(name=name, description=description,
- file_origin=file_origin, **annotations)
- # initialize containers
- for container in self._child_containers:
- setattr(self, container, [])
- @property
- def _single_child_objects(self):
- """
- Child objects that have a single parent.
- """
- return self._container_child_objects + self._data_child_objects
- @property
- def _container_child_containers(self):
- """
- Containers for child objects that are a container and
- have a single parent.
- """
- return tuple([_container_name(child) for child in
- self._container_child_objects])
- @property
- def _data_child_containers(self):
- """
- Containers for child objects that have data and have a single parent.
- """
- return tuple([_container_name(child) for child in
- self._data_child_objects])
- @property
- def _single_child_containers(self):
- """
- Containers for child objects with a single parent.
- """
- return tuple([_container_name(child) for child in
- self._single_child_objects])
- @property
- def _multi_child_containers(self):
- """
- Containers for child objects that can have multiple parents.
- """
- return tuple([_container_name(child) for child in
- self._multi_child_objects])
- @property
- def _child_objects(self):
- """
- All types for child objects.
- """
- return self._single_child_objects + self._multi_child_objects
- @property
- def _child_containers(self):
- """
- All containers for child objects.
- """
- return self._single_child_containers + self._multi_child_containers
- @property
- def _single_children(self):
- """
- All child objects that can only have single parents.
- """
- childs = [list(getattr(self, attr)) for attr in
- self._single_child_containers]
- return tuple(sum(childs, []))
- @property
- def _multi_children(self):
- """
- All child objects that can have multiple parents.
- """
- childs = [list(getattr(self, attr)) for attr in
- self._multi_child_containers]
- return tuple(sum(childs, []))
- @property
- def data_children(self):
- """
- All data child objects stored in the current object.
- Not recursive.
- """
- childs = [list(getattr(self, attr)) for attr in
- self._data_child_containers]
- return tuple(sum(childs, []))
- @property
- def container_children(self):
- """
- All container child objects stored in the current object.
- Not recursive.
- """
- childs = [list(getattr(self, attr)) for attr in
- self._container_child_containers +
- self._multi_child_containers]
- return tuple(sum(childs, []))
- @property
- def children(self):
- """
- All child objects stored in the current object.
- Not recursive.
- """
- return self.data_children + self.container_children
- @property
- def data_children_recur(self):
- """
- All data child objects stored in the current object,
- obtained recursively.
- """
- childs = [list(child.data_children_recur) for child in
- self.container_children]
- return self.data_children + tuple(sum(childs, []))
- @property
- def container_children_recur(self):
- """
- All container child objects stored in the current object,
- obtained recursively.
- """
- childs = [list(child.container_children_recur) for child in
- self.container_children]
- return self.container_children + tuple(sum(childs, []))
- @property
- def children_recur(self):
- """
- All child objects stored in the current object,
- obtained recursively.
- """
- return self.data_children_recur + self.container_children_recur
- @property
- def size(self):
- """
- Get dictionary containing the names of child containers in the current
- object as keys and the number of children of that type as values.
- """
- return {name: len(getattr(self, name))
- for name in self._child_containers}
- def filter(self, targdict=None, data=True, container=False, recursive=True,
- objects=None, **kwargs):
- """
- Return a list of child objects matching *any* of the search terms
- in either their attributes or annotations. Search terms can be
- provided as keyword arguments or a dictionary, either as a positional
- argument after data or to the argument targdict. targdict can also
- be a list of dictionaries, in which case the filters are applied
- sequentially. If targdict and kwargs are both supplied, the
- targdict filters are applied first, followed by the kwarg filters.
- A targdict of None or {} corresponds to no filters applied, therefore
- returning all child objects. Default targdict is None.
- If data is True (default), include data objects.
- If container is True (default False), include container objects.
- If recursive is True (default), descend into child containers for
- objects.
- objects (optional) should be the name of a Neo object type,
- a neo object class, or a list of one or both of these. If specified,
- only these objects will be returned. If not specified any type of
- object is returned. Default is None.
- Note that if recursive is True, containers not in objects will still
- be descended into. This overrides data and container.
- Examples::
- >>> obj.filter(name="Vm")
- >>> obj.filter(objects=neo.SpikeTrain)
- >>> obj.filter(targdict={'myannotation':3})
- """
- if isinstance(targdict, str):
- raise TypeError("filtering is based on key-value pairs."
- " Only a single string was provided.")
- # if objects are specified, get the classes
- if objects:
- data = True
- container = True
- children = []
- # get the objects we want
- if data:
- if recursive:
- children.extend(self.data_children_recur)
- else:
- children.extend(self.data_children)
- if container:
- if recursive:
- children.extend(self.container_children_recur)
- else:
- children.extend(self.container_children)
- return filterdata(children, objects=objects,
- targdict=targdict, **kwargs)
- def list_children_by_class(self, cls):
- """
- List all children of a particular class recursively.
- You can either provide a class object, a class name,
- or the name of the container storing the class.
- """
- if not hasattr(cls, 'lower'):
- cls = cls.__name__
- container_name = _container_name(cls)
- objs = list(getattr(self, container_name, []))
- for child in self.container_children_recur:
- objs.extend(getattr(child, container_name, []))
- return objs
- def create_many_to_one_relationship(self, force=False, recursive=True):
- """
- For each child of the current object that can only have a single
- parent, set its parent to be the current object.
- Usage:
- >>> a_block.create_many_to_one_relationship()
- >>> a_block.create_many_to_one_relationship(force=True)
- If the current object is a :class:`Block`, you want to run
- populate_RecordingChannel first, because this will create new objects
- that this method will link up.
- If force is True overwrite any existing relationships
- If recursive is True desecend into child objects and create
- relationships there
- """
- parent_name = _reference_name(self.__class__.__name__)
- for child in self._single_children:
- if (hasattr(child, parent_name) and
- getattr(child, parent_name) is None or force):
- setattr(child, parent_name, self)
- if recursive:
- for child in self.container_children:
- child.create_many_to_one_relationship(force=force,
- recursive=True)
- def create_many_to_many_relationship(self, append=True, recursive=True):
- """
- For children of the current object that can have more than one parent
- of this type, put the current object in the parent list.
- If append is True add it to the list, otherwise overwrite the list.
- If recursive is True desecend into child objects and create
- relationships there
- """
- parent_name = _container_name(self.__class__.__name__)
- for child in self._multi_children:
- if not hasattr(child, parent_name):
- continue
- if append:
- target = getattr(child, parent_name)
- if self not in target:
- target.append(self)
- continue
- setattr(child, parent_name, [self])
- if recursive:
- for child in self.container_children:
- child.create_many_to_many_relationship(append=append,
- recursive=True)
- def create_relationship(self, force=False, append=True, recursive=True):
- """
- For each child of the current object that can only have a single
- parent, set its parent to be the current object.
- For children of the current object that can have more than one parent
- of this type, put the current object in the parent list.
- If the current object is a :class:`Block`, you want to run
- populate_RecordingChannel first, because this will create new objects
- that this method will link up.
- If force is True overwrite any existing relationships
- If append is True add it to the list, otherwise overwrite the list.
- If recursive is True desecend into child objects and create
- relationships there
- """
- self.create_many_to_one_relationship(force=force, recursive=False)
- self.create_many_to_many_relationship(append=append, recursive=False)
- if recursive:
- for child in self.container_children:
- child.create_relationship(force=force, append=append,
- recursive=True)
- def __deepcopy__(self, memo):
- """
- Creates a deep copy of the container.
- All contained objects will also be deep copied and relationships
- between all objects will be identical to the original relationships.
- Attributes and annotations of the container are deep copied as well.
- :param memo: (dict) Objects that have been deep copied already
- :return: (Container) Deep copy of input Container
- """
- cls = self.__class__
- necessary_attrs = {}
- for k in self._necessary_attrs:
- necessary_attrs[k[0]] = getattr(self, k[0], None)
- new_container = cls(**necessary_attrs)
- new_container.__dict__.update(self.__dict__)
- memo[id(self)] = new_container
- for k, v in self.__dict__.items():
- try:
- setattr(new_container, k, deepcopy(v, memo))
- except TypeError:
- setattr(new_container, k, v)
- new_container.create_relationship()
- return new_container
- def merge(self, other):
- """
- Merge the contents of another object into this one.
- Container children of the current object with the same name will be
- merged. All other objects will be appended to the list of objects
- in this one. Duplicate copies of the same object will be skipped.
- Annotations are merged such that only items not present in the current
- annotations are added.
- Note that the other object will be linked inconsistently to other Neo objects
- after the merge operation and should not be used further.
- """
- # merge containers with the same name
- for container in (self._container_child_containers +
- self._multi_child_containers):
- lookup = {obj.name: obj for obj in getattr(self, container)}
- ids = [id(obj) for obj in getattr(self, container)]
- for obj in getattr(other, container):
- if id(obj) in ids:
- continue
- if obj.name in lookup:
- lookup[obj.name].merge(obj)
- else:
- lookup[obj.name] = obj
- ids.append(id(obj))
- getattr(self, container).append(obj)
- # for data objects, ignore the name and just add them
- for container in self._data_child_containers:
- objs = getattr(self, container)
- lookup = {obj.name: i for i, obj in enumerate(objs)}
- ids = [id(obj) for obj in objs]
- for obj in getattr(other, container):
- if id(obj) in ids:
- pass
- elif hasattr(obj, 'merge') and obj.name is not None and obj.name in lookup:
- ind = lookup[obj.name]
- try:
- newobj = getattr(self, container)[ind].merge(obj)
- getattr(self, container)[ind] = newobj
- except NotImplementedError:
- getattr(self, container).append(obj)
- ids.append(id(obj))
- else:
- lookup[obj.name] = obj
- ids.append(id(obj))
- getattr(self, container).append(obj)
- obj.set_parent(self)
- # use the BaseNeo merge as well
- super().merge(other)
- def _repr_pretty_(self, pp, cycle):
- """
- Handle pretty-printing.
- """
- pp.text(self.__class__.__name__)
- pp.text(" with ")
- vals = []
- for container in self._child_containers:
- objs = getattr(self, container)
- if objs:
- vals.append('{} {}'.format(len(objs), container))
- pp.text(', '.join(vals))
- if self._has_repr_pretty_attrs_():
- pp.breakable()
- self._repr_pretty_attrs_(pp, cycle)
- for container in self._repr_pretty_containers:
- pp.breakable()
- objs = getattr(self, container)
- pp.text("# {} (N={})".format(container, len(objs)))
- for (i, obj) in enumerate(objs):
- pp.breakable()
- pp.text("%s: " % i)
- with pp.indent(3):
- pp.pretty(obj)
|