Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

container.py 24 KB

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