influxdb3-python 0.6.1__tar.gz → 0.8.0__tar.gz

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.
Files changed (60) hide show
  1. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/PKG-INFO +2 -2
  2. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/README.md +1 -1
  3. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb3_python.egg-info/PKG-INFO +2 -2
  4. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb3_python.egg-info/SOURCES.txt +4 -1
  5. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/__init__.py +22 -2
  6. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/version.py +1 -1
  7. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/_base.py +6 -5
  8. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/exceptions.py +11 -1
  9. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/influxdb_client.py +1 -0
  10. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +16 -156
  11. influxdb3_python-0.8.0/influxdb_client_3/write_client/client/write/polars_dataframe_serializer.py +160 -0
  12. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/write_api.py +2 -2
  13. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/extras.py +1 -6
  14. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_api_client.py +37 -1
  15. influxdb3_python-0.8.0/tests/test_dataframe_serializer.py +515 -0
  16. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_influxdb_client_3.py +21 -0
  17. influxdb3_python-0.8.0/tests/test_influxdb_client_3_integration.py +58 -0
  18. influxdb3_python-0.8.0/tests/test_polars.py +63 -0
  19. influxdb3_python-0.8.0/tests/test_write_file.py +66 -0
  20. influxdb3_python-0.6.1/tests/test_dataframe_serializer.py +0 -45
  21. influxdb3_python-0.6.1/tests/test_influxdb_client_3_integration.py +0 -45
  22. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/LICENSE +0 -0
  23. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb3_python.egg-info/dependency_links.txt +0 -0
  24. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb3_python.egg-info/requires.txt +0 -0
  25. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb3_python.egg-info/top_level.txt +0 -0
  26. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/py.typed +0 -0
  27. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/query/__init__.py +0 -0
  28. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/query/query_api.py +0 -0
  29. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/read_file.py +0 -0
  30. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/__init__.py +0 -0
  31. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
  32. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/_sync/api_client.py +0 -0
  33. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/_sync/rest.py +0 -0
  34. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/__init__.py +0 -0
  35. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
  36. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
  37. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/util/date_utils.py +0 -0
  38. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
  39. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
  40. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
  41. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/warnings.py +0 -0
  42. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
  43. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/write/point.py +0 -0
  44. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/client/write/retry.py +0 -0
  45. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/configuration.py +0 -0
  46. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/domain/__init__.py +0 -0
  47. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/domain/write_precision.py +0 -0
  48. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/rest.py +0 -0
  49. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/service/__init__.py +0 -0
  50. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/service/_base_service.py +0 -0
  51. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/service/signin_service.py +0 -0
  52. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/service/signout_service.py +0 -0
  53. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/influxdb_client_3/write_client/service/write_service.py +0 -0
  54. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/setup.cfg +0 -0
  55. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/setup.py +0 -0
  56. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_date_helper.py +0 -0
  57. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_deep_merge.py +0 -0
  58. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_merge_options.py +0 -0
  59. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_point.py +0 -0
  60. {influxdb3_python-0.6.1 → influxdb3_python-0.8.0}/tests/test_query.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.6.1
3
+ Version: 0.8.0
4
4
  Summary: Community Python client for InfluxDB 3.0
5
5
  Home-page: https://github.com/InfluxCommunity/influxdb3-python
6
6
  Author: InfluxData
@@ -93,7 +93,7 @@ from influxdb_client_3 import InfluxDBClient3, Point
93
93
  ## Initialization
94
94
  If you are using InfluxDB Cloud, then you should note that:
95
95
  1. You will need to supply your org id, this is not necessary for InfluxDB Dedicated.
96
- 2. Use a bucketname for the database argument.
96
+ 2. Use bucket name for the `database` argument.
97
97
 
98
98
  ```python
99
99
  client = InfluxDBClient3(token="your-token",
@@ -58,7 +58,7 @@ from influxdb_client_3 import InfluxDBClient3, Point
58
58
  ## Initialization
59
59
  If you are using InfluxDB Cloud, then you should note that:
60
60
  1. You will need to supply your org id, this is not necessary for InfluxDB Dedicated.
61
- 2. Use a bucketname for the database argument.
61
+ 2. Use bucket name for the `database` argument.
62
62
 
63
63
  ```python
64
64
  client = InfluxDBClient3(token="your-token",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.6.1
3
+ Version: 0.8.0
4
4
  Summary: Community Python client for InfluxDB 3.0
5
5
  Home-page: https://github.com/InfluxCommunity/influxdb3-python
6
6
  Author: InfluxData
@@ -93,7 +93,7 @@ from influxdb_client_3 import InfluxDBClient3, Point
93
93
  ## Initialization
94
94
  If you are using InfluxDB Cloud, then you should note that:
95
95
  1. You will need to supply your org id, this is not necessary for InfluxDB Dedicated.
96
- 2. Use a bucketname for the database argument.
96
+ 2. Use bucket name for the `database` argument.
97
97
 
98
98
  ```python
99
99
  client = InfluxDBClient3(token="your-token",
@@ -34,6 +34,7 @@ influxdb_client_3/write_client/client/util/multiprocessing_helper.py
34
34
  influxdb_client_3/write_client/client/write/__init__.py
35
35
  influxdb_client_3/write_client/client/write/dataframe_serializer.py
36
36
  influxdb_client_3/write_client/client/write/point.py
37
+ influxdb_client_3/write_client/client/write/polars_dataframe_serializer.py
37
38
  influxdb_client_3/write_client/client/write/retry.py
38
39
  influxdb_client_3/write_client/domain/__init__.py
39
40
  influxdb_client_3/write_client/domain/write_precision.py
@@ -50,4 +51,6 @@ tests/test_influxdb_client_3.py
50
51
  tests/test_influxdb_client_3_integration.py
51
52
  tests/test_merge_options.py
52
53
  tests/test_point.py
53
- tests/test_query.py
54
+ tests/test_polars.py
55
+ tests/test_query.py
56
+ tests/test_write_file.py
@@ -1,5 +1,4 @@
1
1
  import urllib.parse
2
-
3
2
  import pyarrow as pa
4
3
  import importlib.util
5
4
 
@@ -111,7 +110,28 @@ class InfluxDBClient3:
111
110
  :type write_client_options: callable
112
111
  :param flight_client_options: Function for providing additional arguments for the FlightClient.
113
112
  :type flight_client_options: callable
114
- :param kwargs: Additional arguments for the InfluxDB Client.
113
+ :key auth_scheme: token authentication scheme. Set to "Bearer" for Edge.
114
+ :key bool verify_ssl: Set this to false to skip verifying SSL certificate when calling API from https server.
115
+ :key str ssl_ca_cert: Set this to customize the certificate file to verify the peer.
116
+ :key str cert_file: Path to the certificate that will be used for mTLS authentication.
117
+ :key str cert_key_file: Path to the file contains private key for mTLS certificate.
118
+ :key str cert_key_password: String or function which returns password for decrypting the mTLS private key.
119
+ :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
120
+ Be aware that only delivered certificate/ key files or an SSL Context are
121
+ possible.
122
+ :key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128)
123
+ :key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
124
+ authentication.
125
+ :key int connection_pool_maxsize: Number of connections to save that can be reused by urllib3.
126
+ Defaults to "multiprocessing.cpu_count() * 5".
127
+ :key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
128
+ except batching writes. As a default there is no one retry strategy.
129
+ :key bool auth_basic: Set this to true to enable basic authentication when talking to a InfluxDB 1.8.x that
130
+ does not use auth-enabled but is protected by a reverse proxy with basic authentication.
131
+ (defaults to false, don't set to true when talking to InfluxDB 2)
132
+ :key str username: ``username`` to authenticate via username and password credentials to the InfluxDB 2.x
133
+ :key str password: ``password`` to authenticate via username and password credentials to the InfluxDB 2.x
134
+ :key list[str] profilers: list of enabled Flux profilers
115
135
  """
116
136
  self._org = org if org is not None else "default"
117
137
  self._database = database
@@ -1,4 +1,4 @@
1
1
  """Version of the Client that is used in User-Agent header."""
2
2
 
3
- VERSION = '0.6.1'
3
+ VERSION = '0.8.0'
4
4
  USER_AGENT = f'influxdb3-python/{VERSION}'
@@ -7,8 +7,7 @@ import logging
7
7
  import os
8
8
  from typing import Iterable
9
9
 
10
- from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer, \
11
- PolarsDataframeSerializer
10
+ from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer
12
11
  from influxdb_client_3.write_client.configuration import Configuration
13
12
  from influxdb_client_3.write_client.rest import _UTF_8_encoding
14
13
  from influxdb_client_3.write_client.service.write_service import WriteService
@@ -38,7 +37,6 @@ class _BaseClient(object):
38
37
  def __init__(self, url, token, debug=None, timeout=10_000, enable_gzip=False, org: str = None,
39
38
  default_tags: dict = None, http_client_logger: str = None, **kwargs) -> None:
40
39
  self.url = url
41
- self.token = token
42
40
  self.org = org
43
41
 
44
42
  self.default_tags = default_tags
@@ -71,9 +69,10 @@ class _BaseClient(object):
71
69
  self.auth_header_name = None
72
70
  self.auth_header_value = None
73
71
  # by token
74
- if self.token:
72
+ if token:
73
+ auth_scheme = kwargs.get('auth_scheme', "Token")
75
74
  self.auth_header_name = "Authorization"
76
- self.auth_header_value = "Token " + self.token
75
+ self.auth_header_value = f"{auth_scheme} {token}"
77
76
  # by HTTP basic
78
77
  auth_basic = kwargs.get('auth_basic', False)
79
78
  if auth_basic:
@@ -249,6 +248,8 @@ class _BaseWriteApi(object):
249
248
  self._serialize(Point.from_dict(record, write_precision=write_precision, **kwargs),
250
249
  write_precision, payload, **kwargs)
251
250
  elif 'polars' in str(type(record)):
251
+ from influxdb_client_3.write_client.client.write.polars_dataframe_serializer import \
252
+ PolarsDataframeSerializer
252
253
  serializer = PolarsDataframeSerializer(record, self._point_settings, write_precision, **kwargs)
253
254
  self._serialize(serializer.serialize(), write_precision, payload, **kwargs)
254
255
 
@@ -26,8 +26,18 @@ class InfluxDBError(Exception):
26
26
  # Body
27
27
  if response.data:
28
28
  import json
29
+
30
+ def get(d, key):
31
+ if not key or d is None:
32
+ return d
33
+ return get(d.get(key[0]), key[1:])
29
34
  try:
30
- return json.loads(response.data)["message"]
35
+ node = json.loads(response.data)
36
+ for key in [['message'], ['data', 'error_message'], ['error']]:
37
+ value = get(node, key)
38
+ if value is not None:
39
+ return value
40
+ return response.data
31
41
  except Exception as e:
32
42
  logging.debug(f"Cannot parse error response to JSON: {response.data}, {e}")
33
43
  return response.data
@@ -27,6 +27,7 @@ class InfluxDBClient(_BaseClient):
27
27
  :param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
28
28
  supports the Gzip compression.
29
29
  :param org: organization name (used as a default in Query, Write and Delete API)
30
+ :key auth_scheme: token authentication scheme. Set to "Bearer" for Edge.
30
31
  :key bool verify_ssl: Set this to false to skip verifying SSL certificate when calling API from https server.
31
32
  :key str ssl_ca_cert: Set this to customize the certificate file to verify the peer.
32
33
  :key str cert_file: Path to the certificate that will be used for mTLS authentication.
@@ -132,7 +132,7 @@ class DataframeSerializer:
132
132
  keys = []
133
133
  # tags holds a list of tag f-string segments ordered alphabetically by tag key.
134
134
  tags = []
135
- # fields holds a list of field f-string segments ordered alphebetically by field key
135
+ # fields holds a list of field f-string segments ordered alphabetically by field key
136
136
  fields = []
137
137
  # field_indexes holds the index into each row of all the fields.
138
138
  field_indexes = []
@@ -160,6 +160,11 @@ class DataframeSerializer:
160
160
  # null_columns has a bool value for each column holding
161
161
  # whether that column contains any null (NaN or None) values.
162
162
  null_columns = data_frame.isnull().any()
163
+
164
+ # inf_columns has a bool value for each column holding
165
+ # whether that column contains any Inf values.
166
+ inf_columns = data_frame.isin([np.inf, -np.inf]).any()
167
+
163
168
  timestamp_index = 0
164
169
 
165
170
  # Iterate through the columns building up the expression for each column.
@@ -175,9 +180,10 @@ class DataframeSerializer:
175
180
 
176
181
  if key in data_frame_tag_columns:
177
182
  # This column is a tag column.
178
- if null_columns.iloc[index]:
183
+ if null_columns.iloc[index] or inf_columns.iloc[index]:
179
184
  key_value = f"""{{
180
- '' if {val_format} == '' or pd.isna({val_format}) else
185
+ '' if {val_format} == '' or pd.isna({val_format}) or
186
+ ({inf_columns.iloc[index]} and np.isinf({val_format})) else
181
187
  f',{key_format}={{str({val_format}).translate(_ESCAPE_STRING)}}'
182
188
  }}"""
183
189
  else:
@@ -199,16 +205,17 @@ class DataframeSerializer:
199
205
  if (issubclass(value.type, np.integer) or issubclass(value.type, np.floating) or
200
206
  issubclass(value.type, np.bool_)):
201
207
  suffix = 'i' if issubclass(value.type, np.integer) else ''
202
- if null_columns.iloc[index]:
208
+ if null_columns.iloc[index] or inf_columns.iloc[index]:
203
209
  field_value = (
204
- f"""{{"" if pd.isna({val_format}) else f"{sep}{key_format}={{{val_format}}}{suffix}"}}"""
210
+ f"""{{"" if pd.isna({val_format}) or ({inf_columns.iloc[index]} and np.isinf({val_format})) else
211
+ f"{sep}{key_format}={{{val_format}}}{suffix}"}}"""
205
212
  )
206
213
  else:
207
214
  field_value = f'{sep}{key_format}={{{val_format}}}{suffix}'
208
215
  else:
209
- if null_columns.iloc[index]:
216
+ if null_columns.iloc[index] or inf_columns.iloc[index]:
210
217
  field_value = f"""{{
211
- '' if pd.isna({val_format}) else
218
+ '' if pd.isna({val_format}) or ({inf_columns.iloc[index]} and np.isinf({val_format})) else
212
219
  f'{sep}{key_format}="{{str({val_format}).translate(_ESCAPE_STRING)}}"'
213
220
  }}"""
214
221
  else:
@@ -234,11 +241,12 @@ class DataframeSerializer:
234
241
  '_ESCAPE_STRING': _ESCAPE_STRING,
235
242
  'keys': keys,
236
243
  'pd': pd,
244
+ 'np': np,
237
245
  })
238
246
 
239
247
  for k, v in dict(data_frame.dtypes).items():
240
248
  if k in data_frame_tag_columns:
241
- data_frame[k].replace('', np.nan, inplace=True)
249
+ data_frame[k] = data_frame[k].apply(lambda x: np.nan if x == '' else x)
242
250
 
243
251
  self.data_frame = data_frame
244
252
  self.f = f
@@ -284,137 +292,6 @@ class DataframeSerializer:
284
292
  return self.number_of_chunks
285
293
 
286
294
 
287
- class PolarsDataframeSerializer:
288
- """Serialize DataFrame into LineProtocols."""
289
-
290
- def __init__(self, data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, chunk_size: int = None,
291
- **kwargs) -> None:
292
- """
293
- Init serializer.
294
-
295
- :param data_frame: Polars DataFrame to serialize
296
- :param point_settings: Default Tags
297
- :param precision: The precision for the unix timestamps within the body line-protocol.
298
- :param chunk_size: The size of chunk for serializing into chunks.
299
- :key data_frame_measurement_name: name of measurement for writing Polars DataFrame
300
- :key data_frame_tag_columns: list of DataFrame columns which are tags, rest columns will be fields
301
- :key data_frame_timestamp_column: name of DataFrame column which contains a timestamp.
302
- :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column
303
- """
304
-
305
- self.data_frame = data_frame
306
- self.point_settings = point_settings
307
- self.precision = precision
308
- self.chunk_size = chunk_size
309
- self.measurement_name = kwargs.get("data_frame_measurement_name", "measurement")
310
- self.tag_columns = kwargs.get("data_frame_tag_columns", [])
311
- self.timestamp_column = kwargs.get("data_frame_timestamp_column", None)
312
- self.timestamp_timezone = kwargs.get("data_frame_timestamp_timezone", None)
313
-
314
- self.column_indices = {name: index for index, name in enumerate(data_frame.columns)}
315
-
316
- if self.timestamp_column is None or self.timestamp_column not in self.column_indices:
317
- raise ValueError(
318
- f"Timestamp column {self.timestamp_column} not found in DataFrame. Please define a valid timestamp "
319
- f"column.")
320
-
321
- #
322
- # prepare chunks
323
- #
324
- if chunk_size is not None:
325
- self.number_of_chunks = int(math.ceil(len(data_frame) / float(chunk_size)))
326
- self.chunk_size = chunk_size
327
- else:
328
- self.number_of_chunks = None
329
-
330
- def escape_key(self, value):
331
- return str(value).translate(_ESCAPE_KEY)
332
-
333
- def escape_value(self, value):
334
- return str(value).translate(_ESCAPE_STRING)
335
-
336
- def to_line_protocol(self, row):
337
- # Filter out None or empty values for tags
338
- tags = ""
339
-
340
- tags = ",".join(
341
- f'{self.escape_key(col)}={self.escape_key(row[self.column_indices[col]])}'
342
- for col in self.tag_columns
343
- if row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
344
- )
345
-
346
- if self.point_settings.defaultTags:
347
- default_tags = ",".join(
348
- f'{self.escape_key(key)}={self.escape_key(value)}'
349
- for key, value in self.point_settings.defaultTags.items()
350
- )
351
- # Ensure there's a comma between existing tags and default tags if both are present
352
- if tags and default_tags:
353
- tags += ","
354
- tags += default_tags
355
-
356
- # add escape symbols for special characters to tags
357
-
358
- fields = ",".join(
359
- f"{col}=\"{self.escape_value(row[self.column_indices[col]])}\"" if isinstance(row[self.column_indices[col]],
360
- str)
361
- else f"{col}={str(row[self.column_indices[col]]).lower()}" if isinstance(row[self.column_indices[col]],
362
- bool) # Check for bool first
363
- else f"{col}={row[self.column_indices[col]]}i" if isinstance(row[self.column_indices[col]], int)
364
- else f"{col}={row[self.column_indices[col]]}"
365
- for col in self.column_indices
366
- if col not in self.tag_columns + [self.timestamp_column] and
367
- row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
368
- )
369
-
370
- # Access the Unix timestamp
371
- timestamp = row[self.column_indices[self.timestamp_column]]
372
- if tags != "":
373
- line_protocol = f"{self.measurement_name},{tags} {fields} {timestamp}"
374
- else:
375
- line_protocol = f"{self.measurement_name} {fields} {timestamp}"
376
-
377
- return line_protocol
378
-
379
- def serialize(self, chunk_idx: int = None):
380
- from ...extras import pl
381
-
382
- df = self.data_frame
383
-
384
- # Check if the timestamp column is already an integer
385
- if df[self.timestamp_column].dtype in [pl.Int32, pl.Int64]:
386
- # The timestamp column is already an integer, assuming it's in Unix format
387
- pass
388
- else:
389
- # Convert timestamp to Unix timestamp based on specified precision
390
- if self.precision in [None, 'ns']:
391
- df = df.with_columns(
392
- pl.col(self.timestamp_column).dt.epoch(time_unit="ns").alias(self.timestamp_column))
393
- elif self.precision == 'us':
394
- df = df.with_columns(
395
- pl.col(self.timestamp_column).dt.epoch(time_unit="us").alias(self.timestamp_column))
396
- elif self.precision == 'ms':
397
- df = df.with_columns(
398
- pl.col(self.timestamp_column).dt.epoch(time_unit="ms").alias(self.timestamp_column))
399
- elif self.precision == 's':
400
- df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="s").alias(self.timestamp_column))
401
- else:
402
- raise ValueError(f"Unsupported precision: {self.precision}")
403
-
404
- if chunk_idx is None:
405
- chunk = df
406
- else:
407
- logger.debug("Serialize chunk %s/%s ...", chunk_idx + 1, self.number_of_chunks)
408
- chunk = df[chunk_idx * self.chunk_size:(chunk_idx + 1) * self.chunk_size]
409
-
410
- # Apply the UDF to each row
411
- line_protocol_expr = chunk.apply(self.to_line_protocol, return_dtype=pl.Object)
412
-
413
- lp = line_protocol_expr['map'].to_list()
414
-
415
- return lp
416
-
417
-
418
295
  def data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, **kwargs):
419
296
  """
420
297
  Serialize DataFrame into LineProtocols.
@@ -430,20 +307,3 @@ def data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_W
430
307
  :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
431
308
  """ # noqa: E501
432
309
  return DataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
433
-
434
-
435
- def polars_data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, **kwargs):
436
- """
437
- Serialize DataFrame into LineProtocols.
438
-
439
- :param data_frame: Pandas DataFrame to serialize
440
- :param point_settings: Default Tags
441
- :param precision: The precision for the unix timestamps within the body line-protocol.
442
- :key data_frame_measurement_name: name of measurement for writing Pandas DataFrame
443
- :key data_frame_tag_columns: list of DataFrame columns which are tags, rest columns will be fields
444
- :key data_frame_timestamp_column: name of DataFrame column which contains a timestamp. The column can be defined as a :class:`~str` value
445
- formatted as `2018-10-26`, `2018-10-26 12:00`, `2018-10-26 12:00:00-05:00`
446
- or other formats and types supported by `pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_ - ``DataFrame``
447
- :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
448
- """ # noqa: E501
449
- return PolarsDataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
@@ -0,0 +1,160 @@
1
+ """
2
+ Functions for serialize Polars DataFrame.
3
+
4
+ Much of the code here is inspired by that in the aioinflux packet found here: https://github.com/gusutabopb/aioinflux
5
+ """
6
+
7
+ import logging
8
+ import math
9
+
10
+ from influxdb_client_3.write_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, DEFAULT_WRITE_PRECISION
11
+
12
+ logger = logging.getLogger('influxdb_client.client.write.polars_dataframe_serializer')
13
+
14
+
15
+ class PolarsDataframeSerializer:
16
+ """Serialize DataFrame into LineProtocols."""
17
+
18
+ def __init__(self, data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, chunk_size: int = None,
19
+ **kwargs) -> None:
20
+ """
21
+ Init serializer.
22
+
23
+ :param data_frame: Polars DataFrame to serialize
24
+ :param point_settings: Default Tags
25
+ :param precision: The precision for the unix timestamps within the body line-protocol.
26
+ :param chunk_size: The size of chunk for serializing into chunks.
27
+ :key data_frame_measurement_name: name of measurement for writing Polars DataFrame
28
+ :key data_frame_tag_columns: list of DataFrame columns which are tags, rest columns will be fields
29
+ :key data_frame_timestamp_column: name of DataFrame column which contains a timestamp.
30
+ :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column
31
+ """
32
+
33
+ self.data_frame = data_frame
34
+ self.point_settings = point_settings
35
+ self.precision = precision
36
+ self.chunk_size = chunk_size
37
+ self.measurement_name = kwargs.get("data_frame_measurement_name", "measurement")
38
+ self.tag_columns = kwargs.get("data_frame_tag_columns", [])
39
+ self.timestamp_column = kwargs.get("data_frame_timestamp_column", None)
40
+ self.timestamp_timezone = kwargs.get("data_frame_timestamp_timezone", None)
41
+
42
+ self.column_indices = {name: index for index, name in enumerate(data_frame.columns)}
43
+
44
+ if self.timestamp_column is None or self.timestamp_column not in self.column_indices:
45
+ raise ValueError(
46
+ f"Timestamp column {self.timestamp_column} not found in DataFrame. Please define a valid timestamp "
47
+ f"column.")
48
+
49
+ #
50
+ # prepare chunks
51
+ #
52
+ if chunk_size is not None:
53
+ self.number_of_chunks = int(math.ceil(len(data_frame) / float(chunk_size)))
54
+ self.chunk_size = chunk_size
55
+ else:
56
+ self.number_of_chunks = None
57
+
58
+ def escape_key(self, value):
59
+ return str(value).translate(_ESCAPE_KEY)
60
+
61
+ def escape_value(self, value):
62
+ return str(value).translate(_ESCAPE_STRING)
63
+
64
+ def to_line_protocol(self, row):
65
+ # Filter out None or empty values for tags
66
+ tags = ""
67
+
68
+ tags = ",".join(
69
+ f'{self.escape_key(col)}={self.escape_key(row[self.column_indices[col]])}'
70
+ for col in self.tag_columns
71
+ if row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
72
+ )
73
+
74
+ if self.point_settings.defaultTags:
75
+ default_tags = ",".join(
76
+ f'{self.escape_key(key)}={self.escape_key(value)}'
77
+ for key, value in self.point_settings.defaultTags.items()
78
+ )
79
+ # Ensure there's a comma between existing tags and default tags if both are present
80
+ if tags and default_tags:
81
+ tags += ","
82
+ tags += default_tags
83
+
84
+ # add escape symbols for special characters to tags
85
+
86
+ fields = ",".join(
87
+ f"{col}=\"{self.escape_value(row[self.column_indices[col]])}\"" if isinstance(row[self.column_indices[col]],
88
+ str)
89
+ else f"{col}={str(row[self.column_indices[col]]).lower()}" if isinstance(row[self.column_indices[col]],
90
+ bool) # Check for bool first
91
+ else f"{col}={row[self.column_indices[col]]}i" if isinstance(row[self.column_indices[col]], int)
92
+ else f"{col}={row[self.column_indices[col]]}"
93
+ for col in self.column_indices
94
+ if col not in self.tag_columns + [self.timestamp_column] and
95
+ row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
96
+ )
97
+
98
+ # Access the Unix timestamp
99
+ timestamp = row[self.column_indices[self.timestamp_column]]
100
+ if tags != "":
101
+ line_protocol = f"{self.measurement_name},{tags} {fields} {timestamp}"
102
+ else:
103
+ line_protocol = f"{self.measurement_name} {fields} {timestamp}"
104
+
105
+ return line_protocol
106
+
107
+ def serialize(self, chunk_idx: int = None):
108
+ import polars as pl
109
+
110
+ df = self.data_frame
111
+
112
+ # Check if the timestamp column is already an integer
113
+ if df[self.timestamp_column].dtype in [pl.Int32, pl.Int64]:
114
+ # The timestamp column is already an integer, assuming it's in Unix format
115
+ pass
116
+ else:
117
+ # Convert timestamp to Unix timestamp based on specified precision
118
+ if self.precision in [None, 'ns']:
119
+ df = df.with_columns(
120
+ pl.col(self.timestamp_column).dt.epoch(time_unit="ns").alias(self.timestamp_column))
121
+ elif self.precision == 'us':
122
+ df = df.with_columns(
123
+ pl.col(self.timestamp_column).dt.epoch(time_unit="us").alias(self.timestamp_column))
124
+ elif self.precision == 'ms':
125
+ df = df.with_columns(
126
+ pl.col(self.timestamp_column).dt.epoch(time_unit="ms").alias(self.timestamp_column))
127
+ elif self.precision == 's':
128
+ df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="s").alias(self.timestamp_column))
129
+ else:
130
+ raise ValueError(f"Unsupported precision: {self.precision}")
131
+
132
+ if chunk_idx is None:
133
+ chunk = df
134
+ else:
135
+ logger.debug("Serialize chunk %s/%s ...", chunk_idx + 1, self.number_of_chunks)
136
+ chunk = df[chunk_idx * self.chunk_size:(chunk_idx + 1) * self.chunk_size]
137
+
138
+ # Apply the UDF to each row
139
+ line_protocol_expr = chunk.map_rows(self.to_line_protocol, return_dtype=pl.Object)
140
+
141
+ lp = line_protocol_expr['map'].to_list()
142
+
143
+ return lp
144
+
145
+
146
+ def polars_data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, **kwargs):
147
+ """
148
+ Serialize DataFrame into LineProtocols.
149
+
150
+ :param data_frame: Pandas DataFrame to serialize
151
+ :param point_settings: Default Tags
152
+ :param precision: The precision for the unix timestamps within the body line-protocol.
153
+ :key data_frame_measurement_name: name of measurement for writing Pandas DataFrame
154
+ :key data_frame_tag_columns: list of DataFrame columns which are tags, rest columns will be fields
155
+ :key data_frame_timestamp_column: name of DataFrame column which contains a timestamp. The column can be defined as a :class:`~str` value
156
+ formatted as `2018-10-26`, `2018-10-26 12:00`, `2018-10-26 12:00:00-05:00`
157
+ or other formats and types supported by `pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_ - ``DataFrame``
158
+ :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
159
+ """ # noqa: E501
160
+ return PolarsDataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
@@ -19,8 +19,7 @@ from reactivex.subject import Subject
19
19
  from influxdb_client_3.write_client.domain import WritePrecision
20
20
  from influxdb_client_3.write_client.client._base import _BaseWriteApi, _HAS_DATACLASS
21
21
  from influxdb_client_3.write_client.client.util.helpers import get_org_query_param
22
- from influxdb_client_3.write_client.client.write.dataframe_serializer import (DataframeSerializer,
23
- PolarsDataframeSerializer)
22
+ from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer
24
23
  from influxdb_client_3.write_client.client.write.point import Point, DEFAULT_WRITE_PRECISION
25
24
  from influxdb_client_3.write_client.client.write.retry import WritesRetry
26
25
  from influxdb_client_3.write_client.rest import _UTF_8_encoding
@@ -462,6 +461,7 @@ You can use native asynchronous version of the client:
462
461
  precision, **kwargs)
463
462
 
464
463
  elif 'polars' in str(type(data)):
464
+ from influxdb_client_3.write_client.client.write.dataframe_serializer import PolarsDataframeSerializer
465
465
  serializer = PolarsDataframeSerializer(data,
466
466
  self._point_settings, precision,
467
467
  self._write_options.batch_size, **kwargs)
@@ -10,9 +10,4 @@ try:
10
10
  except ModuleNotFoundError as err:
11
11
  raise ImportError(f"`data_frame` requires numpy which couldn't be imported due: {err}")
12
12
 
13
- try:
14
- import polars as pl
15
- except ModuleNotFoundError as err:
16
- raise ImportError(f"`polars_frame` requires polars which couldn't be imported due: {err}")
17
-
18
- __all__ = ['pd', 'np', 'pl']
13
+ __all__ = ['pd', 'np']
@@ -1,12 +1,13 @@
1
1
  import unittest
2
2
  from unittest import mock
3
+ from urllib3 import response
3
4
 
4
5
  from influxdb_client_3.write_client._sync.api_client import ApiClient
5
6
  from influxdb_client_3.write_client.configuration import Configuration
7
+ from influxdb_client_3.write_client.client.exceptions import InfluxDBError
6
8
  from influxdb_client_3.write_client.service import WriteService
7
9
  from influxdb_client_3.version import VERSION
8
10
 
9
-
10
11
  _package = "influxdb3-python"
11
12
  _sentHeaders = {}
12
13
 
@@ -69,3 +70,38 @@ class ApiClientTests(unittest.TestCase):
69
70
  self.assertEqual("Bearer TEST_TOKEN", _sentHeaders["Authorization"])
70
71
  self.assertIsNotNone(_sentHeaders["User-Agent"])
71
72
  self.assertEqual(f"{_package}/{VERSION}", _sentHeaders["User-Agent"])
73
+
74
+ def _test_api_error(self, body):
75
+ conf = Configuration()
76
+ client = ApiClient(conf)
77
+ client.rest_client.pool_manager.request \
78
+ = mock.Mock(return_value=response.HTTPResponse(status=400,
79
+ reason='Bad Request',
80
+ body=body.encode()))
81
+ service = WriteService(client)
82
+ service.post_write("TEST_ORG", "TEST_BUCKET", "data,foo=bar val=3.14")
83
+
84
+ def test_api_error_cloud(self):
85
+ response_body = '{"message": "parsing failed for write_lp endpoint"}'
86
+ with self.assertRaises(InfluxDBError) as err:
87
+ self._test_api_error(response_body)
88
+ self.assertEqual('parsing failed for write_lp endpoint', err.exception.message)
89
+
90
+ def test_api_error_oss_without_detail(self):
91
+ response_body = '{"error": "parsing failed for write_lp endpoint"}'
92
+ with self.assertRaises(InfluxDBError) as err:
93
+ self._test_api_error(response_body)
94
+ self.assertEqual('parsing failed for write_lp endpoint', err.exception.message)
95
+
96
+ def test_api_error_oss_with_detail(self):
97
+ response_body = ('{"error":"parsing failed for write_lp endpoint","data":{"error_message":"invalid field value '
98
+ 'in line protocol for field \'val\' on line 1"}}')
99
+ with self.assertRaises(InfluxDBError) as err:
100
+ self._test_api_error(response_body)
101
+ self.assertEqual('invalid field value in line protocol for field \'val\' on line 1', err.exception.message)
102
+
103
+ def test_api_error_unknown(self):
104
+ response_body = '{"detail":"no info"}'
105
+ with self.assertRaises(InfluxDBError) as err:
106
+ self._test_api_error(response_body)
107
+ self.assertEqual(response_body, err.exception.message)