container.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. """
  2. This module implements generic container base class that all neo container
  3. object inherit from. It provides shared methods for all container types.
  4. :class:`Container` is derived from :class:`BaseNeo`
  5. """
  6. from copy import deepcopy
  7. from neo.core.baseneo import BaseNeo, _reference_name, _container_name
  8. def unique_objs(objs):
  9. """
  10. Return a list of objects in the list objs where all objects are unique
  11. using the "is" test.
  12. """
  13. seen = set()
  14. return [obj for obj in objs
  15. if id(obj) not in seen and not seen.add(id(obj))]
  16. def filterdata(data, targdict=None, objects=None, **kwargs):
  17. """
  18. Return a list of the objects in data matching *any* of the search terms
  19. in either their attributes or annotations. Search terms can be
  20. provided as keyword arguments or a dictionary, either as a positional
  21. argument after data or to the argument targdict. targdict can also
  22. be a list of dictionaries, in which case the filters are applied
  23. sequentially. If targdict and kwargs are both supplied, the
  24. targdict filters are applied first, followed by the kwarg filters.
  25. A targdict of None or {} and objects = None corresponds to no filters
  26. applied, therefore returning all child objects.
  27. Default targdict and objects is None.
  28. objects (optional) should be the name of a Neo object type,
  29. a neo object class, or a list of one or both of these. If specified,
  30. only these objects will be returned.
  31. """
  32. # if objects are specified, get the classes
  33. if objects:
  34. if hasattr(objects, 'lower') or isinstance(objects, type):
  35. objects = [objects]
  36. elif objects is not None:
  37. return []
  38. # handle cases with targdict
  39. if targdict is None:
  40. targdict = kwargs
  41. elif not kwargs:
  42. pass
  43. elif hasattr(targdict, 'keys'):
  44. targdict = [targdict, kwargs]
  45. else:
  46. targdict += [kwargs]
  47. if not targdict:
  48. results = data
  49. # if multiple dicts are provided, apply each filter sequentially
  50. elif not hasattr(targdict, 'keys'):
  51. # for performance reasons, only do the object filtering on the first
  52. # iteration
  53. results = filterdata(data, targdict=targdict[0], objects=objects)
  54. for targ in targdict[1:]:
  55. results = filterdata(results, targdict=targ)
  56. return results
  57. else:
  58. # do the actual filtering
  59. results = []
  60. for key, value in sorted(targdict.items()):
  61. for obj in data:
  62. if (hasattr(obj, key) and getattr(obj, key) == value and
  63. all([obj is not res for res in results])):
  64. results.append(obj)
  65. elif (key in obj.annotations and obj.annotations[key] == value and
  66. all([obj is not res for res in results])):
  67. results.append(obj)
  68. # keep only objects of the correct classes
  69. if objects:
  70. results = [result for result in results if
  71. result.__class__ in objects or
  72. result.__class__.__name__ in objects]
  73. return results
  74. class Container(BaseNeo):
  75. """
  76. This is the base class from which Neo container objects inherit. It
  77. derives from :class:`BaseNeo`.
  78. In addition to the setup :class:`BaseNeo` does, this class also
  79. automatically sets up the lists to hold the children of the object.
  80. Each class can define one or more of the following class attributes
  81. (in addition to those of BaseNeo):
  82. :_container_child_objects: Neo container objects that can be children
  83. of this object. This attribute is used in
  84. cases where the child can only have one
  85. parent of this type. An instance attribute
  86. named class.__name__.lower()+'s' will be
  87. automatically defined to hold this child and
  88. will be initialized to an empty list.
  89. :_data_child_objects: Neo data objects that can be children
  90. of this object. An instance attribute named
  91. class.__name__.lower()+'s' will be automatically
  92. defined to hold this child and will be
  93. initialized to an empty list.
  94. :_multi_child_objects: Neo container objects that can be children
  95. of this object. This attribute is used in
  96. cases where the child can have multiple
  97. parents of this type. An instance attribute
  98. named class.__name__.lower()+'s' will be
  99. automatically defined to hold this child and
  100. will be initialized to an empty list.
  101. :_child_properties: Properties that return sub-children of a particular
  102. type. These properties must still be defined.
  103. This is mostly used for generate_diagram.
  104. :_repr_pretty_containers: The names of containers attributes printed
  105. when pretty-printing using iPython.
  106. The following helper properties are available
  107. (in addition to those of BaseNeo):
  108. :_single_child_objects: All neo container objects that can be children
  109. of this object and where the child can only
  110. have one parent of this type.
  111. :_container_child_objects: +
  112. :_data_child_objects:
  113. :_child_objects: All child objects.
  114. :_single_child_objects: + :_multi_child_objects:
  115. :_container_child_containers: The names of the container attributes
  116. used to store :_container_child_objects:
  117. :_data_child_containers: The names of the container attributes used
  118. to store :_data_child_objects:
  119. :_single_child_containers: The names of the container attributes used
  120. to store :_single_child_objects:
  121. :_multi_child_containers: The names of the container attributes used
  122. to store :_multi_child_objects:
  123. :_child_containers: All child container attributes.
  124. :_single_child_containers: +
  125. :_multi_child_containers:
  126. :_single_children: All objects that are children of the current object
  127. where the child can only have one parent of
  128. this type.
  129. :_multi_children: All objects that are children of the current object
  130. where the child can have multiple parents of
  131. this type.
  132. :data_children: All data objects that are children of
  133. the current object.
  134. :container_children: All container objects that are children of
  135. the current object.
  136. :children: All Neo objects that are children of the current object.
  137. :data_children_recur: All data objects that are children of
  138. the current object or any of its children,
  139. any of its children's children, etc.
  140. :container_children_recur: All container objects that are children of
  141. the current object or any of its children,
  142. any of its children's children, etc.
  143. :children_recur: All Neo objects that are children of
  144. the current object or any of its children,
  145. any of its children's children, etc.
  146. The following "universal" methods are available
  147. (in addition to those of BaseNeo):
  148. :size: A dictionary where each key is an attribute storing child
  149. objects and the value is the number of objects stored in that
  150. attribute.
  151. :filter(**args): Retrieves children of the current object that
  152. have particular properties.
  153. :list_children_by_class(**args): Retrieves all children of the current
  154. object recursively that are of a
  155. particular class.
  156. :create_many_to_one_relationship(**args): For each child of the current
  157. object that can only have a
  158. single parent, set its parent
  159. to be the current object.
  160. :create_many_to_many_relationship(**args): For children of the current
  161. object that can have more
  162. than one parent of this
  163. type, put the current
  164. object in the parent list.
  165. :create_relationship(**args): Combines
  166. :create_many_to_one_relationship: and
  167. :create_many_to_many_relationship:
  168. :merge(**args): Annotations are merged based on the rules of
  169. :merge_annotations:. Child objects with the same name
  170. and a :merge: method are merged using that method.
  171. Other child objects are appended to the relevant
  172. container attribute. Parents attributes are NOT
  173. changed in this operation.
  174. Unlike :BaseNeo.merge:, this method implements
  175. all necessary merge rules for a container class.
  176. Each child class should:
  177. 0) call Container.__init__(self, name=name, description=description,
  178. file_origin=file_origin, **annotations)
  179. with the universal recommended arguments, plus optional annotations
  180. 1) process its required arguments in its __new__ or __init__ method
  181. 2) process its non-universal recommended arguments (in its __new__ or
  182. __init__ method
  183. """
  184. # Child objects that are a container and have a single parent
  185. _container_child_objects = ()
  186. # Child objects that have data and have a single parent
  187. _data_child_objects = ()
  188. # Child objects that can have multiple parents
  189. _multi_child_objects = ()
  190. # Properties returning children of children [of children...]
  191. _child_properties = ()
  192. # Containers that are listed when pretty-printing
  193. _repr_pretty_containers = ()
  194. def __init__(self, name=None, description=None, file_origin=None,
  195. **annotations):
  196. """
  197. Initalize a new :class:`Container` instance.
  198. """
  199. super().__init__(name=name, description=description,
  200. file_origin=file_origin, **annotations)
  201. # initialize containers
  202. for container in self._child_containers:
  203. setattr(self, container, [])
  204. @property
  205. def _single_child_objects(self):
  206. """
  207. Child objects that have a single parent.
  208. """
  209. return self._container_child_objects + self._data_child_objects
  210. @property
  211. def _container_child_containers(self):
  212. """
  213. Containers for child objects that are a container and
  214. have a single parent.
  215. """
  216. return tuple([_container_name(child) for child in
  217. self._container_child_objects])
  218. @property
  219. def _data_child_containers(self):
  220. """
  221. Containers for child objects that have data and have a single parent.
  222. """
  223. return tuple([_container_name(child) for child in
  224. self._data_child_objects])
  225. @property
  226. def _single_child_containers(self):
  227. """
  228. Containers for child objects with a single parent.
  229. """
  230. return tuple([_container_name(child) for child in
  231. self._single_child_objects])
  232. @property
  233. def _multi_child_containers(self):
  234. """
  235. Containers for child objects that can have multiple parents.
  236. """
  237. return tuple([_container_name(child) for child in
  238. self._multi_child_objects])
  239. @property
  240. def _child_objects(self):
  241. """
  242. All types for child objects.
  243. """
  244. return self._single_child_objects + self._multi_child_objects
  245. @property
  246. def _child_containers(self):
  247. """
  248. All containers for child objects.
  249. """
  250. return self._single_child_containers + self._multi_child_containers
  251. @property
  252. def _single_children(self):
  253. """
  254. All child objects that can only have single parents.
  255. """
  256. childs = [list(getattr(self, attr)) for attr in
  257. self._single_child_containers]
  258. return tuple(sum(childs, []))
  259. @property
  260. def _multi_children(self):
  261. """
  262. All child objects that can have multiple parents.
  263. """
  264. childs = [list(getattr(self, attr)) for attr in
  265. self._multi_child_containers]
  266. return tuple(sum(childs, []))
  267. @property
  268. def data_children(self):
  269. """
  270. All data child objects stored in the current object.
  271. Not recursive.
  272. """
  273. childs = [list(getattr(self, attr)) for attr in
  274. self._data_child_containers]
  275. return tuple(sum(childs, []))
  276. @property
  277. def container_children(self):
  278. """
  279. All container child objects stored in the current object.
  280. Not recursive.
  281. """
  282. childs = [list(getattr(self, attr)) for attr in
  283. self._container_child_containers +
  284. self._multi_child_containers]
  285. return tuple(sum(childs, []))
  286. @property
  287. def children(self):
  288. """
  289. All child objects stored in the current object.
  290. Not recursive.
  291. """
  292. return self.data_children + self.container_children
  293. @property
  294. def data_children_recur(self):
  295. """
  296. All data child objects stored in the current object,
  297. obtained recursively.
  298. """
  299. childs = [list(child.data_children_recur) for child in
  300. self.container_children]
  301. return self.data_children + tuple(sum(childs, []))
  302. @property
  303. def container_children_recur(self):
  304. """
  305. All container child objects stored in the current object,
  306. obtained recursively.
  307. """
  308. childs = [list(child.container_children_recur) for child in
  309. self.container_children]
  310. return self.container_children + tuple(sum(childs, []))
  311. @property
  312. def children_recur(self):
  313. """
  314. All child objects stored in the current object,
  315. obtained recursively.
  316. """
  317. return self.data_children_recur + self.container_children_recur
  318. @property
  319. def size(self):
  320. """
  321. Get dictionary containing the names of child containers in the current
  322. object as keys and the number of children of that type as values.
  323. """
  324. return {name: len(getattr(self, name))
  325. for name in self._child_containers}
  326. def filter(self, targdict=None, data=True, container=False, recursive=True,
  327. objects=None, **kwargs):
  328. """
  329. Return a list of child objects matching *any* of the search terms
  330. in either their attributes or annotations. Search terms can be
  331. provided as keyword arguments or a dictionary, either as a positional
  332. argument after data or to the argument targdict. targdict can also
  333. be a list of dictionaries, in which case the filters are applied
  334. sequentially. If targdict and kwargs are both supplied, the
  335. targdict filters are applied first, followed by the kwarg filters.
  336. A targdict of None or {} corresponds to no filters applied, therefore
  337. returning all child objects. Default targdict is None.
  338. If data is True (default), include data objects.
  339. If container is True (default False), include container objects.
  340. If recursive is True (default), descend into child containers for
  341. objects.
  342. objects (optional) should be the name of a Neo object type,
  343. a neo object class, or a list of one or both of these. If specified,
  344. only these objects will be returned. If not specified any type of
  345. object is returned. Default is None.
  346. Note that if recursive is True, containers not in objects will still
  347. be descended into. This overrides data and container.
  348. Examples::
  349. >>> obj.filter(name="Vm")
  350. >>> obj.filter(objects=neo.SpikeTrain)
  351. >>> obj.filter(targdict={'myannotation':3})
  352. """
  353. if isinstance(targdict, str):
  354. raise TypeError("filtering is based on key-value pairs."
  355. " Only a single string was provided.")
  356. # if objects are specified, get the classes
  357. if objects:
  358. data = True
  359. container = True
  360. children = []
  361. # get the objects we want
  362. if data:
  363. if recursive:
  364. children.extend(self.data_children_recur)
  365. else:
  366. children.extend(self.data_children)
  367. if container:
  368. if recursive:
  369. children.extend(self.container_children_recur)
  370. else:
  371. children.extend(self.container_children)
  372. return filterdata(children, objects=objects,
  373. targdict=targdict, **kwargs)
  374. def list_children_by_class(self, cls):
  375. """
  376. List all children of a particular class recursively.
  377. You can either provide a class object, a class name,
  378. or the name of the container storing the class.
  379. """
  380. if not hasattr(cls, 'lower'):
  381. cls = cls.__name__
  382. container_name = _container_name(cls)
  383. objs = list(getattr(self, container_name, []))
  384. for child in self.container_children_recur:
  385. objs.extend(getattr(child, container_name, []))
  386. return objs
  387. def create_many_to_one_relationship(self, force=False, recursive=True):
  388. """
  389. For each child of the current object that can only have a single
  390. parent, set its parent to be the current object.
  391. Usage:
  392. >>> a_block.create_many_to_one_relationship()
  393. >>> a_block.create_many_to_one_relationship(force=True)
  394. If the current object is a :class:`Block`, you want to run
  395. populate_RecordingChannel first, because this will create new objects
  396. that this method will link up.
  397. If force is True overwrite any existing relationships
  398. If recursive is True desecend into child objects and create
  399. relationships there
  400. """
  401. parent_name = _reference_name(self.__class__.__name__)
  402. for child in self._single_children:
  403. if (hasattr(child, parent_name) and
  404. getattr(child, parent_name) is None or force):
  405. setattr(child, parent_name, self)
  406. if recursive:
  407. for child in self.container_children:
  408. child.create_many_to_one_relationship(force=force,
  409. recursive=True)
  410. def create_many_to_many_relationship(self, append=True, recursive=True):
  411. """
  412. For children of the current object that can have more than one parent
  413. of this type, put the current object in the parent list.
  414. If append is True add it to the list, otherwise overwrite the list.
  415. If recursive is True desecend into child objects and create
  416. relationships there
  417. """
  418. parent_name = _container_name(self.__class__.__name__)
  419. for child in self._multi_children:
  420. if not hasattr(child, parent_name):
  421. continue
  422. if append:
  423. target = getattr(child, parent_name)
  424. if self not in target:
  425. target.append(self)
  426. continue
  427. setattr(child, parent_name, [self])
  428. if recursive:
  429. for child in self.container_children:
  430. child.create_many_to_many_relationship(append=append,
  431. recursive=True)
  432. def create_relationship(self, force=False, append=True, recursive=True):
  433. """
  434. For each child of the current object that can only have a single
  435. parent, set its parent to be the current object.
  436. For children of the current object that can have more than one parent
  437. of this type, put the current object in the parent list.
  438. If the current object is a :class:`Block`, you want to run
  439. populate_RecordingChannel first, because this will create new objects
  440. that this method will link up.
  441. If force is True overwrite any existing relationships
  442. If append is True add it to the list, otherwise overwrite the list.
  443. If recursive is True desecend into child objects and create
  444. relationships there
  445. """
  446. self.create_many_to_one_relationship(force=force, recursive=False)
  447. self.create_many_to_many_relationship(append=append, recursive=False)
  448. if recursive:
  449. for child in self.container_children:
  450. child.create_relationship(force=force, append=append,
  451. recursive=True)
  452. def __deepcopy__(self, memo):
  453. """
  454. Creates a deep copy of the container.
  455. All contained objects will also be deep copied and relationships
  456. between all objects will be identical to the original relationships.
  457. Attributes and annotations of the container are deep copied as well.
  458. :param memo: (dict) Objects that have been deep copied already
  459. :return: (Container) Deep copy of input Container
  460. """
  461. cls = self.__class__
  462. necessary_attrs = {}
  463. for k in self._necessary_attrs:
  464. necessary_attrs[k[0]] = getattr(self, k[0], None)
  465. new_container = cls(**necessary_attrs)
  466. new_container.__dict__.update(self.__dict__)
  467. memo[id(self)] = new_container
  468. for k, v in self.__dict__.items():
  469. try:
  470. setattr(new_container, k, deepcopy(v, memo))
  471. except TypeError:
  472. setattr(new_container, k, v)
  473. new_container.create_relationship()
  474. return new_container
  475. def merge(self, other):
  476. """
  477. Merge the contents of another object into this one.
  478. Container children of the current object with the same name will be
  479. merged. All other objects will be appended to the list of objects
  480. in this one. Duplicate copies of the same object will be skipped.
  481. Annotations are merged such that only items not present in the current
  482. annotations are added.
  483. Note that the other object will be linked inconsistently to other Neo objects
  484. after the merge operation and should not be used further.
  485. """
  486. # merge containers with the same name
  487. for container in (self._container_child_containers +
  488. self._multi_child_containers):
  489. lookup = {obj.name: obj for obj in getattr(self, container)}
  490. ids = [id(obj) for obj in getattr(self, container)]
  491. for obj in getattr(other, container):
  492. if id(obj) in ids:
  493. continue
  494. if obj.name in lookup:
  495. lookup[obj.name].merge(obj)
  496. else:
  497. lookup[obj.name] = obj
  498. ids.append(id(obj))
  499. getattr(self, container).append(obj)
  500. # for data objects, ignore the name and just add them
  501. for container in self._data_child_containers:
  502. objs = getattr(self, container)
  503. lookup = {obj.name: i for i, obj in enumerate(objs)}
  504. ids = [id(obj) for obj in objs]
  505. for obj in getattr(other, container):
  506. if id(obj) in ids:
  507. pass
  508. elif hasattr(obj, 'merge') and obj.name is not None and obj.name in lookup:
  509. ind = lookup[obj.name]
  510. try:
  511. newobj = getattr(self, container)[ind].merge(obj)
  512. getattr(self, container)[ind] = newobj
  513. except NotImplementedError:
  514. getattr(self, container).append(obj)
  515. ids.append(id(obj))
  516. else:
  517. lookup[obj.name] = obj
  518. ids.append(id(obj))
  519. getattr(self, container).append(obj)
  520. obj.set_parent(self)
  521. # use the BaseNeo merge as well
  522. super().merge(other)
  523. def _repr_pretty_(self, pp, cycle):
  524. """
  525. Handle pretty-printing.
  526. """
  527. pp.text(self.__class__.__name__)
  528. pp.text(" with ")
  529. vals = []
  530. for container in self._child_containers:
  531. objs = getattr(self, container)
  532. if objs:
  533. vals.append('{} {}'.format(len(objs), container))
  534. pp.text(', '.join(vals))
  535. if self._has_repr_pretty_attrs_():
  536. pp.breakable()
  537. self._repr_pretty_attrs_(pp, cycle)
  538. for container in self._repr_pretty_containers:
  539. pp.breakable()
  540. objs = getattr(self, container)
  541. pp.text("# {} (N={})".format(container, len(objs)))
  542. for (i, obj) in enumerate(objs):
  543. pp.breakable()
  544. pp.text("%s: " % i)
  545. with pp.indent(3):
  546. pp.pretty(obj)