baseneo.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. # -*- coding: utf-8 -*-
  2. """
  3. This module defines :class:`BaseNeo`, the abstract base class
  4. used by all :module:`neo.core` classes.
  5. """
  6. # needed for python 3 compatibility
  7. from __future__ import absolute_import, division, print_function
  8. from datetime import datetime, date, time, timedelta
  9. from decimal import Decimal
  10. import logging
  11. from numbers import Number
  12. import numpy as np
  13. ALLOWED_ANNOTATION_TYPES = (int, float, complex,
  14. str, bytes,
  15. type(None),
  16. datetime, date, time, timedelta,
  17. Number, Decimal,
  18. np.number, np.bool_)
  19. # handle both Python 2 and Python 3
  20. try:
  21. ALLOWED_ANNOTATION_TYPES += (long, unicode)
  22. except NameError:
  23. pass
  24. try:
  25. basestring
  26. except NameError:
  27. basestring = str
  28. logger = logging.getLogger("Neo")
  29. class MergeError(Exception):
  30. pass
  31. def _check_annotations(value):
  32. """
  33. Recursively check that value is either of a "simple" type (number, string,
  34. date/time) or is a (possibly nested) dict, list or numpy array containing
  35. only simple types.
  36. """
  37. if isinstance(value, np.ndarray):
  38. if not issubclass(value.dtype.type, ALLOWED_ANNOTATION_TYPES):
  39. raise ValueError("Invalid annotation. NumPy arrays with dtype %s"
  40. "are not allowed" % value.dtype.type)
  41. elif isinstance(value, dict):
  42. for element in value.values():
  43. _check_annotations(element)
  44. elif isinstance(value, (list, tuple)):
  45. for element in value:
  46. _check_annotations(element)
  47. elif not isinstance(value, ALLOWED_ANNOTATION_TYPES):
  48. raise ValueError("Invalid annotation. Annotations of type %s are not"
  49. "allowed" % type(value))
  50. def merge_annotation(a, b):
  51. """
  52. First attempt at a policy for merging annotations (intended for use with
  53. parallel computations using MPI). This policy needs to be discussed
  54. further, or we could allow the user to specify a policy.
  55. Current policy:
  56. For arrays or lists: concatenate
  57. For dicts: merge recursively
  58. For strings: concatenate with ';'
  59. Otherwise: fail if the annotations are not equal
  60. """
  61. assert type(a) == type(b), 'type(%s) %s != type(%s) %s' % (a, type(a),
  62. b, type(b))
  63. if isinstance(a, dict):
  64. return merge_annotations(a, b)
  65. elif isinstance(a, np.ndarray): # concatenate b to a
  66. return np.append(a, b)
  67. elif isinstance(a, list): # concatenate b to a
  68. return a + b
  69. elif isinstance(a, basestring):
  70. if a == b:
  71. return a
  72. else:
  73. return a + ";" + b
  74. else:
  75. assert a == b, '%s != %s' % (a, b)
  76. return a
  77. def merge_annotations(A, B):
  78. """
  79. Merge two sets of annotations.
  80. Merging follows these rules:
  81. All keys that are in A or B, but not both, are kept.
  82. For keys that are present in both:
  83. For arrays or lists: concatenate
  84. For dicts: merge recursively
  85. For strings: concatenate with ';'
  86. Otherwise: warn if the annotations are not equal
  87. """
  88. merged = {}
  89. for name in A:
  90. if name in B:
  91. try:
  92. merged[name] = merge_annotation(A[name], B[name])
  93. except BaseException as exc:
  94. # exc.args += ('key %s' % name,)
  95. # raise
  96. merged[name] = "MERGE CONFLICT" # temporary hack
  97. else:
  98. merged[name] = A[name]
  99. for name in B:
  100. if name not in merged:
  101. merged[name] = B[name]
  102. logger.debug("Merging annotations: A=%s B=%s merged=%s", A, B, merged)
  103. return merged
  104. def _reference_name(class_name):
  105. """
  106. Given the name of a class, return an attribute name to be used for
  107. references to instances of that class.
  108. For example, a Segment object has a parent Block object, referenced by
  109. `segment.block`. The attribute name `block` is obtained by calling
  110. `_container_name("Block")`.
  111. """
  112. name_map = {
  113. "ChannelIndex": "channel_index"
  114. }
  115. return name_map.get(class_name, class_name.lower())
  116. def _container_name(class_name):
  117. """
  118. Given the name of a class, return an attribute name to be used for
  119. lists (or other containers) containing instances of that class.
  120. For example, a Block object contains a list of Segment objects,
  121. referenced by `block.segments`. The attribute name `segments` is
  122. obtained by calling `_container_name_plural("Segment")`.
  123. """
  124. name_map = {
  125. "ChannelIndex": "channel_indexes"
  126. }
  127. return name_map.get(class_name, _reference_name(class_name) + 's')
  128. class BaseNeo(object):
  129. """
  130. This is the base class from which all Neo objects inherit.
  131. This class implements support for universally recommended arguments,
  132. and also sets up the :attr:`annotations` dict for additional arguments.
  133. Each class can define one or more of the following class attributes:
  134. :_single_parent_objects: Neo objects that can be parents of this
  135. object. This attribute is used in cases where
  136. only one parent of this class is allowed.
  137. An instance attribute named
  138. class.__name__.lower() will be automatically
  139. defined to hold this parent and will be
  140. initialized to None.
  141. :_multi_parent_objects: Neo objects that can be parents of this
  142. object. This attribute is used in cases where
  143. multiple parents of this class is allowed.
  144. An instance attribute named
  145. class.__name__.lower()+'s' will be
  146. automatically defined to hold this parent and
  147. will be initialized to an empty list.
  148. :_necessary_attrs: A list of tuples containing the attributes that the
  149. class must have. The tuple can have 2-4 elements.
  150. The first element is the attribute name.
  151. The second element is the attribute type.
  152. The third element is the number of dimensions
  153. (only for numpy arrays and quantities).
  154. The fourth element is the dtype of array
  155. (only for numpy arrays and quantities).
  156. This does NOT include the attributes holding the
  157. parents or children of the object.
  158. :_recommended_attrs: A list of tuples containing the attributes that
  159. the class may optionally have. It uses the same
  160. structure as :_necessary_attrs:
  161. :_repr_pretty_attrs_keys_: The names of attributes printed when
  162. pretty-printing using iPython.
  163. The following helper properties are available:
  164. :_parent_objects: All parent objects.
  165. :_single_parent_objects: + :_multi_parent_objects:
  166. :_single_parent_containers: The names of the container attributes used
  167. to store :_single_parent_objects:
  168. :_multi_parent_containers: The names of the container attributes used
  169. to store :_multi_parent_objects:
  170. :_parent_containers: All parent container attributes.
  171. :_single_parent_containers: +
  172. :_multi_parent_containers:
  173. :parents: All objects that are parents of the current object.
  174. :_all_attrs: All required and optional attributes.
  175. :_necessary_attrs: + :_recommended_attrs:
  176. The following "universal" methods are available:
  177. :__init__: Grabs the universally recommended arguments :attr:`name`,
  178. :attr:`file_origin`, and :attr:`description` and stores them as
  179. attributes.
  180. Also takes every additional argument (that is, every argument
  181. that is not handled by :class:`BaseNeo` or the child class), and
  182. puts in the dict :attr:`annotations`.
  183. :annotate(**args): Updates :attr:`annotations` with keyword/value
  184. pairs.
  185. :merge(**args): Merge the contents of another object into this one.
  186. The merge method implemented here only merges
  187. annotations (see :merge_annotations:).
  188. Subclasses should implementt their own merge rules.
  189. :merge_annotations(**args): Merge the :attr:`annotations` of another
  190. object into this one.
  191. Each child class should:
  192. 0) describe its parents (if any) and attributes in the relevant
  193. class attributes. :_recommended_attrs: should append
  194. BaseNeo._recommended_attrs to the end.
  195. 1) call BaseNeo.__init__(self, name=name, description=description,
  196. file_origin=file_origin, **annotations)
  197. with the universal recommended arguments, plus optional annotations
  198. 2) process its required arguments in its __new__ or __init__ method
  199. 3) process its non-universal recommended arguments (in its __new__ or
  200. __init__ method
  201. Non-keyword arguments should only be used for required arguments.
  202. The required and recommended arguments for each child class (Neo object)
  203. are specified in the _necessary_attrs and _recommended_attrs attributes and
  204. documentation for the child object.
  205. """
  206. # these attributes control relationships, they need to be
  207. # specified in each child class
  208. # Parent objects whose children can have a single parent
  209. _single_parent_objects = ()
  210. # Parent objects whose children can have multiple parents
  211. _multi_parent_objects = ()
  212. # Attributes that an instance is requires to have defined
  213. _necessary_attrs = ()
  214. # Attributes that an instance may or may have defined
  215. _recommended_attrs = (('name', str),
  216. ('description', str),
  217. ('file_origin', str))
  218. # Attributes that are used for pretty-printing
  219. _repr_pretty_attrs_keys_ = ("name", "description", "annotations")
  220. def __init__(self, name=None, description=None, file_origin=None,
  221. **annotations):
  222. """
  223. This is the base constructor for all Neo objects.
  224. Stores universally recommended attributes and creates
  225. :attr:`annotations` from additional arguments not processed by
  226. :class:`BaseNeo` or the child class.
  227. """
  228. # create `annotations` for additional arguments
  229. _check_annotations(annotations)
  230. self.annotations = annotations
  231. # these attributes are recommended for all objects.
  232. self.name = name
  233. self.description = description
  234. self.file_origin = file_origin
  235. # initialize parent containers
  236. for parent in self._single_parent_containers:
  237. setattr(self, parent, None)
  238. for parent in self._multi_parent_containers:
  239. setattr(self, parent, [])
  240. def annotate(self, **annotations):
  241. """
  242. Add annotations (non-standardized metadata) to a Neo object.
  243. Example:
  244. >>> obj.annotate(key1=value0, key2=value1)
  245. >>> obj.key2
  246. value2
  247. """
  248. _check_annotations(annotations)
  249. self.annotations.update(annotations)
  250. def _has_repr_pretty_attrs_(self):
  251. return any(getattr(self, k) for k in self._repr_pretty_attrs_keys_)
  252. def _repr_pretty_attrs_(self, pp, cycle):
  253. first = True
  254. for key in self._repr_pretty_attrs_keys_:
  255. value = getattr(self, key)
  256. if value:
  257. if first:
  258. first = False
  259. else:
  260. pp.breakable()
  261. with pp.group(indent=1):
  262. pp.text("{0}: ".format(key))
  263. pp.pretty(value)
  264. def _repr_pretty_(self, pp, cycle):
  265. """
  266. Handle pretty-printing the :class:`BaseNeo`.
  267. """
  268. pp.text(self.__class__.__name__)
  269. if self._has_repr_pretty_attrs_():
  270. pp.breakable()
  271. self._repr_pretty_attrs_(pp, cycle)
  272. @property
  273. def _single_parent_containers(self):
  274. """
  275. Containers for parent objects whose children can have a single parent.
  276. """
  277. return tuple([_reference_name(parent) for parent in
  278. self._single_parent_objects])
  279. @property
  280. def _multi_parent_containers(self):
  281. """
  282. Containers for parent objects whose children can have multiple parents.
  283. """
  284. return tuple([_container_name(parent) for parent in
  285. self._multi_parent_objects])
  286. @property
  287. def _parent_objects(self):
  288. """
  289. All types for parent objects.
  290. """
  291. return self._single_parent_objects + self._multi_parent_objects
  292. @property
  293. def _parent_containers(self):
  294. """
  295. All containers for parent objects.
  296. """
  297. return self._single_parent_containers + self._multi_parent_containers
  298. @property
  299. def parents(self):
  300. """
  301. All parent objects storing the current object.
  302. """
  303. single = [getattr(self, attr) for attr in
  304. self._single_parent_containers]
  305. multi = [list(getattr(self, attr)) for attr in
  306. self._multi_parent_containers]
  307. return tuple(single + sum(multi, []))
  308. @property
  309. def _all_attrs(self):
  310. """
  311. Returns a combination of all required and recommended
  312. attributes.
  313. """
  314. return self._necessary_attrs + self._recommended_attrs
  315. def merge_annotations(self, other):
  316. """
  317. Merge annotations from the other object into this one.
  318. Merging follows these rules:
  319. All keys that are in the either object, but not both, are kept.
  320. For keys that are present in both objects:
  321. For arrays or lists: concatenate the two arrays
  322. For dicts: merge recursively
  323. For strings: concatenate with ';'
  324. Otherwise: fail if the annotations are not equal
  325. """
  326. merged_annotations = merge_annotations(self.annotations,
  327. other.annotations)
  328. self.annotations.update(merged_annotations)
  329. def merge(self, other):
  330. """
  331. Merge the contents of another object into this one.
  332. See :meth:`merge_annotations` for details of the merge operation.
  333. """
  334. self.merge_annotations(other)