container.py 24 KB

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