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 +21 -0
- influxdb/_dataframe_client.py +502 -0
- influxdb/chunked_json.py +38 -0
- influxdb/client.py +1121 -0
- influxdb/dataframe_client.py +29 -0
- influxdb/exceptions.py +44 -0
- influxdb/helper.py +203 -0
- influxdb/influxdb08/__init__.py +18 -0
- influxdb/influxdb08/chunked_json.py +27 -0
- influxdb/influxdb08/client.py +799 -0
- influxdb/influxdb08/dataframe_client.py +171 -0
- influxdb/influxdb08/helper.py +142 -0
- influxdb/line_protocol.py +230 -0
- influxdb/resultset.py +224 -0
- influxdb_api_sdk-1.0.0.dist-info/METADATA +244 -0
- influxdb_api_sdk-1.0.0.dist-info/RECORD +20 -0
- influxdb_api_sdk-1.0.0.dist-info/WHEEL +5 -0
- influxdb_api_sdk-1.0.0.dist-info/licenses/LICENSE +201 -0
- influxdb_api_sdk-1.0.0.dist-info/licenses/NOTICE +7 -0
- influxdb_api_sdk-1.0.0.dist-info/top_level.txt +1 -0
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
|
influxdb/chunked_json.py
ADDED
|
@@ -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:]
|