influxdb3-python 0.3.5__tar.gz → 0.4.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 (45) hide show
  1. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/PKG-INFO +11 -7
  2. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/README.md +7 -4
  3. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb3_python.egg-info/PKG-INFO +11 -7
  4. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb3_python.egg-info/SOURCES.txt +1 -0
  5. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/__init__.py +34 -27
  6. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/read_file.py +6 -4
  7. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/_sync/api_client.py +8 -8
  8. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/_base.py +10 -10
  9. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/influxdb_client.py +0 -8
  10. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +60 -52
  11. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/write_api.py +6 -6
  12. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/setup.py +4 -3
  13. influxdb3_python-0.4.0/tests/test_dataframe_serializer.py +45 -0
  14. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/tests/test_influxdb_client_3.py +4 -10
  15. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/LICENSE +0 -0
  16. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb3_python.egg-info/dependency_links.txt +0 -0
  17. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb3_python.egg-info/requires.txt +0 -0
  18. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb3_python.egg-info/top_level.txt +0 -0
  19. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/__init__.py +0 -0
  20. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
  21. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/_sync/rest.py +0 -0
  22. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/__init__.py +0 -0
  23. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/exceptions.py +0 -0
  24. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
  25. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
  26. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/util/date_utils.py +0 -0
  27. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
  28. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
  29. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
  30. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/warnings.py +0 -0
  31. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
  32. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/write/point.py +0 -0
  33. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/client/write/retry.py +0 -0
  34. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/configuration.py +0 -0
  35. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/domain/__init__.py +0 -0
  36. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/domain/write_precision.py +0 -0
  37. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/extras.py +0 -0
  38. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/rest.py +0 -0
  39. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/service/__init__.py +0 -0
  40. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/service/_base_service.py +0 -0
  41. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/service/signin_service.py +0 -0
  42. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/service/signout_service.py +0 -0
  43. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/service/write_service.py +0 -0
  44. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/influxdb_client_3/write_client/version.py +0 -0
  45. {influxdb3-python-0.3.5 → influxdb3_python-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.3.5
3
+ Version: 0.4.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
@@ -8,11 +8,12 @@ Author-email: contact@influxdata.com
8
8
  Classifier: Development Status :: 4 - Beta
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3.7
12
11
  Classifier: Programming Language :: Python :: 3.8
13
12
  Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
- Requires-Python: >=3.7
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.8
16
17
  Description-Content-Type: text/markdown
17
18
  License-File: LICENSE
18
19
  Requires-Dist: reactivex>=4.0.4
@@ -40,11 +41,14 @@ Requires-Dist: polars; extra == "dataframe"
40
41
  <a href="https://pypi.org/project/influxdb3-python/">
41
42
  <img src="https://img.shields.io/pypi/dm/influxdb3-python.svg" alt="PyPI downloads">
42
43
  </a>
43
- <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/pylint.yml">
44
- <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/pylint.yml/badge.svg" alt="Lint Code Base">
44
+ <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/codeql-analysis.yml">
45
+ <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/codeql-analysis.yml/badge.svg?branch=main" alt="CodeQL analysis">
45
46
  </a>
46
- <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/python-publish.yml">
47
- <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/python-publish.yml/badge.svg" alt="Lint Code Base">
47
+ <a href="https://dl.circleci.com/status-badge/redirect/gh/InfluxCommunity/influxdb3-python/tree/main">
48
+ <img src="https://dl.circleci.com/status-badge/img/gh/InfluxCommunity/influxdb3-python/tree/main.svg?style=svg" alt="CircleCI">
49
+ </a>
50
+ <a href="https://codecov.io/gh/InfluxCommunity/influxdb3-python">
51
+ <img src="https://codecov.io/gh/InfluxCommunity/influxdb3-python/branch/main/graph/badge.svg" alt="Code Cov"/>
48
52
  </a>
49
53
  <a href="https://influxcommunity.slack.com">
50
54
  <img src="https://img.shields.io/badge/slack-join_chat-white.svg?logo=slack&style=social" alt="Community Slack">
@@ -9,11 +9,14 @@
9
9
  <a href="https://pypi.org/project/influxdb3-python/">
10
10
  <img src="https://img.shields.io/pypi/dm/influxdb3-python.svg" alt="PyPI downloads">
11
11
  </a>
12
- <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/pylint.yml">
13
- <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/pylint.yml/badge.svg" alt="Lint Code Base">
12
+ <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/codeql-analysis.yml">
13
+ <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/codeql-analysis.yml/badge.svg?branch=main" alt="CodeQL analysis">
14
14
  </a>
15
- <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/python-publish.yml">
16
- <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/python-publish.yml/badge.svg" alt="Lint Code Base">
15
+ <a href="https://dl.circleci.com/status-badge/redirect/gh/InfluxCommunity/influxdb3-python/tree/main">
16
+ <img src="https://dl.circleci.com/status-badge/img/gh/InfluxCommunity/influxdb3-python/tree/main.svg?style=svg" alt="CircleCI">
17
+ </a>
18
+ <a href="https://codecov.io/gh/InfluxCommunity/influxdb3-python">
19
+ <img src="https://codecov.io/gh/InfluxCommunity/influxdb3-python/branch/main/graph/badge.svg" alt="Code Cov"/>
17
20
  </a>
18
21
  <a href="https://influxcommunity.slack.com">
19
22
  <img src="https://img.shields.io/badge/slack-join_chat-white.svg?logo=slack&style=social" alt="Community Slack">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.3.5
3
+ Version: 0.4.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
@@ -8,11 +8,12 @@ Author-email: contact@influxdata.com
8
8
  Classifier: Development Status :: 4 - Beta
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3.7
12
11
  Classifier: Programming Language :: Python :: 3.8
13
12
  Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
- Requires-Python: >=3.7
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.8
16
17
  Description-Content-Type: text/markdown
17
18
  License-File: LICENSE
18
19
  Requires-Dist: reactivex>=4.0.4
@@ -40,11 +41,14 @@ Requires-Dist: polars; extra == "dataframe"
40
41
  <a href="https://pypi.org/project/influxdb3-python/">
41
42
  <img src="https://img.shields.io/pypi/dm/influxdb3-python.svg" alt="PyPI downloads">
42
43
  </a>
43
- <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/pylint.yml">
44
- <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/pylint.yml/badge.svg" alt="Lint Code Base">
44
+ <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/codeql-analysis.yml">
45
+ <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/codeql-analysis.yml/badge.svg?branch=main" alt="CodeQL analysis">
45
46
  </a>
46
- <a href="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/python-publish.yml">
47
- <img src="https://github.com/InfluxCommunity/influxdb3-python/actions/workflows/python-publish.yml/badge.svg" alt="Lint Code Base">
47
+ <a href="https://dl.circleci.com/status-badge/redirect/gh/InfluxCommunity/influxdb3-python/tree/main">
48
+ <img src="https://dl.circleci.com/status-badge/img/gh/InfluxCommunity/influxdb3-python/tree/main.svg?style=svg" alt="CircleCI">
49
+ </a>
50
+ <a href="https://codecov.io/gh/InfluxCommunity/influxdb3-python">
51
+ <img src="https://codecov.io/gh/InfluxCommunity/influxdb3-python/branch/main/graph/badge.svg" alt="Code Cov"/>
48
52
  </a>
49
53
  <a href="https://influxcommunity.slack.com">
50
54
  <img src="https://img.shields.io/badge/slack-join_chat-white.svg?logo=slack&style=social" alt="Community Slack">
@@ -39,4 +39,5 @@ influxdb_client_3/write_client/service/_base_service.py
39
39
  influxdb_client_3/write_client/service/signin_service.py
40
40
  influxdb_client_3/write_client/service/signout_service.py
41
41
  influxdb_client_3/write_client/service/write_service.py
42
+ tests/test_dataframe_serializer.py
42
43
  tests/test_influxdb_client_3.py
@@ -1,20 +1,24 @@
1
- import urllib.parse, json
1
+ import json
2
+ import urllib.parse
3
+
2
4
  import pyarrow as pa
3
- from influxdb_client_3.write_client import InfluxDBClient as _InfluxDBClient, WriteOptions, Point
4
- from influxdb_client_3.write_client.client.write_api import WriteApi as _WriteApi, SYNCHRONOUS, ASYNCHRONOUS, PointSettings
5
- from influxdb_client_3.write_client.domain.write_precision import WritePrecision
6
- from influxdb_client_3.write_client.client.exceptions import InfluxDBError
7
5
  from pyarrow.flight import FlightClient, Ticket, FlightCallOptions
6
+
8
7
  from influxdb_client_3.read_file import UploadFile
9
- import urllib.parse
8
+ from influxdb_client_3.write_client import InfluxDBClient as _InfluxDBClient, WriteOptions, Point
9
+ from influxdb_client_3.write_client.client.exceptions import InfluxDBError
10
+ from influxdb_client_3.write_client.client.write_api import WriteApi as _WriteApi, SYNCHRONOUS, ASYNCHRONOUS, \
11
+ PointSettings
12
+ from influxdb_client_3.write_client.domain.write_precision import WritePrecision
13
+
10
14
  try:
11
15
  import polars as pl
16
+
12
17
  polars = True
13
18
  except ImportError:
14
19
  polars = False
15
20
 
16
21
 
17
-
18
22
  def write_client_options(**kwargs):
19
23
  """
20
24
  Function for providing additional arguments for the WriteApi client.
@@ -24,9 +28,11 @@ def write_client_options(**kwargs):
24
28
  """
25
29
  return kwargs
26
30
 
31
+
27
32
  def default_client_options(**kwargs):
28
33
  return kwargs
29
34
 
35
+
30
36
  def flight_client_options(**kwargs):
31
37
  """
32
38
  Function for providing additional arguments for the FlightClient.
@@ -36,6 +42,7 @@ def flight_client_options(**kwargs):
36
42
  """
37
43
  return kwargs
38
44
 
45
+
39
46
  def file_parser_options(**kwargs):
40
47
  """
41
48
  Function for providing additional arguments for the file parser.
@@ -43,7 +50,7 @@ def file_parser_options(**kwargs):
43
50
  :param kwargs: Additional arguments for the file parser.
44
51
  :return: dict with the arguments.
45
52
  """
46
- return kwargs
53
+ return kwargs
47
54
 
48
55
 
49
56
  def _deep_merge(target, source):
@@ -67,6 +74,7 @@ def _deep_merge(target, source):
67
74
  target = source
68
75
  return target
69
76
 
77
+
70
78
  class InfluxDBClient3:
71
79
  def __init__(
72
80
  self,
@@ -99,11 +107,12 @@ class InfluxDBClient3:
99
107
  self._org = org if org is not None else "default"
100
108
  self._database = database
101
109
  self._token = token
102
- self._write_client_options = write_client_options if write_client_options is not None else default_client_options(write_options=SYNCHRONOUS)
103
-
110
+ self._write_client_options = write_client_options if write_client_options is not None \
111
+ else default_client_options(write_options=SYNCHRONOUS)
112
+
104
113
  # Parse the host input
105
114
  parsed_url = urllib.parse.urlparse(host)
106
-
115
+
107
116
  # Determine the protocol (scheme), hostname, and port
108
117
  scheme = parsed_url.scheme if parsed_url.scheme else "https"
109
118
  hostname = parsed_url.hostname if parsed_url.hostname else host
@@ -118,10 +127,10 @@ class InfluxDBClient3:
118
127
  token=self._token,
119
128
  org=self._org,
120
129
  **kwargs)
121
-
130
+
122
131
  self._write_api = _WriteApi(influxdb_client=self._client, **self._write_client_options)
123
132
  self._flight_client_options = flight_client_options or {}
124
-
133
+
125
134
  if query_port_overwrite is not None:
126
135
  port = query_port_overwrite
127
136
  self._flight_client = FlightClient(f"grpc+tls://{hostname}:{port}", **self._flight_client_options)
@@ -134,7 +143,7 @@ class InfluxDBClient3:
134
143
  return defaults
135
144
  return _deep_merge(defaults, {key: value for key, value in custom.items()})
136
145
 
137
- def write(self, record=None, database=None ,**kwargs):
146
+ def write(self, record=None, database=None, **kwargs):
138
147
  """
139
148
  Write data to InfluxDB.
140
149
 
@@ -151,9 +160,9 @@ class InfluxDBClient3:
151
160
  self._write_api.write(bucket=database, record=record, **kwargs)
152
161
  except InfluxDBError as e:
153
162
  raise e
154
-
155
163
 
156
- def write_file(self, file, measurement_name=None, tag_columns=None, timestamp_column='time', database=None, file_parser_options=None ,**kwargs):
164
+ def write_file(self, file, measurement_name=None, tag_columns=None, timestamp_column='time', database=None,
165
+ file_parser_options=None, **kwargs):
157
166
  """
158
167
  Write data from a file to InfluxDB.
159
168
 
@@ -177,10 +186,10 @@ class InfluxDBClient3:
177
186
  try:
178
187
  table = UploadFile(file, file_parser_options).load_file()
179
188
  df = table.to_pandas() if isinstance(table, pa.Table) else table
180
- self._process_dataframe(df, measurement_name, tag_columns or [], timestamp_column, database=database, **kwargs)
189
+ self._process_dataframe(df, measurement_name, tag_columns or [], timestamp_column, database=database,
190
+ **kwargs)
181
191
  except Exception as e:
182
192
  raise e
183
-
184
193
 
185
194
  def _process_dataframe(self, df, measurement_name, tag_columns, timestamp_column, database, **kwargs):
186
195
  # This function is factored out for clarity.
@@ -204,9 +213,8 @@ class InfluxDBClient3:
204
213
  data_frame_measurement_name=measurement_name,
205
214
  data_frame_tag_columns=tag_columns,
206
215
  data_frame_timestamp_column=timestamp_column, **kwargs)
207
-
208
-
209
- def query(self, query, language="sql", mode="all", database=None,**kwargs ):
216
+
217
+ def query(self, query, language="sql", mode="all", database=None, **kwargs):
210
218
  """
211
219
  Query data from InfluxDB.
212
220
 
@@ -223,12 +231,10 @@ class InfluxDBClient3:
223
231
  """
224
232
  if mode == "polars" and polars is False:
225
233
  raise ImportError("Polars is not installed. Please install it with `pip install polars`.")
226
-
227
-
228
234
 
229
235
  if database is None:
230
236
  database = self._database
231
-
237
+
232
238
  try:
233
239
  # Create an authorization header
234
240
  optargs = {
@@ -237,7 +243,7 @@ class InfluxDBClient3:
237
243
  }
238
244
  opts = self._merge_options(optargs, kwargs)
239
245
  _options = FlightCallOptions(**opts)
240
-
246
+
241
247
  ticket_data = {"database": database, "sql_query": query, "query_type": language}
242
248
  ticket = Ticket(json.dumps(ticket_data).encode('utf-8'))
243
249
  flight_reader = self._flight_client.do_get(ticket, _options)
@@ -249,7 +255,7 @@ class InfluxDBClient3:
249
255
  "chunk": lambda: flight_reader,
250
256
  "reader": flight_reader.to_reader,
251
257
  "schema": lambda: flight_reader.schema
252
-
258
+
253
259
  }.get(mode, flight_reader.read_all)
254
260
 
255
261
  return mode_func() if callable(mode_func) else mode_func
@@ -267,7 +273,8 @@ class InfluxDBClient3:
267
273
 
268
274
  def __exit__(self, exc_type, exc_val, exc_tb):
269
275
  self.close()
270
-
276
+
277
+
271
278
  __all__ = [
272
279
  "InfluxDBClient3",
273
280
  "Point",
@@ -12,6 +12,7 @@ class UploadFile:
12
12
  """
13
13
  Class for uploading and reading different types of files.
14
14
  """
15
+
15
16
  def __init__(self, file, file_parser_options=None):
16
17
  """
17
18
  Initialize an UploadFile instance.
@@ -44,7 +45,7 @@ class UploadFile:
44
45
  else:
45
46
  raise ValueError("Unsupported file type")
46
47
 
47
- def load_feather(self, file ):
48
+ def load_feather(self, file):
48
49
  """
49
50
  Load a Feather file.
50
51
 
@@ -99,6 +100,7 @@ class UploadFile:
99
100
  try:
100
101
  import pandas as pd
101
102
  except ImportError:
102
- raise ImportError("Pandas is required for write_file(). Please install it using 'pip install pandas' or 'pip install influxdb3-python[pandas]'")
103
-
104
- return pd.read_json(file, **self._kwargs)
103
+ raise ImportError("Pandas is required for write_file(). Please install it using 'pip install pandas' or "
104
+ "'pip install influxdb3-python[pandas]'")
105
+
106
+ return pd.read_json(file, **self._kwargs)
@@ -267,7 +267,7 @@ class ApiClient(object):
267
267
  if data is None:
268
268
  return None
269
269
 
270
- if type(klass) == str:
270
+ if klass is str:
271
271
  if klass.startswith('list['):
272
272
  sub_kls = re.match(r'list\[(.*)\]', klass).group(1)
273
273
  return [self.__deserialize(sub_data, sub_kls)
@@ -348,13 +348,13 @@ class ApiClient(object):
348
348
  _preload_content, _request_timeout, urlopen_kw)
349
349
  else:
350
350
  thread = self.pool.apply_async(self.__call_api, (resource_path,
351
- method, path_params, query_params,
352
- header_params, body,
353
- post_params, files,
354
- response_type, auth_settings,
355
- _return_http_data_only,
356
- collection_formats,
357
- _preload_content, _request_timeout, urlopen_kw))
351
+ method, path_params, query_params,
352
+ header_params, body,
353
+ post_params, files,
354
+ response_type, auth_settings,
355
+ _return_http_data_only,
356
+ collection_formats,
357
+ _preload_content, _request_timeout, urlopen_kw))
358
358
  return thread
359
359
 
360
360
  def request(self, method, url, query_params=None, headers=None,
@@ -5,16 +5,13 @@ import base64
5
5
  import configparser
6
6
  import logging
7
7
  import os
8
- from datetime import datetime, timedelta
9
- from typing import List, Generator, Any, Union, Iterable, AsyncGenerator
10
-
11
- from urllib3 import HTTPResponse
8
+ from typing import Iterable
12
9
 
10
+ from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer, \
11
+ PolarsDataframeSerializer
13
12
  from influxdb_client_3.write_client.configuration import Configuration
14
- from influxdb_client_3.write_client.service.write_service import WriteService
15
-
16
- from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer
17
13
  from influxdb_client_3.write_client.rest import _UTF_8_encoding
14
+ from influxdb_client_3.write_client.service.write_service import WriteService
18
15
 
19
16
  try:
20
17
  import dataclasses
@@ -208,7 +205,6 @@ class _BaseClient(object):
208
205
  profilers=profilers, **kwargs)
209
206
 
210
207
 
211
-
212
208
  class _BaseWriteApi(object):
213
209
  def __init__(self, influxdb_client, point_settings=None):
214
210
  self._influxdb_client = influxdb_client
@@ -252,9 +248,14 @@ class _BaseWriteApi(object):
252
248
  elif isinstance(record, dict):
253
249
  self._serialize(Point.from_dict(record, write_precision=write_precision, **kwargs),
254
250
  write_precision, payload, **kwargs)
255
- elif 'DataFrame' in type(record).__name__:
251
+ elif 'polars' in str(type(record)):
252
+ serializer = PolarsDataframeSerializer(record, self._point_settings, write_precision, **kwargs)
253
+ self._serialize(serializer.serialize(), write_precision, payload, **kwargs)
254
+
255
+ elif 'pandas' in str(type(record)):
256
256
  serializer = DataframeSerializer(record, self._point_settings, write_precision, **kwargs)
257
257
  self._serialize(serializer.serialize(), write_precision, payload, **kwargs)
258
+
258
259
  elif hasattr(record, "_asdict"):
259
260
  # noinspection PyProtectedMember
260
261
  self._serialize(record._asdict(), write_precision, payload, **kwargs)
@@ -265,7 +266,6 @@ class _BaseWriteApi(object):
265
266
  self._serialize(item, write_precision, payload, **kwargs)
266
267
 
267
268
 
268
-
269
269
  class _Configuration(Configuration):
270
270
  def __init__(self):
271
271
  Configuration.__init__(self)
@@ -3,8 +3,6 @@
3
3
  from __future__ import absolute_import
4
4
 
5
5
  import logging
6
- import warnings
7
-
8
6
 
9
7
  from influxdb_client_3.write_client.client._base import _BaseClient
10
8
  from influxdb_client_3.write_client.client.write_api import WriteApi, WriteOptions, PointSettings
@@ -284,7 +282,6 @@ class InfluxDBClient(_BaseClient):
284
282
  """
285
283
  return WriteApi(influxdb_client=self, write_options=write_options, point_settings=point_settings, **kwargs)
286
284
 
287
-
288
285
  def close(self):
289
286
  """Shutdown the client."""
290
287
  self.__del__()
@@ -294,8 +291,3 @@ class InfluxDBClient(_BaseClient):
294
291
  if self.api_client:
295
292
  self.api_client.__del__()
296
293
  self.api_client = None
297
-
298
-
299
-
300
-
301
-
@@ -9,20 +9,22 @@ import math
9
9
  import re
10
10
 
11
11
  from influxdb_client_3.write_client.domain import WritePrecision
12
- from influxdb_client_3.write_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, _ESCAPE_MEASUREMENT, DEFAULT_WRITE_PRECISION
12
+ from influxdb_client_3.write_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, _ESCAPE_MEASUREMENT, \
13
+ DEFAULT_WRITE_PRECISION
13
14
 
14
15
  logger = logging.getLogger('influxdb_client.client.write.dataframe_serializer')
15
16
 
16
17
 
18
+ def _not_nan(x):
19
+ from ...extras import pd
20
+ return not pd.isna(x)
21
+
22
+
17
23
  def _itertuples(data_frame):
18
24
  cols = [data_frame.iloc[:, k] for k in range(len(data_frame.columns))]
19
25
  return zip(data_frame.index, *cols)
20
26
 
21
27
 
22
- def _not_nan(x):
23
- return x == x
24
-
25
-
26
28
  def _any_not_nan(p, indexes):
27
29
  return any(map(lambda x: _not_nan(p[x]), indexes))
28
30
 
@@ -175,7 +177,7 @@ class DataframeSerializer:
175
177
  # This column is a tag column.
176
178
  if null_columns.iloc[index]:
177
179
  key_value = f"""{{
178
- '' if {val_format} == '' or type({val_format}) == float and math.isnan({val_format}) else
180
+ '' if {val_format} == '' or pd.isna({val_format}) else
179
181
  f',{key_format}={{str({val_format}).translate(_ESCAPE_STRING)}}'
180
182
  }}"""
181
183
  else:
@@ -191,20 +193,22 @@ class DataframeSerializer:
191
193
  # It's important to omit it because when the first
192
194
  # field column has no nulls, we don't run the comma-removal
193
195
  # regexp substitution step.
196
+
194
197
  sep = '' if len(field_indexes) == 0 else ','
195
- if issubclass(value.type, np.integer):
196
- field_value = f"{sep}{key_format}={{{val_format}}}i"
197
- elif issubclass(value.type, np.bool_):
198
- field_value = f'{sep}{key_format}={{{val_format}}}'
199
- elif issubclass(value.type, np.floating):
198
+
199
+ if (issubclass(value.type, np.integer) or issubclass(value.type, np.floating) or
200
+ issubclass(value.type, np.bool_)):
201
+ suffix = 'i' if issubclass(value.type, np.integer) else ''
200
202
  if null_columns.iloc[index]:
201
- field_value = f"""{{"" if math.isnan({val_format}) else f"{sep}{key_format}={{{val_format}}}"}}"""
203
+ field_value = (
204
+ f"""{{"" if pd.isna({val_format}) else f"{sep}{key_format}={{{val_format}}}{suffix}"}}"""
205
+ )
202
206
  else:
203
- field_value = f'{sep}{key_format}={{{val_format}}}'
207
+ field_value = f'{sep}{key_format}={{{val_format}}}{suffix}'
204
208
  else:
205
209
  if null_columns.iloc[index]:
206
210
  field_value = f"""{{
207
- '' if type({val_format}) == float and math.isnan({val_format}) else
211
+ '' if pd.isna({val_format}) else
208
212
  f'{sep}{key_format}="{{str({val_format}).translate(_ESCAPE_STRING)}}"'
209
213
  }}"""
210
214
  else:
@@ -229,7 +233,7 @@ class DataframeSerializer:
229
233
  '_ESCAPE_KEY': _ESCAPE_KEY,
230
234
  '_ESCAPE_STRING': _ESCAPE_STRING,
231
235
  'keys': keys,
232
- 'math': math,
236
+ 'pd': pd,
233
237
  })
234
238
 
235
239
  for k, v in dict(data_frame.dtypes).items():
@@ -297,8 +301,7 @@ class PolarsDataframeSerializer:
297
301
  :key data_frame_timestamp_column: name of DataFrame column which contains a timestamp.
298
302
  :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column
299
303
  """
300
-
301
-
304
+
302
305
  self.data_frame = data_frame
303
306
  self.point_settings = point_settings
304
307
  self.precision = precision
@@ -311,7 +314,9 @@ class PolarsDataframeSerializer:
311
314
  self.column_indices = {name: index for index, name in enumerate(data_frame.columns)}
312
315
 
313
316
  if self.timestamp_column is None or self.timestamp_column not in self.column_indices:
314
- raise ValueError(f"Timestamp column {self.timestamp_column} not found in DataFrame. Please define a valid timestamp column.")
317
+ raise ValueError(
318
+ f"Timestamp column {self.timestamp_column} not found in DataFrame. Please define a valid timestamp "
319
+ f"column.")
315
320
 
316
321
  #
317
322
  # prepare chunks
@@ -321,43 +326,45 @@ class PolarsDataframeSerializer:
321
326
  self.chunk_size = chunk_size
322
327
  else:
323
328
  self.number_of_chunks = None
324
-
325
- def escape_value(self,value):
329
+
330
+ def escape_key(self, value):
326
331
  return str(value).translate(_ESCAPE_KEY)
327
332
 
328
-
333
+ def escape_value(self, value):
334
+ return str(value).translate(_ESCAPE_STRING)
335
+
329
336
  def to_line_protocol(self, row):
330
337
  # Filter out None or empty values for tags
331
338
  tags = ""
332
-
339
+
333
340
  tags = ",".join(
334
- f'{self.escape_value(col)}={self.escape_value(row[self.column_indices[col]])}'
341
+ f'{self.escape_key(col)}={self.escape_key(row[self.column_indices[col]])}'
335
342
  for col in self.tag_columns
336
343
  if row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
337
344
  )
338
345
 
339
346
  if self.point_settings.defaultTags:
340
347
  default_tags = ",".join(
341
- f'{self.escape_value(key)}={self.escape_value(value)}'
348
+ f'{self.escape_key(key)}={self.escape_key(value)}'
342
349
  for key, value in self.point_settings.defaultTags.items()
343
350
  )
344
351
  # Ensure there's a comma between existing tags and default tags if both are present
345
352
  if tags and default_tags:
346
353
  tags += ","
347
354
  tags += default_tags
348
-
349
-
350
355
 
351
-
352
356
  # add escape symbols for special characters to tags
353
357
 
354
358
  fields = ",".join(
355
- f"{col}=\"{row[self.column_indices[col]]}\"" if isinstance(row[self.column_indices[col]], str)
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
356
363
  else f"{col}={row[self.column_indices[col]]}i" if isinstance(row[self.column_indices[col]], int)
357
364
  else f"{col}={row[self.column_indices[col]]}"
358
365
  for col in self.column_indices
359
- if col not in self.tag_columns + [self.timestamp_column]
360
- and row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
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]] != ""
361
368
  )
362
369
 
363
370
  # Access the Unix timestamp
@@ -369,26 +376,31 @@ class PolarsDataframeSerializer:
369
376
 
370
377
  return line_protocol
371
378
 
372
-
373
379
  def serialize(self, chunk_idx: int = None):
374
380
  from ...extras import pl
375
-
381
+
376
382
  df = self.data_frame
377
383
 
378
- # Convert timestamp to unix timestamp
379
- if self.precision is None:
380
- df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="ns").alias(self.timestamp_column))
381
- elif self.precision == 'ns':
382
- df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="ns").alias(self.timestamp_column))
383
- elif self.precision == 'us':
384
- df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="us").alias(self.timestamp_column))
385
- elif self.precision == 'ms':
386
- df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="ms").alias(self.timestamp_column))
387
- elif self.precision == 's':
388
- df = df.with_columns(pl.col(self.timestamp_column).dt.epoch(time_unit="s").alias(self.timestamp_column))
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
389
388
  else:
390
- raise ValueError(f"Unsupported precision: {self.precision}")
391
-
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
+
392
404
  if chunk_idx is None:
393
405
  chunk = df
394
406
  else:
@@ -396,18 +408,13 @@ class PolarsDataframeSerializer:
396
408
  chunk = df[chunk_idx * self.chunk_size:(chunk_idx + 1) * self.chunk_size]
397
409
 
398
410
  # Apply the UDF to each row
399
- line_protocol_expr = chunk.apply(self.to_line_protocol,return_dtype=pl.Object)
411
+ line_protocol_expr = chunk.apply(self.to_line_protocol, return_dtype=pl.Object)
400
412
 
401
413
  lp = line_protocol_expr['map'].to_list()
402
414
 
403
-
404
415
  return lp
405
416
 
406
417
 
407
-
408
-
409
-
410
-
411
418
  def data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, **kwargs):
412
419
  """
413
420
  Serialize DataFrame into LineProtocols.
@@ -424,6 +431,7 @@ def data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_W
424
431
  """ # noqa: E501
425
432
  return DataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
426
433
 
434
+
427
435
  def polars_data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, **kwargs):
428
436
  """
429
437
  Serialize DataFrame into LineProtocols.
@@ -438,4 +446,4 @@ def polars_data_frame_to_list_of_points(data_frame, point_settings, precision=DE
438
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``
439
447
  :key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
440
448
  """ # noqa: E501
441
- return PolarsDataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
449
+ return PolarsDataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
@@ -19,14 +19,14 @@ 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, PolarsDataframeSerializer
22
+ from influxdb_client_3.write_client.client.write.dataframe_serializer import (DataframeSerializer,
23
+ PolarsDataframeSerializer)
23
24
  from influxdb_client_3.write_client.client.write.point import Point, DEFAULT_WRITE_PRECISION
24
25
  from influxdb_client_3.write_client.client.write.retry import WritesRetry
25
26
  from influxdb_client_3.write_client.rest import _UTF_8_encoding
26
27
 
27
28
  logger = logging.getLogger('influxdb_client_3.write_client.client.write_api')
28
29
 
29
-
30
30
  if _HAS_DATACLASS:
31
31
  import dataclasses
32
32
  from dataclasses import dataclass
@@ -460,10 +460,11 @@ You can use native asynchronous version of the client:
460
460
  elif isinstance(data, dict):
461
461
  self._write_batching(bucket, org, Point.from_dict(data, write_precision=precision, **kwargs),
462
462
  precision, **kwargs)
463
-
463
+
464
464
  elif 'polars' in str(type(data)):
465
- serializer = PolarsDataframeSerializer(data, self._point_settings, precision, self._write_options.batch_size,
466
- **kwargs)
465
+ serializer = PolarsDataframeSerializer(data,
466
+ self._point_settings, precision,
467
+ self._write_options.batch_size, **kwargs)
467
468
  for chunk_idx in range(serializer.number_of_chunks):
468
469
  self._write_batching(bucket, org,
469
470
  serializer.serialize(chunk_idx),
@@ -477,7 +478,6 @@ You can use native asynchronous version of the client:
477
478
  serializer.serialize(chunk_idx),
478
479
  precision, **kwargs)
479
480
 
480
-
481
481
  elif hasattr(data, "_asdict"):
482
482
  # noinspection PyProtectedMember
483
483
  self._write_batching(bucket, org, data._asdict(), precision, **kwargs)
@@ -47,14 +47,15 @@ setup(
47
47
  packages=find_packages(exclude=['tests', 'tests.*', 'examples', 'examples.*']),
48
48
  extras_require={'pandas': ['pandas'], 'polars': ['polars'], 'dataframe': ['pandas', 'polars']},
49
49
  install_requires=requires,
50
- python_requires='>=3.7',
50
+ python_requires='>=3.8',
51
51
  classifiers=[
52
52
  'Development Status :: 4 - Beta',
53
53
  'Intended Audience :: Developers',
54
54
  'License :: OSI Approved :: MIT License',
55
- 'Programming Language :: Python :: 3.7',
56
55
  'Programming Language :: Python :: 3.8',
57
56
  'Programming Language :: Python :: 3.9',
58
57
  'Programming Language :: Python :: 3.10',
58
+ 'Programming Language :: Python :: 3.11',
59
+ 'Programming Language :: Python :: 3.12',
59
60
  ]
60
- )
61
+ )
@@ -0,0 +1,45 @@
1
+ import unittest
2
+
3
+ from influxdb_client_3 import PointSettings
4
+ from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer
5
+ import pandas as pd
6
+
7
+
8
+ class TestDataFrameSerializer(unittest.TestCase):
9
+
10
+ def test_nullable_types(self):
11
+ df = pd.DataFrame({
12
+ "bool_nulls": [True, None, False],
13
+ "int_nulls": [None, 1, 2],
14
+ "float_nulls": [1.0, 2.0, None],
15
+ "str_nulls": ["a", "b", None],
16
+ })
17
+ df['bool_nulls_pd'] = df['bool_nulls'].astype(pd.BooleanDtype())
18
+ df['int_nulls_pd'] = df['int_nulls'].astype(pd.Int64Dtype())
19
+ df['float_nulls_pd'] = df['float_nulls'].astype(pd.Float64Dtype())
20
+ df['str_nulls_pd'] = df['str_nulls'].astype(pd.StringDtype())
21
+
22
+ df.index = pd.to_datetime(["2021-01-01", "2021-01-02", "2021-01-03"])
23
+
24
+ ps = PointSettings()
25
+
26
+ serializer = DataframeSerializer(df, ps, data_frame_measurement_name="test")
27
+
28
+ lines = serializer.serialize()
29
+
30
+ # make sure there are no `<NA>` values in the serialized lines
31
+ # first line should not have "int"
32
+ first_line = lines[0]
33
+ self.assertNotIn('<NA>', first_line)
34
+ self.assertNotIn('int_nulls', first_line)
35
+
36
+ # the second line should not have "bool"
37
+ second_line = lines[1]
38
+ self.assertNotIn('<NA>', second_line)
39
+ self.assertNotIn('bool_nulls', second_line)
40
+
41
+ # the third line should not have "str" or "float"
42
+ third_line = lines[2]
43
+ self.assertNotIn('<NA>', third_line)
44
+ self.assertNotIn('str_nulls', third_line)
45
+ self.assertNotIn('float_nulls', third_line)
@@ -1,7 +1,9 @@
1
1
  import unittest
2
- from unittest.mock import patch, Mock
2
+ from unittest.mock import patch
3
+
3
4
  from influxdb_client_3 import InfluxDBClient3
4
5
 
6
+
5
7
  class TestInfluxDBClient3(unittest.TestCase):
6
8
 
7
9
  @patch('influxdb_client_3._InfluxDBClient')
@@ -25,14 +27,6 @@ class TestInfluxDBClient3(unittest.TestCase):
25
27
  self.assertEqual(self.client._write_api, self.mock_write_api.return_value)
26
28
  self.assertEqual(self.client._flight_client, self.mock_flight_client.return_value)
27
29
 
28
- @patch('influxdb_client_3._WriteApi.write')
29
- def test_write(self, mock_write):
30
- record = "test_record"
31
- self.client.write(record=record)
32
- mock_write.assert_called_once_with(bucket=self.client._database, record=record)
33
-
34
- # Add more tests for other methods
35
-
36
30
 
37
31
  if __name__ == '__main__':
38
- unittest.main()
32
+ unittest.main()