Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7576b33

Browse files
committed
Merge pull request influxdata#154 from influxdb/resultset
2 parents ae67cce + b9430f9 commit 7576b33

File tree

5 files changed

+398
-166
lines changed

5 files changed

+398
-166
lines changed

influxdb/client.py

Lines changed: 21 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import requests
99
import requests.exceptions
1010

11+
from influxdb.resultset import ResultSet
1112

1213
try:
1314
xrange
@@ -109,7 +110,7 @@ def __init__(self,
109110
# if one doesn't care in that, then it can simply change its client
110111
# instance 'keep_json_response_order' attribute value (to a falsy one).
111112
# This will then eventually help for performance considerations.
112-
_keep_json_response_order = True
113+
_keep_json_response_order = False
113114
# NB: For "group by" query type :
114115
# This setting is actually necessary in order to have a consistent and
115116
# reproducible rsp format if you "group by" on more than 1 tag.
@@ -122,33 +123,6 @@ def keep_json_response_order(self):
122123
def keep_json_response_order(self, new_value):
123124
self._keep_json_response_order = new_value
124125

125-
@staticmethod
126-
def format_query_response(response):
127-
"""Returns a list of items from a query response"""
128-
series = {}
129-
if 'results' in response:
130-
for result in response['results']:
131-
if 'series' in result:
132-
for row in result['series']:
133-
items = []
134-
if 'name' in row:
135-
name = row['name']
136-
tags = row.get('tags', None)
137-
if tags:
138-
name = (row['name'], tuple(tags.items()))
139-
assert name not in series
140-
series[name] = items
141-
else:
142-
series = items # Special case for system queries.
143-
if 'columns' in row and 'values' in row:
144-
columns = row['columns']
145-
for value in row['values']:
146-
item = {}
147-
for cur_col, field in enumerate(value):
148-
item[columns[cur_col]] = field
149-
items.append(item)
150-
return series
151-
152126
def switch_database(self, database):
153127
"""
154128
switch_database()
@@ -234,15 +208,16 @@ def query(self,
234208
query,
235209
params={},
236210
expected_response_code=200,
237-
database=None,
238-
raw=False):
211+
database=None):
239212
"""
240213
Query data
241214
242215
:param params: Additional parameters to be passed to requests.
243216
:param database: Database to query, default to None.
244217
:param expected_response_code: Expected response code. Defaults to 200.
245-
:param raw: Wether or not to return the raw influxdb response.
218+
219+
:rtype : ResultSet
220+
246221
"""
247222

248223
params['q'] = query
@@ -261,8 +236,7 @@ def query(self,
261236
json_kw.update(object_pairs_hook=OrderedDict)
262237
data = response.json(**json_kw)
263238

264-
return (data if raw
265-
else self.format_query_response(data))
239+
return ResultSet(data)
266240

267241
def write_points(self,
268242
points,
@@ -327,8 +301,7 @@ def get_list_database(self):
327301
"""
328302
Get the list of databases
329303
"""
330-
rsp = self.query("SHOW DATABASES")
331-
return [db['name'] for db in rsp['databases']]
304+
return list(self.query("SHOW DATABASES")['databases'])
332305

333306
def create_database(self, dbname):
334307
"""
@@ -368,21 +341,31 @@ def get_list_retention_policies(self, database=None):
368341
"""
369342
Get the list of retention policies
370343
"""
371-
return self.query(
344+
rsp = self.query(
372345
"SHOW RETENTION POLICIES %s" % (database or self._database)
373346
)
347+
return list(rsp['results'])
374348

375349
def get_list_series(self, database=None):
376350
"""
377351
Get the list of series
378352
"""
379-
return self.query("SHOW SERIES", database=database)
353+
rsp = self.query("SHOW SERIES", database=database)
354+
series = []
355+
for serie in rsp.items():
356+
series.append(
357+
{
358+
"name": serie[0][0],
359+
"tags": list(serie[1])
360+
}
361+
)
362+
return series
380363

381364
def get_list_users(self):
382365
"""
383366
Get the list of users
384367
"""
385-
return self.query("SHOW USERS")
368+
return list(self.query("SHOW USERS"))
386369

387370
def delete_series(self, name, database=None):
388371
database = database or self._database

influxdb/resultset.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# -*- coding: utf-8 -*-
2+
3+
_sentinel = object()
4+
5+
6+
class ResultSet(object):
7+
"""A wrapper around series results """
8+
9+
def __init__(self, series):
10+
self.raw = series
11+
12+
def __getitem__(self, key):
13+
"""
14+
:param key: Either a serie name or a 2-tuple(serie_name, tags_dict)
15+
If the given serie name is None then any serie (matching
16+
the eventual given tags) will be given its points one
17+
after the other.
18+
:return: A generator yielding `Point`s matching the given key.
19+
NB:
20+
The order in which the points are yielded is actually undefined but
21+
it might change..
22+
"""
23+
if isinstance(key, tuple):
24+
if 2 != len(key):
25+
raise TypeError('only 2-tuples allowed')
26+
name = key[0]
27+
tags = key[1]
28+
if not isinstance(tags, dict) and tags is not None:
29+
raise TypeError('tags should be a dict')
30+
else:
31+
name = key
32+
tags = None
33+
# TODO(aviau): Fix for python 3.2
34+
# if not isinstance(name, (str, bytes, type(None))) \
35+
# and not isinstance(name, type("".decode("utf-8"))):
36+
# raise TypeError('serie_name must be an str or None')
37+
38+
for serie in self._get_series():
39+
serie_name = serie.get('name', 'results')
40+
if serie_name is None:
41+
# this is a "system" query or a query which
42+
# doesn't return a name attribute.
43+
# like 'show retention policies' ..
44+
if key is None:
45+
for point in serie['values']:
46+
yield self.point_from_cols_vals(
47+
serie['columns'],
48+
point
49+
)
50+
51+
elif name in (None, serie_name):
52+
# by default if no tags was provided then
53+
# we will matches every returned serie
54+
serie_tags = serie.get('tags', {})
55+
if tags is None or self._tag_matches(serie_tags, tags):
56+
for point in serie.get('values', []):
57+
yield self.point_from_cols_vals(
58+
serie['columns'],
59+
point
60+
)
61+
62+
def __repr__(self):
63+
return str(self.raw)
64+
65+
def __iter__(self):
66+
""" Iterating a ResultSet will yield one dict instance per serie result.
67+
"""
68+
for key in self.keys():
69+
yield list(self.__getitem__(key))
70+
71+
def _tag_matches(self, tags, filter):
72+
"""Checks if all key/values in filter match in tags"""
73+
for tag_name, tag_value in filter.items():
74+
# using _sentinel as I'm not sure that "None"
75+
# could be used, because it could be a valid
76+
# serie_tags value : when a serie has no such tag
77+
# then I think it's set to /null/None/.. TBC..
78+
serie_tag_value = tags.get(tag_name, _sentinel)
79+
if serie_tag_value != tag_value:
80+
return False
81+
return True
82+
83+
def _get_series(self):
84+
"""Returns all series"""
85+
series = []
86+
try:
87+
for result in self.raw['results']:
88+
series.extend(result['series'])
89+
except KeyError:
90+
pass
91+
return series
92+
93+
def __len__(self):
94+
return len(self.keys())
95+
96+
def keys(self):
97+
keys = []
98+
for serie in self._get_series():
99+
keys.append(
100+
(serie.get('name', 'results'), serie.get('tags', None))
101+
)
102+
return keys
103+
104+
def items(self):
105+
items = []
106+
for serie in self._get_series():
107+
serie_key = (serie.get('name', 'results'), serie.get('tags', None))
108+
items.append(
109+
(serie_key, self[serie_key])
110+
)
111+
return items
112+
113+
@staticmethod
114+
def point_from_cols_vals(cols, vals):
115+
point = {}
116+
for col_index, col_name in enumerate(cols):
117+
point[col_name] = vals[col_index]
118+
return point

tests/influxdb/client_test.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,11 @@ def test_query(self):
265265
"http://localhost:8086/query",
266266
text=example_response
267267
)
268-
self.assertDictEqual(
269-
self.cli.query('select * from foo'),
270-
{'cpu_load_short':
271-
[{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}],
272-
'sdfsdfsdf':
273-
[{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}]}
268+
rs = self.cli.query('select * from foo')
269+
270+
self.assertListEqual(
271+
list(rs['cpu_load_short']),
272+
[{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}]
274273
)
275274

276275
@unittest.skip('Not implemented for 0.9')
@@ -349,14 +348,19 @@ def test_drop_database_fails(self):
349348
cli.drop_database('old_db')
350349

351350
def test_get_list_database(self):
352-
data = {'results': [{'series': [
353-
{'name': 'databases', 'columns': ['name'],
354-
'values': [['mydb'], ['myotherdb']]}]}]}
351+
data = {'results': [
352+
{'series': [
353+
{'name': 'databases',
354+
'values': [
355+
['new_db_1'],
356+
['new_db_2']],
357+
'columns': ['name']}]}
358+
]}
355359

356360
with _mocked_session(self.cli, 'get', 200, json.dumps(data)):
357361
self.assertListEqual(
358362
self.cli.get_list_database(),
359-
['mydb', 'myotherdb']
363+
[{'name': 'new_db_1'}, {'name': 'new_db_2'}]
360364
)
361365

362366
@raises(Exception)
@@ -367,19 +371,23 @@ def test_get_list_database_fails(self):
367371

368372
def test_get_list_series(self):
369373
example_response = \
370-
'{"results": [{"series": [{"values": [["fsfdsdf", "24h0m0s", 2]],'\
371-
' "columns": ["name", "duration", "replicaN"]}]}]}'
374+
'{"results": [{"series": [{"name": "cpu_load_short", "columns": ' \
375+
'["_id", "host", "region"], "values": ' \
376+
'[[1, "server01", "us-west"]]}]}]}'
372377

373378
with requests_mock.Mocker() as m:
374379
m.register_uri(
375380
requests_mock.GET,
376381
"http://localhost:8086/query",
377382
text=example_response
378383
)
384+
379385
self.assertListEqual(
380386
self.cli.get_list_series(),
381-
[{'duration': '24h0m0s',
382-
'name': 'fsfdsdf', 'replicaN': 2}]
387+
[{'name': 'cpu_load_short',
388+
'tags': [
389+
{'host': 'server01', '_id': 1, 'region': 'us-west'}
390+
]}]
383391
)
384392

385393
def test_create_retention_policy_default(self):

0 commit comments

Comments
 (0)