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

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