resultset.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # -*- coding: utf-8 -*-
  2. """Module to prepare the resultset."""
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. from __future__ import unicode_literals
  7. import warnings
  8. from influxdb.exceptions import InfluxDBClientError
  9. _sentinel = object()
  10. class ResultSet(object):
  11. """A wrapper around a single InfluxDB query result."""
  12. def __init__(self, series, raise_errors=True):
  13. """Initialize the ResultSet."""
  14. self._raw = series
  15. self._error = self._raw.get('error', None)
  16. if self.error is not None and raise_errors is True:
  17. raise InfluxDBClientError(self.error)
  18. @property
  19. def raw(self):
  20. """Raw JSON from InfluxDB."""
  21. return self._raw
  22. @raw.setter
  23. def raw(self, value):
  24. self._raw = value
  25. @property
  26. def error(self):
  27. """Error returned by InfluxDB."""
  28. return self._error
  29. def __getitem__(self, key):
  30. """Retrieve the series name or specific set based on key.
  31. :param key: Either a series name, or a tags_dict, or
  32. a 2-tuple(series_name, tags_dict).
  33. If the series name is None (or not given) then any serie
  34. matching the eventual given tags will be given its points
  35. one after the other.
  36. To get the points of every series in this resultset then
  37. you have to provide None as key.
  38. :return: A generator yielding `Point`s matching the given key.
  39. NB:
  40. The order in which the points are yielded is actually undefined but
  41. it might change..
  42. """
  43. warnings.warn(
  44. ("ResultSet's ``__getitem__`` method will be deprecated. Use"
  45. "``get_points`` instead."),
  46. DeprecationWarning
  47. )
  48. if isinstance(key, tuple):
  49. if len(key) != 2:
  50. raise TypeError('only 2-tuples allowed')
  51. name = key[0]
  52. tags = key[1]
  53. if not isinstance(tags, dict) and tags is not None:
  54. raise TypeError('tags should be a dict')
  55. elif isinstance(key, dict):
  56. name = None
  57. tags = key
  58. else:
  59. name = key
  60. tags = None
  61. return self.get_points(name, tags)
  62. def get_points(self, measurement=None, tags=None):
  63. """Return a generator for all the points that match the given filters.
  64. :param measurement: The measurement name
  65. :type measurement: str
  66. :param tags: Tags to look for
  67. :type tags: dict
  68. :return: Points generator
  69. """
  70. # Raise error if measurement is not str or bytes
  71. if not isinstance(measurement,
  72. (bytes, type(b''.decode()), type(None))):
  73. raise TypeError('measurement must be an str or None')
  74. for series in self._get_series():
  75. series_name = series.get('measurement',
  76. series.get('name', 'results'))
  77. if series_name is None:
  78. # this is a "system" query or a query which
  79. # doesn't return a name attribute.
  80. # like 'show retention policies' ..
  81. if tags is None:
  82. for item in self._get_points_for_series(series):
  83. yield item
  84. elif measurement in (None, series_name):
  85. # by default if no tags was provided then
  86. # we will matches every returned series
  87. series_tags = series.get('tags', {})
  88. for item in self._get_points_for_series(series):
  89. if tags is None or \
  90. self._tag_matches(item, tags) or \
  91. self._tag_matches(series_tags, tags):
  92. yield item
  93. def __repr__(self):
  94. """Representation of ResultSet object."""
  95. items = []
  96. for item in self.items():
  97. items.append("'%s': %s" % (item[0], list(item[1])))
  98. return "ResultSet({%s})" % ", ".join(items)
  99. def __iter__(self):
  100. """Yield one dict instance per series result."""
  101. for key in self.keys():
  102. yield list(self.__getitem__(key))
  103. @staticmethod
  104. def _tag_matches(tags, filter):
  105. """Check if all key/values in filter match in tags."""
  106. for tag_name, tag_value in filter.items():
  107. # using _sentinel as I'm not sure that "None"
  108. # could be used, because it could be a valid
  109. # series_tags value : when a series has no such tag
  110. # then I think it's set to /null/None/.. TBC..
  111. series_tag_value = tags.get(tag_name, _sentinel)
  112. if series_tag_value != tag_value:
  113. return False
  114. return True
  115. def _get_series(self):
  116. """Return all series."""
  117. return self.raw.get('series', [])
  118. def __len__(self):
  119. """Return the len of the keys in the ResultSet."""
  120. return len(self.keys())
  121. def keys(self):
  122. """Return the list of keys in the ResultSet.
  123. :return: List of keys. Keys are tuples (series_name, tags)
  124. """
  125. keys = []
  126. for series in self._get_series():
  127. keys.append(
  128. (series.get('measurement',
  129. series.get('name', 'results')),
  130. series.get('tags', None))
  131. )
  132. return keys
  133. def items(self):
  134. """Return the set of items from the ResultSet.
  135. :return: List of tuples, (key, generator)
  136. """
  137. items = []
  138. for series in self._get_series():
  139. series_key = (series.get('measurement',
  140. series.get('name', 'results')),
  141. series.get('tags', None))
  142. items.append(
  143. (series_key, self._get_points_for_series(series))
  144. )
  145. return items
  146. def _get_points_for_series(self, series):
  147. """Return generator of dict from columns and values of a series.
  148. :param series: One series
  149. :return: Generator of dicts
  150. """
  151. for point in series.get('values', []):
  152. yield self.point_from_cols_vals(
  153. series['columns'],
  154. point
  155. )
  156. @staticmethod
  157. def point_from_cols_vals(cols, vals):
  158. """Create a dict from columns and values lists.
  159. :param cols: List of columns
  160. :param vals: List of values
  161. :return: Dict where keys are columns.
  162. """
  163. point = {}
  164. for col_index, col_name in enumerate(cols):
  165. point[col_name] = vals[col_index]
  166. return point