influxdb-api-sdk 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
influxdb/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Initialize the influxdb package."""
3
+
4
+ from __future__ import absolute_import
5
+ from __future__ import division
6
+ from __future__ import print_function
7
+ from __future__ import unicode_literals
8
+
9
+ from .client import InfluxDBClient
10
+ from .dataframe_client import DataFrameClient
11
+ from .helper import SeriesHelper
12
+
13
+
14
+ __all__ = [
15
+ "InfluxDBClient",
16
+ "DataFrameClient",
17
+ "SeriesHelper",
18
+ ]
19
+
20
+
21
+ __version__ = "5.3.1"
@@ -0,0 +1,502 @@
1
+ # -*- coding: utf-8 -*-
2
+ """DataFrame client for InfluxDB."""
3
+
4
+ from __future__ import absolute_import
5
+ from __future__ import division
6
+ from __future__ import print_function
7
+ from __future__ import unicode_literals
8
+
9
+ import math
10
+ from collections import defaultdict
11
+
12
+ import pandas as pd
13
+ import numpy as np
14
+
15
+ from .client import InfluxDBClient
16
+ from .line_protocol import _escape_tag
17
+
18
+
19
+ def _pandas_time_unit(time_precision):
20
+ unit = time_precision
21
+ if time_precision == "m":
22
+ unit = "ms"
23
+ elif time_precision == "u":
24
+ unit = "us"
25
+ elif time_precision == "n":
26
+ unit = "ns"
27
+ assert unit in ("s", "ms", "us", "ns")
28
+ return unit
29
+
30
+
31
+ def _escape_pandas_series(s):
32
+ return s.apply(lambda v: _escape_tag(v))
33
+
34
+
35
+ class DataFrameClient(InfluxDBClient):
36
+ """DataFrameClient instantiates InfluxDBClient to connect to the backend.
37
+
38
+ The ``DataFrameClient`` object holds information necessary to connect
39
+ to InfluxDB. Requests can be made to InfluxDB directly through the client.
40
+ The client reads and writes from pandas DataFrames.
41
+ """
42
+
43
+ EPOCH = pd.Timestamp("1970-01-01 00:00:00.000+00:00")
44
+
45
+ def write_points(
46
+ self,
47
+ dataframe,
48
+ measurement,
49
+ tags=None,
50
+ tag_columns=None,
51
+ field_columns=None,
52
+ time_precision=None,
53
+ database=None,
54
+ retention_policy=None,
55
+ batch_size=None,
56
+ protocol="line",
57
+ numeric_precision=None,
58
+ ):
59
+ """Write to multiple time series names.
60
+
61
+ Args:
62
+ dataframe (pd.DataFrame): data points in a DataFrame
63
+ measurement (str): name of measurement
64
+ tags (dict): dictionary of tags, with string key-values
65
+ tag_columns (list): [Optional, default None] List of data tag names
66
+ field_columns (list): [Optional, default None] List of data field names
67
+ time_precision (str): [Optional, default None] Either 's', 'ms', 'u' or 'n'.
68
+ database (str): [Optional] database to write to
69
+ retention_policy (str): [Optional] retention policy to write to
70
+ batch_size (int): [Optional] Value to write the points in batches
71
+ instead of all at one time. Useful for when doing data dumps from
72
+ one database to another or when doing a massive write operation
73
+ protocol (str): Protocol for writing data. Either 'line' or 'json'.
74
+ numeric_precision (str or int): Precision for floating point values.
75
+ Either None, 'full' or some int, where int is the desired decimal
76
+ precision. 'full' preserves full precision for int and float
77
+ datatypes. Defaults to None, which preserves 14-15 significant
78
+ figures for float and all significant figures for int datatypes.
79
+
80
+ """
81
+ if tag_columns is None:
82
+ tag_columns = []
83
+
84
+ if field_columns is None:
85
+ field_columns = []
86
+
87
+ if batch_size:
88
+ number_batches = int(math.ceil(len(dataframe) / float(batch_size)))
89
+
90
+ for batch in range(number_batches):
91
+ start_index = batch * batch_size
92
+ end_index = (batch + 1) * batch_size
93
+
94
+ if protocol == "line":
95
+ points = self._convert_dataframe_to_lines(
96
+ dataframe.iloc[start_index:end_index].copy(),
97
+ measurement=measurement,
98
+ global_tags=tags,
99
+ time_precision=time_precision,
100
+ tag_columns=tag_columns,
101
+ field_columns=field_columns,
102
+ numeric_precision=numeric_precision,
103
+ )
104
+ else:
105
+ points = self._convert_dataframe_to_json(
106
+ dataframe.iloc[start_index:end_index].copy(),
107
+ measurement=measurement,
108
+ tags=tags,
109
+ time_precision=time_precision,
110
+ tag_columns=tag_columns,
111
+ field_columns=field_columns,
112
+ )
113
+
114
+ super(DataFrameClient, self).write_points(
115
+ points,
116
+ time_precision,
117
+ database,
118
+ retention_policy,
119
+ protocol=protocol,
120
+ )
121
+
122
+ return True
123
+
124
+ if protocol == "line":
125
+ points = self._convert_dataframe_to_lines(
126
+ dataframe,
127
+ measurement=measurement,
128
+ global_tags=tags,
129
+ tag_columns=tag_columns,
130
+ field_columns=field_columns,
131
+ time_precision=time_precision,
132
+ numeric_precision=numeric_precision,
133
+ )
134
+ else:
135
+ points = self._convert_dataframe_to_json(
136
+ dataframe,
137
+ measurement=measurement,
138
+ tags=tags,
139
+ time_precision=time_precision,
140
+ tag_columns=tag_columns,
141
+ field_columns=field_columns,
142
+ )
143
+
144
+ super(DataFrameClient, self).write_points(points, time_precision, database, retention_policy, protocol=protocol)
145
+
146
+ return True
147
+
148
+ def query(
149
+ self,
150
+ query,
151
+ params=None,
152
+ bind_params=None,
153
+ epoch=None,
154
+ expected_response_code=200,
155
+ database=None,
156
+ raise_errors=True,
157
+ chunked=False,
158
+ chunk_size=0,
159
+ method="GET",
160
+ dropna=True,
161
+ data_frame_index=None,
162
+ ):
163
+ """Query data into a DataFrame.
164
+
165
+ Warning:
166
+ In order to avoid injection vulnerabilities (similar to SQL injection),
167
+ do not directly include untrusted data into the query parameter,
168
+ use bind_params instead.
169
+
170
+ Args:
171
+ query (str): the actual query string
172
+ params (dict): additional parameters for the request, defaults to {}
173
+ bind_params (dict): bind parameters for the query:
174
+ any variable in the query written as '$var_name' will be
175
+ replaced with bind_params['var_name']. Only works in the
176
+ WHERE clause and takes precedence over params['params']
177
+ epoch (str): response timestamps to be in epoch format either 'h',
178
+ 'm', 's', 'ms', 'u', or 'ns', defaults to None which is
179
+ RFC3339 UTC format with nanosecond precision
180
+ expected_response_code (int): the expected status code of response,
181
+ defaults to 200
182
+ database (str): database to query, defaults to None
183
+ raise_errors (bool): Whether or not to raise exceptions when InfluxDB
184
+ returns errors, defaults to True
185
+ chunked (bool): Enable to use chunked responses from InfluxDB.
186
+ With chunked enabled, one ResultSet is returned per chunk
187
+ containing all results within that chunk
188
+ chunk_size (int): Size of each chunk to tell InfluxDB to use.
189
+ method (str): the HTTP method for the request, defaults to GET
190
+ dropna (bool): drop columns where all values are missing
191
+ data_frame_index (list): the list of columns that are used as DataFrame index
192
+
193
+ Returns:
194
+ ResultSet or dict: the queried data
195
+
196
+ """
197
+ query_args = {
198
+ "params": params,
199
+ "bind_params": bind_params,
200
+ "epoch": epoch,
201
+ "expected_response_code": expected_response_code,
202
+ "raise_errors": raise_errors,
203
+ "chunked": chunked,
204
+ "database": database,
205
+ "method": method,
206
+ "chunk_size": chunk_size,
207
+ }
208
+ results = super(DataFrameClient, self).query(query, **query_args)
209
+ if query.strip().upper().startswith("SELECT"):
210
+ if len(results) > 0:
211
+ return self._to_dataframe(results, dropna, data_frame_index=data_frame_index)
212
+ else:
213
+ return {}
214
+ else:
215
+ return results
216
+
217
+ def _to_dataframe(self, rs, dropna=True, data_frame_index=None):
218
+ result = defaultdict(list)
219
+ if isinstance(rs, list):
220
+ return map(self._to_dataframe, rs, [dropna for _ in range(len(rs))])
221
+
222
+ for key, data in rs.items():
223
+ name, tags = key
224
+ if tags is None:
225
+ key = name
226
+ else:
227
+ key = (name, tuple(sorted(tags.items())))
228
+ df = pd.DataFrame(data)
229
+ if pd.api.types.is_object_dtype(df.time) or pd.api.types.is_string_dtype(df.time):
230
+ df.time = pd.to_datetime(df.time, format="ISO8601")
231
+ else:
232
+ df.time = pd.to_datetime(df.time)
233
+
234
+ if data_frame_index:
235
+ df.set_index(data_frame_index, inplace=True)
236
+ else:
237
+ df.set_index("time", inplace=True)
238
+ if df.index.tzinfo is None:
239
+ df.index = df.index.tz_localize("UTC")
240
+ df.index.name = None
241
+
242
+ result[key].append(df)
243
+ for key, data in result.items():
244
+ df = pd.concat(data).sort_index()
245
+ if dropna:
246
+ df.dropna(how="all", axis=1, inplace=True)
247
+ result[key] = df
248
+
249
+ return result
250
+
251
+ @staticmethod
252
+ def _convert_dataframe_to_json(
253
+ dataframe,
254
+ measurement,
255
+ tags=None,
256
+ tag_columns=None,
257
+ field_columns=None,
258
+ time_precision=None,
259
+ ):
260
+
261
+ if not isinstance(dataframe, pd.DataFrame):
262
+ raise TypeError("Must be DataFrame, but type was: {0}.".format(type(dataframe)))
263
+ if not (isinstance(dataframe.index, pd.PeriodIndex) or isinstance(dataframe.index, pd.DatetimeIndex)):
264
+ raise TypeError("Must be DataFrame with DatetimeIndex or PeriodIndex.")
265
+
266
+ # Make sure tags and tag columns are correctly typed
267
+ tag_columns = tag_columns if tag_columns is not None else []
268
+ field_columns = field_columns if field_columns is not None else []
269
+ tags = tags if tags is not None else {}
270
+ # Assume field columns are all columns not included in tag columns
271
+ if not field_columns:
272
+ field_columns = list(set(dataframe.columns).difference(set(tag_columns)))
273
+
274
+ if not isinstance(dataframe.index, pd.DatetimeIndex):
275
+ dataframe.index = pd.to_datetime(dataframe.index)
276
+ if dataframe.index.tzinfo is None:
277
+ dataframe.index = dataframe.index.tz_localize("UTC")
278
+
279
+ # Convert column to strings
280
+ dataframe.columns = dataframe.columns.astype("str")
281
+
282
+ # Convert dtype for json serialization
283
+ dataframe = dataframe.astype("object")
284
+
285
+ precision_factor = {
286
+ "n": 1,
287
+ "u": 1e3,
288
+ "ms": 1e6,
289
+ "s": 1e9,
290
+ "m": 1e9 * 60,
291
+ "h": 1e9 * 3600,
292
+ }.get(time_precision, 1)
293
+
294
+ if not tag_columns:
295
+ points = [
296
+ {
297
+ "measurement": measurement,
298
+ "fields": rec.replace([np.inf, -np.inf], np.nan).dropna().to_dict(),
299
+ "time": np.int64(ts.value / precision_factor),
300
+ }
301
+ for ts, (_, rec) in zip(dataframe.index, dataframe[field_columns].iterrows(), strict=True)
302
+ ]
303
+
304
+ return points
305
+
306
+ points = [
307
+ {
308
+ "measurement": measurement,
309
+ "tags": dict(list(tag.items()) + list(tags.items())),
310
+ "fields": rec.replace([np.inf, -np.inf], np.nan).dropna().to_dict(),
311
+ "time": np.int64(ts.value / precision_factor),
312
+ }
313
+ for ts, tag, (_, rec) in zip(
314
+ dataframe.index,
315
+ dataframe[tag_columns].to_dict("records"),
316
+ dataframe[field_columns].iterrows(),
317
+ strict=True,
318
+ )
319
+ ]
320
+
321
+ return points
322
+
323
+ def _convert_dataframe_to_lines( # noqa: C901
324
+ self,
325
+ dataframe,
326
+ measurement,
327
+ field_columns=None,
328
+ tag_columns=None,
329
+ global_tags=None,
330
+ time_precision=None,
331
+ numeric_precision=None,
332
+ ):
333
+
334
+ dataframe = dataframe.dropna(how="all").copy()
335
+ if len(dataframe) == 0:
336
+ return []
337
+
338
+ if not isinstance(dataframe, pd.DataFrame):
339
+ raise TypeError("Must be DataFrame, but type was: {0}.".format(type(dataframe)))
340
+ if not (isinstance(dataframe.index, pd.PeriodIndex) or isinstance(dataframe.index, pd.DatetimeIndex)):
341
+ raise TypeError("Must be DataFrame with DatetimeIndex or PeriodIndex.")
342
+
343
+ dataframe = dataframe.rename(columns={item: _escape_tag(item) for item in dataframe.columns})
344
+ # Create a Series of columns for easier indexing
345
+ column_series = pd.Series(dataframe.columns)
346
+
347
+ if field_columns is None:
348
+ field_columns = []
349
+
350
+ if tag_columns is None:
351
+ tag_columns = []
352
+
353
+ if global_tags is None:
354
+ global_tags = {}
355
+
356
+ # Make sure field_columns and tag_columns are lists
357
+ field_columns = list(field_columns) if list(field_columns) else []
358
+ tag_columns = list(tag_columns) if list(tag_columns) else []
359
+
360
+ # If field columns but no tag columns, assume rest of columns are tags
361
+ if field_columns and (not tag_columns):
362
+ tag_columns = list(column_series[~column_series.isin(field_columns)])
363
+
364
+ # If no field columns, assume non-tag columns are fields
365
+ if not field_columns:
366
+ field_columns = list(column_series[~column_series.isin(tag_columns)])
367
+
368
+ precision_factor = {
369
+ "n": 1,
370
+ "u": 1e3,
371
+ "ms": 1e6,
372
+ "s": 1e9,
373
+ "m": 1e9 * 60,
374
+ "h": 1e9 * 3600,
375
+ }.get(time_precision, 1)
376
+
377
+ # Make array of timestamp ints
378
+ if isinstance(dataframe.index, pd.PeriodIndex):
379
+ time = (
380
+ (dataframe.index.to_timestamp().values.astype("datetime64[ns]").astype(np.int64) / precision_factor)
381
+ .astype(np.int64)
382
+ .astype(str)
383
+ )
384
+ else:
385
+ time = (
386
+ (pd.to_datetime(dataframe.index).values.astype("datetime64[ns]").astype(np.int64) / precision_factor)
387
+ .astype(np.int64)
388
+ .astype(str)
389
+ )
390
+
391
+ # If tag columns exist, make an array of formatted tag keys and values
392
+ if tag_columns:
393
+ # Make global_tags as tag_columns
394
+ if global_tags:
395
+ for tag in global_tags:
396
+ dataframe[tag] = global_tags[tag]
397
+ tag_columns.append(tag)
398
+
399
+ tag_df = dataframe[tag_columns]
400
+ tag_df = tag_df.fillna("") # replace NA with empty string
401
+ tag_df = tag_df.sort_index(axis=1)
402
+ tag_df = self._stringify_dataframe(tag_df, numeric_precision, datatype="tag")
403
+
404
+ # join prepended tags, leaving None values out
405
+ tags = tag_df.apply(lambda s: ["," + s.name + "=" + v if v else "" for v in s])
406
+ tags = tags.sum(axis=1)
407
+
408
+ del tag_df
409
+ elif global_tags:
410
+ tag_string = "".join(
411
+ [
412
+ ",{}={}".format(k, _escape_tag(v)) if v not in [None, ""] else ""
413
+ for k, v in sorted(global_tags.items())
414
+ ]
415
+ )
416
+ tags = pd.Series(tag_string, index=dataframe.index)
417
+ else:
418
+ tags = ""
419
+
420
+ # Make an array of formatted field keys and values
421
+ field_df = dataframe[field_columns].replace([np.inf, -np.inf], np.nan)
422
+ nans = pd.isnull(field_df)
423
+
424
+ field_df = self._stringify_dataframe(field_df, numeric_precision, datatype="field")
425
+
426
+ field_df = (field_df.columns.values + "=").tolist() + field_df
427
+ field_df[field_df.columns[1:]] = "," + field_df[field_df.columns[1:]]
428
+ field_df[nans] = ""
429
+
430
+ fields = field_df.sum(axis=1).map(lambda x: x.lstrip(","))
431
+ del field_df
432
+
433
+ # Generate line protocol string
434
+ measurement = _escape_tag(measurement)
435
+ points = (measurement + tags + " " + fields + " " + time).tolist()
436
+ return points
437
+
438
+ @staticmethod
439
+ def _stringify_dataframe(dframe, numeric_precision, datatype="field"):
440
+
441
+ # Prevent modification of input dataframe
442
+ dframe = dframe.copy()
443
+
444
+ # Find int and string columns for field-type data
445
+ int_columns = dframe.select_dtypes(include=["integer"]).columns
446
+ # For pandas 3+ compatibility: explicitly include 'string' dtype to avoid deprecation warning
447
+ try:
448
+ string_columns = dframe.select_dtypes(include=["object", "string"]).columns
449
+ except (TypeError, AttributeError): # pragma: no cover
450
+ # Older pandas versions don't have 'string' dtype
451
+ string_columns = dframe.select_dtypes(include=["object"]).columns
452
+
453
+ # Convert dframe to string
454
+ if numeric_precision is None:
455
+ # If no precision specified, convert directly to string (fast)
456
+ dframe = dframe.astype(str)
457
+ elif numeric_precision == "full":
458
+ # If full precision, use repr to get full float precision
459
+ float_columns = dframe.select_dtypes(include=["floating"]).columns
460
+ nonfloat_columns = dframe.columns[~dframe.columns.isin(float_columns)]
461
+ dframe[float_columns] = dframe[float_columns].apply(lambda col: col.map(repr))
462
+ dframe[nonfloat_columns] = dframe[nonfloat_columns].astype(str)
463
+ elif isinstance(numeric_precision, int):
464
+ # If precision is specified, round to appropriate precision
465
+ float_columns = dframe.select_dtypes(include=["floating"]).columns
466
+ nonfloat_columns = dframe.columns[~dframe.columns.isin(float_columns)]
467
+ dframe[float_columns] = dframe[float_columns].round(numeric_precision)
468
+
469
+ # If desired precision is > 10 decimal places, need to use repr
470
+ if numeric_precision > 10:
471
+ dframe[float_columns] = dframe[float_columns].apply(lambda col: col.map(repr))
472
+ dframe[nonfloat_columns] = dframe[nonfloat_columns].astype(str)
473
+ else:
474
+ dframe = dframe.astype(str)
475
+ else:
476
+ raise ValueError("Invalid numeric precision.")
477
+
478
+ if datatype == "field":
479
+ # If dealing with fields, format ints and strings correctly
480
+ dframe[int_columns] += "i"
481
+ dframe[string_columns] = '"' + dframe[string_columns] + '"'
482
+ elif datatype == "tag":
483
+ dframe = dframe.apply(_escape_pandas_series)
484
+
485
+ dframe.columns = dframe.columns.astype(str)
486
+
487
+ return dframe
488
+
489
+ def _datetime_to_epoch(self, datetime, time_precision="s"):
490
+ seconds = (datetime - self.EPOCH).total_seconds()
491
+ if time_precision == "h":
492
+ return seconds / 3600
493
+ elif time_precision == "m":
494
+ return seconds / 60
495
+ elif time_precision == "s":
496
+ return seconds
497
+ elif time_precision == "ms":
498
+ return seconds * 1e3
499
+ elif time_precision == "u":
500
+ return seconds * 1e6
501
+ elif time_precision == "n":
502
+ return seconds * 1e9
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Module to generate chunked JSON replies."""
3
+
4
+ #
5
+ # Author: Adrian Sampson <adrian@radbox.org>
6
+ # Source: https://gist.github.com/sampsyo/920215
7
+ #
8
+
9
+ from __future__ import absolute_import
10
+ from __future__ import division
11
+ from __future__ import print_function
12
+ from __future__ import unicode_literals
13
+
14
+ import json
15
+
16
+
17
+ def loads(s):
18
+ """Generate a sequence of JSON values from a string.
19
+
20
+ Args:
21
+ s (str): JSON string to parse
22
+
23
+ Yields:
24
+ dict: JSON objects parsed from the string
25
+
26
+ Raises:
27
+ ValueError: if no JSON object is found
28
+
29
+ """
30
+ _decoder = json.JSONDecoder()
31
+
32
+ while s:
33
+ s = s.strip()
34
+ obj, pos = _decoder.raw_decode(s)
35
+ if not pos:
36
+ raise ValueError("no JSON object found at %i" % pos)
37
+ yield obj
38
+ s = s[pos:]