influxdb3-python 0.4.0__tar.gz → 0.6.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 (53) hide show
  1. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/PKG-INFO +11 -7
  2. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/README.md +7 -6
  3. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb3_python.egg-info/PKG-INFO +11 -7
  4. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb3_python.egg-info/SOURCES.txt +10 -2
  5. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb3_python.egg-info/requires.txt +4 -0
  6. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/__init__.py +43 -57
  7. influxdb3_python-0.6.0/influxdb_client_3/py.typed +0 -0
  8. {influxdb3_python-0.4.0/influxdb_client_3/write_client → influxdb3_python-0.6.0/influxdb_client_3}/version.py +2 -1
  9. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/__init__.py +1 -1
  10. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/_sync/api_client.py +2 -2
  11. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/util/date_utils.py +5 -1
  12. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/write/point.py +1 -1
  13. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/setup.py +9 -2
  14. influxdb3_python-0.6.0/tests/test_api_client.py +71 -0
  15. influxdb3_python-0.6.0/tests/test_date_helper.py +21 -0
  16. influxdb3_python-0.6.0/tests/test_deep_merge.py +54 -0
  17. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/tests/test_influxdb_client_3.py +4 -4
  18. influxdb3_python-0.6.0/tests/test_influxdb_client_3_integration.py +45 -0
  19. influxdb3_python-0.6.0/tests/test_merge_options.py +28 -0
  20. influxdb3_python-0.6.0/tests/test_point.py +14 -0
  21. influxdb3_python-0.6.0/tests/test_query.py +152 -0
  22. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/LICENSE +0 -0
  23. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb3_python.egg-info/dependency_links.txt +0 -0
  24. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb3_python.egg-info/top_level.txt +0 -0
  25. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/read_file.py +0 -0
  26. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
  27. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/_sync/rest.py +0 -0
  28. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/__init__.py +0 -0
  29. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/_base.py +0 -0
  30. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/exceptions.py +0 -0
  31. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/influxdb_client.py +0 -0
  32. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
  33. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
  34. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
  35. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
  36. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
  37. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/warnings.py +0 -0
  38. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
  39. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +0 -0
  40. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/write/retry.py +0 -0
  41. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/client/write_api.py +0 -0
  42. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/configuration.py +0 -0
  43. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/domain/__init__.py +0 -0
  44. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/domain/write_precision.py +0 -0
  45. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/extras.py +0 -0
  46. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/rest.py +0 -0
  47. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/service/__init__.py +0 -0
  48. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/service/_base_service.py +0 -0
  49. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/service/signin_service.py +0 -0
  50. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/service/signout_service.py +0 -0
  51. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/influxdb_client_3/write_client/service/write_service.py +0 -0
  52. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/setup.cfg +0 -0
  53. {influxdb3_python-0.4.0 → influxdb3_python-0.6.0}/tests/test_dataframe_serializer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -29,6 +29,9 @@ Requires-Dist: polars; extra == "polars"
29
29
  Provides-Extra: dataframe
30
30
  Requires-Dist: pandas; extra == "dataframe"
31
31
  Requires-Dist: polars; extra == "dataframe"
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest; extra == "test"
34
+ Requires-Dist: pytest-cov; extra == "test"
32
35
 
33
36
  <p align="center">
34
37
  <img src="https://github.com/InfluxCommunity/influxdb3-python/blob/main/python-logo.png?raw=true" alt="Your Image" width="150px">
@@ -60,11 +63,12 @@ Requires-Dist: polars; extra == "dataframe"
60
63
 
61
64
  `influxdb_client_3` is a Python module that provides a simple and convenient way to interact with InfluxDB 3.0. This module supports both writing data to InfluxDB and querying data using the Flight client, which allows you to execute SQL and InfluxQL queries on InfluxDB 3.0.
62
65
 
66
+ We offer a ["Getting Started: InfluxDB 3.0 Python Client Library"](https://www.youtube.com/watch?v=tpdONTm1GC8) video that goes over how to use the library and goes over the examples.
63
67
  ## Dependencies
64
68
 
65
69
  - `pyarrow` (automatically installed)
66
70
  - `pandas` (optional)
67
-
71
+
68
72
 
69
73
  ## Installation
70
74
 
@@ -145,7 +149,7 @@ write_options = WriteOptions(batch_size=500,
145
149
  wco = write_client_options(success_callback=callback.success,
146
150
  error_callback=callback.error,
147
151
  retry_callback=callback.retry,
148
- WriteOptions=write_options
152
+ write_options=write_options
149
153
  )
150
154
 
151
155
  with InfluxDBClient3.InfluxDBClient3(
@@ -158,11 +162,11 @@ with InfluxDBClient3.InfluxDBClient3(
158
162
  client.write_file(
159
163
  file='./out.csv',
160
164
  timestamp_column='time', tag_columns=["provider", "machineID"])
161
-
165
+
162
166
  client.write_file(
163
167
  file='./out.json',
164
168
  timestamp_column='time', tag_columns=["provider", "machineID"], date_unit='ns' )
165
-
169
+
166
170
 
167
171
  ```
168
172
 
@@ -176,7 +180,7 @@ client._write_api.write(bucket="pokemon-codex", record=pd_df, data_frame_measure
176
180
  client._write_api.write(bucket="pokemon-codex", record=pl_df, data_frame_measurement_name='caught', data_frame_tag_columns=['trainer', 'id', 'num'], data_frame_timestamp_column='timestamp')
177
181
  ```
178
182
 
179
- ## Querying
183
+ ## Querying
180
184
 
181
185
  ### Querying with SQL
182
186
  ```python
@@ -228,4 +232,4 @@ table = client.query(
228
232
 
229
233
  print(table.to_pandas())
230
234
  ```
231
- You may also include your own root certificate via this manor aswell.
235
+ You may also include your own root certificate via this manor aswell.
@@ -28,11 +28,12 @@
28
28
 
29
29
  `influxdb_client_3` is a Python module that provides a simple and convenient way to interact with InfluxDB 3.0. This module supports both writing data to InfluxDB and querying data using the Flight client, which allows you to execute SQL and InfluxQL queries on InfluxDB 3.0.
30
30
 
31
+ We offer a ["Getting Started: InfluxDB 3.0 Python Client Library"](https://www.youtube.com/watch?v=tpdONTm1GC8) video that goes over how to use the library and goes over the examples.
31
32
  ## Dependencies
32
33
 
33
34
  - `pyarrow` (automatically installed)
34
35
  - `pandas` (optional)
35
-
36
+
36
37
 
37
38
  ## Installation
38
39
 
@@ -113,7 +114,7 @@ write_options = WriteOptions(batch_size=500,
113
114
  wco = write_client_options(success_callback=callback.success,
114
115
  error_callback=callback.error,
115
116
  retry_callback=callback.retry,
116
- WriteOptions=write_options
117
+ write_options=write_options
117
118
  )
118
119
 
119
120
  with InfluxDBClient3.InfluxDBClient3(
@@ -126,11 +127,11 @@ with InfluxDBClient3.InfluxDBClient3(
126
127
  client.write_file(
127
128
  file='./out.csv',
128
129
  timestamp_column='time', tag_columns=["provider", "machineID"])
129
-
130
+
130
131
  client.write_file(
131
132
  file='./out.json',
132
133
  timestamp_column='time', tag_columns=["provider", "machineID"], date_unit='ns' )
133
-
134
+
134
135
 
135
136
  ```
136
137
 
@@ -144,7 +145,7 @@ client._write_api.write(bucket="pokemon-codex", record=pd_df, data_frame_measure
144
145
  client._write_api.write(bucket="pokemon-codex", record=pl_df, data_frame_measurement_name='caught', data_frame_tag_columns=['trainer', 'id', 'num'], data_frame_timestamp_column='timestamp')
145
146
  ```
146
147
 
147
- ## Querying
148
+ ## Querying
148
149
 
149
150
  ### Querying with SQL
150
151
  ```python
@@ -196,4 +197,4 @@ table = client.query(
196
197
 
197
198
  print(table.to_pandas())
198
199
  ```
199
- You may also include your own root certificate via this manor aswell.
200
+ You may also include your own root certificate via this manor aswell.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -29,6 +29,9 @@ Requires-Dist: polars; extra == "polars"
29
29
  Provides-Extra: dataframe
30
30
  Requires-Dist: pandas; extra == "dataframe"
31
31
  Requires-Dist: polars; extra == "dataframe"
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest; extra == "test"
34
+ Requires-Dist: pytest-cov; extra == "test"
32
35
 
33
36
  <p align="center">
34
37
  <img src="https://github.com/InfluxCommunity/influxdb3-python/blob/main/python-logo.png?raw=true" alt="Your Image" width="150px">
@@ -60,11 +63,12 @@ Requires-Dist: polars; extra == "dataframe"
60
63
 
61
64
  `influxdb_client_3` is a Python module that provides a simple and convenient way to interact with InfluxDB 3.0. This module supports both writing data to InfluxDB and querying data using the Flight client, which allows you to execute SQL and InfluxQL queries on InfluxDB 3.0.
62
65
 
66
+ We offer a ["Getting Started: InfluxDB 3.0 Python Client Library"](https://www.youtube.com/watch?v=tpdONTm1GC8) video that goes over how to use the library and goes over the examples.
63
67
  ## Dependencies
64
68
 
65
69
  - `pyarrow` (automatically installed)
66
70
  - `pandas` (optional)
67
-
71
+
68
72
 
69
73
  ## Installation
70
74
 
@@ -145,7 +149,7 @@ write_options = WriteOptions(batch_size=500,
145
149
  wco = write_client_options(success_callback=callback.success,
146
150
  error_callback=callback.error,
147
151
  retry_callback=callback.retry,
148
- WriteOptions=write_options
152
+ write_options=write_options
149
153
  )
150
154
 
151
155
  with InfluxDBClient3.InfluxDBClient3(
@@ -158,11 +162,11 @@ with InfluxDBClient3.InfluxDBClient3(
158
162
  client.write_file(
159
163
  file='./out.csv',
160
164
  timestamp_column='time', tag_columns=["provider", "machineID"])
161
-
165
+
162
166
  client.write_file(
163
167
  file='./out.json',
164
168
  timestamp_column='time', tag_columns=["provider", "machineID"], date_unit='ns' )
165
-
169
+
166
170
 
167
171
  ```
168
172
 
@@ -176,7 +180,7 @@ client._write_api.write(bucket="pokemon-codex", record=pd_df, data_frame_measure
176
180
  client._write_api.write(bucket="pokemon-codex", record=pl_df, data_frame_measurement_name='caught', data_frame_tag_columns=['trainer', 'id', 'num'], data_frame_timestamp_column='timestamp')
177
181
  ```
178
182
 
179
- ## Querying
183
+ ## Querying
180
184
 
181
185
  ### Querying with SQL
182
186
  ```python
@@ -228,4 +232,4 @@ table = client.query(
228
232
 
229
233
  print(table.to_pandas())
230
234
  ```
231
- You may also include your own root certificate via this manor aswell.
235
+ You may also include your own root certificate via this manor aswell.
@@ -7,12 +7,13 @@ influxdb3_python.egg-info/dependency_links.txt
7
7
  influxdb3_python.egg-info/requires.txt
8
8
  influxdb3_python.egg-info/top_level.txt
9
9
  influxdb_client_3/__init__.py
10
+ influxdb_client_3/py.typed
10
11
  influxdb_client_3/read_file.py
12
+ influxdb_client_3/version.py
11
13
  influxdb_client_3/write_client/__init__.py
12
14
  influxdb_client_3/write_client/configuration.py
13
15
  influxdb_client_3/write_client/extras.py
14
16
  influxdb_client_3/write_client/rest.py
15
- influxdb_client_3/write_client/version.py
16
17
  influxdb_client_3/write_client/_sync/__init__.py
17
18
  influxdb_client_3/write_client/_sync/api_client.py
18
19
  influxdb_client_3/write_client/_sync/rest.py
@@ -39,5 +40,12 @@ influxdb_client_3/write_client/service/_base_service.py
39
40
  influxdb_client_3/write_client/service/signin_service.py
40
41
  influxdb_client_3/write_client/service/signout_service.py
41
42
  influxdb_client_3/write_client/service/write_service.py
43
+ tests/test_api_client.py
42
44
  tests/test_dataframe_serializer.py
43
- tests/test_influxdb_client_3.py
45
+ tests/test_date_helper.py
46
+ tests/test_deep_merge.py
47
+ tests/test_influxdb_client_3.py
48
+ tests/test_influxdb_client_3_integration.py
49
+ tests/test_merge_options.py
50
+ tests/test_point.py
51
+ tests/test_query.py
@@ -14,3 +14,7 @@ pandas
14
14
 
15
15
  [polars]
16
16
  polars
17
+
18
+ [test]
19
+ pytest
20
+ pytest-cov
@@ -1,9 +1,9 @@
1
- import json
2
1
  import urllib.parse
3
2
 
4
3
  import pyarrow as pa
5
- from pyarrow.flight import FlightClient, Ticket, FlightCallOptions
4
+ import importlib.util
6
5
 
6
+ from influxdb_client_3.query.query_api import QueryApi as _QueryApi
7
7
  from influxdb_client_3.read_file import UploadFile
8
8
  from influxdb_client_3.write_client import InfluxDBClient as _InfluxDBClient, WriteOptions, Point
9
9
  from influxdb_client_3.write_client.client.exceptions import InfluxDBError
@@ -11,12 +11,7 @@ from influxdb_client_3.write_client.client.write_api import WriteApi as _WriteAp
11
11
  PointSettings
12
12
  from influxdb_client_3.write_client.domain.write_precision import WritePrecision
13
13
 
14
- try:
15
- import polars as pl
16
-
17
- polars = True
18
- except ImportError:
19
- polars = False
14
+ polars = importlib.util.find_spec("polars") is not None
20
15
 
21
16
 
22
17
  def write_client_options(**kwargs):
@@ -69,12 +64,26 @@ def _deep_merge(target, source):
69
64
  elif isinstance(target, list) and isinstance(source, list):
70
65
  # If both target and source are lists, concatenate them
71
66
  target.extend(source)
72
- else:
67
+ elif source is not None:
73
68
  # For other types, simply replace the target with the source
74
69
  target = source
75
70
  return target
76
71
 
77
72
 
73
+ def _merge_options(defaults, exclude_keys=None, custom=None):
74
+ """
75
+ Merge default option arguments with custom (user-provided) arguments,
76
+ excluding specific keys defined in exclude_keys.
77
+ """
78
+ if custom is None or len(custom) == 0:
79
+ return defaults
80
+
81
+ if exclude_keys is None:
82
+ exclude_keys = []
83
+
84
+ return _deep_merge(defaults, {key: value for key, value in custom.items() if key not in exclude_keys})
85
+
86
+
78
87
  class InfluxDBClient3:
79
88
  def __init__(
80
89
  self,
@@ -129,19 +138,15 @@ class InfluxDBClient3:
129
138
  **kwargs)
130
139
 
131
140
  self._write_api = _WriteApi(influxdb_client=self._client, **self._write_client_options)
132
- self._flight_client_options = flight_client_options or {}
133
141
 
134
142
  if query_port_overwrite is not None:
135
143
  port = query_port_overwrite
136
- self._flight_client = FlightClient(f"grpc+tls://{hostname}:{port}", **self._flight_client_options)
137
-
138
- def _merge_options(self, defaults, custom={}):
139
- """
140
- Merge default option arguments with custom (user-provided) arguments.
141
- """
142
- if len(custom) == 0:
143
- return defaults
144
- return _deep_merge(defaults, {key: value for key, value in custom.items()})
144
+ if scheme == 'https':
145
+ connection_string = f"grpc+tls://{hostname}:{port}"
146
+ else:
147
+ connection_string = f"grpc+tcp://{hostname}:{port}"
148
+ self._query_api = _QueryApi(connection_string=connection_string, token=token,
149
+ flight_client_options=flight_client_options)
145
150
 
146
151
  def write(self, record=None, database=None, **kwargs):
147
152
  """
@@ -214,20 +219,23 @@ class InfluxDBClient3:
214
219
  data_frame_tag_columns=tag_columns,
215
220
  data_frame_timestamp_column=timestamp_column, **kwargs)
216
221
 
217
- def query(self, query, language="sql", mode="all", database=None, **kwargs):
218
- """
219
- Query data from InfluxDB.
220
-
221
- :param query: The query string.
222
- :type query: str
223
- :param language: The query language; "sql" or "influxql" (default is "sql").
224
- :type language: str
225
- :param mode: The mode of fetching data (all, pandas, chunk, reader, schema).
226
- :type mode: str
222
+ def query(self, query: str, language: str = "sql", mode: str = "all", database: str = None, **kwargs):
223
+ """Query data from InfluxDB.
224
+
225
+ If you want to use query parameters, you can pass them as kwargs:
226
+
227
+ >>> client.query("select * from cpu where host=$host", query_parameters={"host": "server01"})
228
+
229
+ :param query: The query to execute on the database.
230
+ :param language: The query language to use. It should be one of "influxql" or "sql". Defaults to "sql".
231
+ :param mode: The mode to use for the query. It should be one of "all", "pandas", "polars", "chunk",
232
+ "reader" or "schema". Defaults to "all".
227
233
  :param database: The database to query from. If not provided, uses the database provided during initialization.
228
- :type database: str
229
- :param kwargs: FlightClientCallOptions for the query.
230
- :return: The queried data.
234
+ :param kwargs: Additional arguments to pass to the ``FlightCallOptions headers``. For example, it can be used to
235
+ set up per request headers.
236
+ :keyword query_parameters: The query parameters to use in the query.
237
+ It should be a ``dictionary`` of key-value pairs.
238
+ :return: The query result in the specified mode.
231
239
  """
232
240
  if mode == "polars" and polars is False:
233
241
  raise ImportError("Polars is not installed. Please install it with `pip install polars`.")
@@ -236,36 +244,14 @@ class InfluxDBClient3:
236
244
  database = self._database
237
245
 
238
246
  try:
239
- # Create an authorization header
240
- optargs = {
241
- "headers": [(b"authorization", f"Bearer {self._token}".encode('utf-8'))],
242
- "timeout": 300
243
- }
244
- opts = self._merge_options(optargs, kwargs)
245
- _options = FlightCallOptions(**opts)
246
-
247
- ticket_data = {"database": database, "sql_query": query, "query_type": language}
248
- ticket = Ticket(json.dumps(ticket_data).encode('utf-8'))
249
- flight_reader = self._flight_client.do_get(ticket, _options)
250
-
251
- mode_func = {
252
- "all": flight_reader.read_all,
253
- "pandas": flight_reader.read_pandas,
254
- "polars": lambda: pl.from_arrow(flight_reader.read_all()),
255
- "chunk": lambda: flight_reader,
256
- "reader": flight_reader.to_reader,
257
- "schema": lambda: flight_reader.schema
258
-
259
- }.get(mode, flight_reader.read_all)
260
-
261
- return mode_func() if callable(mode_func) else mode_func
262
- except Exception as e:
247
+ return self._query_api.query(query=query, language=language, mode=mode, database=database, **kwargs)
248
+ except InfluxDBError as e:
263
249
  raise e
264
250
 
265
251
  def close(self):
266
252
  """Close the client and clean up resources."""
267
253
  self._write_api.close()
268
- self._flight_client.close()
254
+ self._query_api.close()
269
255
  self._client.close()
270
256
 
271
257
  def __enter__(self):
File without changes
@@ -1,3 +1,4 @@
1
1
  """Version of the Client that is used in User-Agent header."""
2
2
 
3
- VERSION = '1.38.0dev0'
3
+ VERSION = '0.6.0'
4
+ USER_AGENT = f'influxdb3-python/{VERSION}'
@@ -27,5 +27,5 @@ from influxdb_client_3.write_client.service.signout_service import SignoutServic
27
27
  from influxdb_client_3.write_client.domain.write_precision import WritePrecision
28
28
 
29
29
  from influxdb_client_3.write_client.configuration import Configuration
30
- from influxdb_client_3.write_client.version import VERSION
30
+ from influxdb_client_3.version import VERSION
31
31
  __version__ = VERSION
@@ -76,8 +76,8 @@ class ApiClient(object):
76
76
  self.default_headers[header_name] = header_value
77
77
  self.cookie = cookie
78
78
  # Set default User-Agent.
79
- from influxdb_client_3.write_client.version import VERSION
80
- self.user_agent = f'influxdb-client-python/{VERSION}'
79
+ from influxdb_client_3.version import USER_AGENT
80
+ self.user_agent = USER_AGENT
81
81
 
82
82
  def __del__(self):
83
83
  """Dispose pools."""
@@ -2,6 +2,7 @@
2
2
  import datetime
3
3
  import threading
4
4
  from datetime import timezone as tz
5
+ from sys import version_info
5
6
 
6
7
  from dateutil import parser
7
8
 
@@ -90,7 +91,10 @@ def get_date_helper() -> DateHelper:
90
91
  import ciso8601
91
92
  _date_helper.parse_date = ciso8601.parse_datetime
92
93
  except ModuleNotFoundError:
93
- _date_helper.parse_date = parser.parse
94
+ if (version_info.major, version_info.minor) >= (3, 11):
95
+ _date_helper.parse_date = datetime.datetime.fromisoformat
96
+ else:
97
+ _date_helper.parse_date = parser.parse
94
98
  date_helper = _date_helper
95
99
 
96
100
  return date_helper
@@ -10,7 +10,7 @@ from numbers import Integral
10
10
  from influxdb_client_3.write_client.client.util.date_utils import get_date_helper
11
11
  from influxdb_client_3.write_client.domain.write_precision import WritePrecision
12
12
 
13
- EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=timezone.utc)
13
+ EPOCH = datetime.fromtimestamp(0, tz=timezone.utc)
14
14
 
15
15
  DEFAULT_WRITE_PRECISION = WritePrecision.NS
16
16
 
@@ -2,7 +2,6 @@ from setuptools import setup, find_packages
2
2
  import os
3
3
  import re
4
4
 
5
-
6
5
  requires = [
7
6
  'reactivex >= 4.0.4',
8
7
  'certifi >= 14.05.14',
@@ -15,6 +14,7 @@ requires = [
15
14
  with open("./README.md", "r", encoding="utf-8") as fh:
16
15
  long_description = fh.read()
17
16
 
17
+
18
18
  def get_version_from_github_ref():
19
19
  github_ref = os.environ.get("GITHUB_REF")
20
20
  if not github_ref:
@@ -26,6 +26,7 @@ def get_version_from_github_ref():
26
26
 
27
27
  return match.group(1)
28
28
 
29
+
29
30
  def get_version():
30
31
  # If running in GitHub Actions, get version from GITHUB_REF
31
32
  version = get_version_from_github_ref()
@@ -35,6 +36,7 @@ def get_version():
35
36
  # Fallback to a default version if not in GitHub Actions
36
37
  return "v0.0.0"
37
38
 
39
+
38
40
  setup(
39
41
  name='influxdb3-python',
40
42
  version=get_version(),
@@ -45,7 +47,12 @@ setup(
45
47
  author_email='contact@influxdata.com',
46
48
  url='https://github.com/InfluxCommunity/influxdb3-python',
47
49
  packages=find_packages(exclude=['tests', 'tests.*', 'examples', 'examples.*']),
48
- extras_require={'pandas': ['pandas'], 'polars': ['polars'], 'dataframe': ['pandas', 'polars']},
50
+ extras_require={
51
+ 'pandas': ['pandas'],
52
+ 'polars': ['polars'],
53
+ 'dataframe': ['pandas', 'polars'],
54
+ 'test': ['pytest', 'pytest-cov']
55
+ },
49
56
  install_requires=requires,
50
57
  python_requires='>=3.8',
51
58
  classifiers=[
@@ -0,0 +1,71 @@
1
+ import unittest
2
+ from unittest import mock
3
+
4
+ from influxdb_client_3.write_client._sync.api_client import ApiClient
5
+ from influxdb_client_3.write_client.configuration import Configuration
6
+ from influxdb_client_3.write_client.service import WriteService
7
+ from influxdb_client_3.version import VERSION
8
+
9
+
10
+ _package = "influxdb3-python"
11
+ _sentHeaders = {}
12
+
13
+
14
+ def mock_rest_request(method,
15
+ url,
16
+ query_params=None,
17
+ headers=None,
18
+ body=None,
19
+ post_params=None,
20
+ _preload_content=True,
21
+ _request_timeout=None,
22
+ **urlopen_kw):
23
+ class MockResponse:
24
+ def __init__(self, data, status_code):
25
+ self.data = data
26
+ self.status_code = status_code
27
+
28
+ def data(self):
29
+ return self.data
30
+
31
+ global _sentHeaders
32
+ _sentHeaders = headers
33
+
34
+ return MockResponse(None, 200)
35
+
36
+
37
+ class ApiClientTests(unittest.TestCase):
38
+
39
+ def test_default_headers(self):
40
+ global _package
41
+ conf = Configuration()
42
+ client = ApiClient(conf,
43
+ header_name="Authorization",
44
+ header_value="Bearer TEST_TOKEN")
45
+ self.assertIsNotNone(client.default_headers["User-Agent"])
46
+ self.assertIsNotNone(client.default_headers["Authorization"])
47
+ self.assertEqual(f"{_package}/{VERSION}", client.default_headers["User-Agent"])
48
+ self.assertEqual("Bearer TEST_TOKEN", client.default_headers["Authorization"])
49
+
50
+ @mock.patch("influxdb_client_3.write_client._sync.rest.RESTClientObject.request",
51
+ side_effect=mock_rest_request)
52
+ def test_call_api(self, mock_post):
53
+ global _package
54
+ global _sentHeaders
55
+ _sentHeaders = {}
56
+
57
+ conf = Configuration()
58
+ client = ApiClient(conf,
59
+ header_name="Authorization",
60
+ header_value="Bearer TEST_TOKEN")
61
+ service = WriteService(client)
62
+ service.post_write("TEST_ORG", "TEST_BUCKET", "data,foo=bar val=3.14")
63
+ self.assertEqual(4, len(_sentHeaders.keys()))
64
+ self.assertIsNotNone(_sentHeaders["Accept"])
65
+ self.assertEqual("application/json", _sentHeaders["Accept"])
66
+ self.assertIsNotNone(_sentHeaders["Content-Type"])
67
+ self.assertEqual("text/plain", _sentHeaders["Content-Type"])
68
+ self.assertIsNotNone(_sentHeaders["Authorization"])
69
+ self.assertEqual("Bearer TEST_TOKEN", _sentHeaders["Authorization"])
70
+ self.assertIsNotNone(_sentHeaders["User-Agent"])
71
+ self.assertEqual(f"{_package}/{VERSION}", _sentHeaders["User-Agent"])
@@ -0,0 +1,21 @@
1
+ import unittest
2
+ from datetime import datetime, timezone
3
+
4
+ from dateutil import tz
5
+
6
+ from influxdb_client_3.write_client.client.util.date_utils import DateHelper, get_date_helper
7
+
8
+
9
+ class TestDateHelper(unittest.TestCase):
10
+
11
+ def test_to_utc(self):
12
+ date = get_date_helper().to_utc(datetime(2021, 4, 29, 20, 30, 10, 0))
13
+ self.assertEqual(datetime(2021, 4, 29, 20, 30, 10, 0, timezone.utc), date)
14
+
15
+ def test_to_utc_different_timezone(self):
16
+ date = DateHelper(timezone=tz.gettz('ETC/GMT+2')).to_utc(datetime(2021, 4, 29, 20, 30, 10, 0))
17
+ self.assertEqual(datetime(2021, 4, 29, 22, 30, 10, 0, timezone.utc), date)
18
+
19
+ def test_parse(self):
20
+ date = get_date_helper().parse_date("2021-03-20T15:59:10.607352Z")
21
+ self.assertEqual(datetime(2021, 3, 20, 15, 59, 10, 607352, timezone.utc), date)
@@ -0,0 +1,54 @@
1
+ import unittest
2
+
3
+ import influxdb_client_3
4
+
5
+
6
+ class TestDeepMerge(unittest.TestCase):
7
+
8
+ def test_deep_merge_dicts_with_no_overlap(self):
9
+ target = {"a": 1, "b": 2}
10
+ source = {"c": 3, "d": 4}
11
+ result = influxdb_client_3._deep_merge(target, source)
12
+ self.assertEqual(result, {"a": 1, "b": 2, "c": 3, "d": 4})
13
+
14
+ def test_deep_merge_dicts_with_overlap(self):
15
+ target = {"a": 1, "b": 2}
16
+ source = {"b": 3, "c": 4}
17
+ result = influxdb_client_3._deep_merge(target, source)
18
+ self.assertEqual(result, {"a": 1, "b": 3, "c": 4})
19
+
20
+ def test_deep_merge_nested_dicts(self):
21
+ target = {"a": {"b": 1}}
22
+ source = {"a": {"c": 2}}
23
+ result = influxdb_client_3._deep_merge(target, source)
24
+ self.assertEqual(result, {"a": {"b": 1, "c": 2}})
25
+
26
+ def test_deep_merge_lists(self):
27
+ target = [1, 2]
28
+ source = [3, 4]
29
+ result = influxdb_client_3._deep_merge(target, source)
30
+ self.assertEqual(result, [1, 2, 3, 4])
31
+
32
+ def test_deep_merge_non_overlapping_types(self):
33
+ target = {"a": 1}
34
+ source = [2, 3]
35
+ result = influxdb_client_3._deep_merge(target, source)
36
+ self.assertEqual(result, [2, 3])
37
+
38
+ def test_deep_merge_none_to_flight(self):
39
+ target = {
40
+ "headers": [(b"authorization", "Bearer xyz".encode('utf-8'))],
41
+ "timeout": 300
42
+ }
43
+ source = None
44
+ result = influxdb_client_3._deep_merge(target, source)
45
+ self.assertEqual(result, target)
46
+
47
+ def test_deep_merge_empty_to_flight(self):
48
+ target = {
49
+ "headers": [(b"authorization", "Bearer xyz".encode('utf-8'))],
50
+ "timeout": 300
51
+ }
52
+ source = {}
53
+ result = influxdb_client_3._deep_merge(target, source)
54
+ self.assertEqual(result, target)
@@ -8,11 +8,11 @@ class TestInfluxDBClient3(unittest.TestCase):
8
8
 
9
9
  @patch('influxdb_client_3._InfluxDBClient')
10
10
  @patch('influxdb_client_3._WriteApi')
11
- @patch('influxdb_client_3.FlightClient')
12
- def setUp(self, mock_flight_client, mock_write_api, mock_influx_db_client):
11
+ @patch('influxdb_client_3._QueryApi')
12
+ def setUp(self, mock_query_api, mock_write_api, mock_influx_db_client):
13
13
  self.mock_influx_db_client = mock_influx_db_client
14
14
  self.mock_write_api = mock_write_api
15
- self.mock_flight_client = mock_flight_client
15
+ self.mock_query_api = mock_query_api
16
16
  self.client = InfluxDBClient3(
17
17
  host="localhost",
18
18
  org="my_org",
@@ -25,7 +25,7 @@ class TestInfluxDBClient3(unittest.TestCase):
25
25
  self.assertEqual(self.client._database, "my_db")
26
26
  self.assertEqual(self.client._client, self.mock_influx_db_client.return_value)
27
27
  self.assertEqual(self.client._write_api, self.mock_write_api.return_value)
28
- self.assertEqual(self.client._flight_client, self.mock_flight_client.return_value)
28
+ self.assertEqual(self.client._query_api, self.mock_query_api.return_value)
29
29
 
30
30
 
31
31
  if __name__ == '__main__':
@@ -0,0 +1,45 @@
1
+ import os
2
+ import time
3
+ import unittest
4
+
5
+ import pytest
6
+
7
+ from influxdb_client_3 import InfluxDBClient3
8
+
9
+
10
+ @pytest.mark.integration
11
+ @pytest.mark.skipif(
12
+ not all(
13
+ [
14
+ os.getenv('TESTING_INFLUXDB_URL'),
15
+ os.getenv('TESTING_INFLUXDB_TOKEN'),
16
+ os.getenv('TESTING_INFLUXDB_DATABASE'),
17
+ ]
18
+ ),
19
+ reason="Integration test environment variables not set.",
20
+ )
21
+ class TestInfluxDBClient3Integration(unittest.TestCase):
22
+
23
+ def setUp(self):
24
+ host = os.getenv('TESTING_INFLUXDB_URL')
25
+ token = os.getenv('TESTING_INFLUXDB_TOKEN')
26
+ database = os.getenv('TESTING_INFLUXDB_DATABASE')
27
+
28
+ self.client = InfluxDBClient3(host=host, database=database, token=token)
29
+
30
+ def tearDown(self):
31
+ if self.client:
32
+ self.client.close()
33
+
34
+ def test_write_and_query(self):
35
+ test_id = time.time_ns()
36
+ self.client.write(f"integration_test_python,type=used value=123.0,test_id={test_id}i")
37
+
38
+ sql = 'SELECT * FROM integration_test_python where type=$type and test_id=$test_id'
39
+
40
+ df = self.client.query(sql, mode="pandas", query_parameters={'type': 'used', 'test_id': test_id})
41
+
42
+ self.assertIsNotNone(df)
43
+ self.assertEqual(1, len(df))
44
+ self.assertEqual(test_id, df['test_id'][0])
45
+ self.assertEqual(123.0, df['value'][0])
@@ -0,0 +1,28 @@
1
+ import unittest
2
+
3
+ import influxdb_client_3
4
+
5
+
6
+ class TestMergeOptions(unittest.TestCase):
7
+
8
+ def test_merge_with_empty_custom(self):
9
+ defaults = {"a": 1, "b": 2}
10
+ result = influxdb_client_3._merge_options(defaults, custom={})
11
+ self.assertEqual(result, defaults)
12
+
13
+ def test_merge_with_none_custom(self):
14
+ defaults = {"a": 1, "b": 2}
15
+ result = influxdb_client_3._merge_options(defaults, custom=None)
16
+ self.assertEqual(result, defaults)
17
+
18
+ def test_merge_with_no_excluded_keys(self):
19
+ defaults = {"a": 1, "b": 2}
20
+ custom = {"b": 3, "c": 4}
21
+ result = influxdb_client_3._merge_options(defaults, custom=custom)
22
+ self.assertEqual(result, {"a": 1, "b": 3, "c": 4})
23
+
24
+ def test_merge_with_excluded_keys(self):
25
+ defaults = {"a": 1, "b": 2}
26
+ custom = {"b": 3, "c": 4}
27
+ result = influxdb_client_3._merge_options(defaults, exclude_keys=["b"], custom=custom)
28
+ self.assertEqual(result, {"a": 1, "b": 2, "c": 4})
@@ -0,0 +1,14 @@
1
+ import datetime
2
+ import unittest
3
+
4
+ from influxdb_client_3.write_client.client.write.point import EPOCH, Point
5
+
6
+
7
+ class TestPoint(unittest.TestCase):
8
+
9
+ def test_epoch(self):
10
+ self.assertEqual(EPOCH, datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc))
11
+
12
+ def test_point(self):
13
+ point = Point.measurement("h2o").tag("location", "europe").field("level", 2.2).time(1_000_000)
14
+ self.assertEqual('h2o,location=europe level=2.2 1000000', point.to_line_protocol())
@@ -0,0 +1,152 @@
1
+ import unittest
2
+ import struct
3
+ from unittest.mock import Mock, ANY
4
+
5
+ from pyarrow import (
6
+ array,
7
+ Table
8
+ )
9
+
10
+ from pyarrow.flight import (
11
+ FlightServerBase,
12
+ FlightUnauthenticatedError,
13
+ GeneratorStream,
14
+ ServerMiddleware,
15
+ ServerMiddlewareFactory,
16
+ ServerAuthHandler,
17
+ Ticket
18
+ )
19
+
20
+ from influxdb_client_3 import InfluxDBClient3
21
+ from influxdb_client_3.version import USER_AGENT
22
+
23
+
24
+ def case_insensitive_header_lookup(headers, lkey):
25
+ """Lookup the value of a given key in the given headers.
26
+ The lkey is case-insensitive.
27
+ """
28
+ for key in headers:
29
+ if key.lower() == lkey.lower():
30
+ return headers.get(key)
31
+
32
+
33
+ class NoopAuthHandler(ServerAuthHandler):
34
+ """A no-op auth handler - as seen in pyarrow tests"""
35
+
36
+ def authenticate(self, outgoing, incoming):
37
+ """Do nothing"""
38
+
39
+ def is_valid(self, token):
40
+ """
41
+ Return an empty string
42
+ N.B. Returning None causes Type error
43
+ :param token:
44
+ :return:
45
+ """
46
+ return ""
47
+
48
+
49
+ _req_headers = {}
50
+
51
+
52
+ class HeaderCheckServerMiddlewareFactory(ServerMiddlewareFactory):
53
+ """Factory to create HeaderCheckServerMiddleware and check header values"""
54
+ def start_call(self, info, headers):
55
+ auth_header = case_insensitive_header_lookup(headers, "Authorization")
56
+ values = auth_header[0].split(' ')
57
+ if values[0] != 'Bearer':
58
+ raise FlightUnauthenticatedError("Token required")
59
+ global _req_headers
60
+ _req_headers = headers
61
+ return HeaderCheckServerMiddleware(values[1])
62
+
63
+
64
+ class HeaderCheckServerMiddleware(ServerMiddleware):
65
+ """
66
+ Middleware needed to catch request headers via factory
67
+ N.B. As found in pyarrow tests
68
+ """
69
+ def __init__(self, token, *args, **kwargs):
70
+ super().__init__(*args, **kwargs)
71
+ self.token = token
72
+
73
+ def sending_headers(self):
74
+ return {'authorization': 'Bearer ' + self.token}
75
+
76
+
77
+ class HeaderCheckFlightServer(FlightServerBase):
78
+ """Mock server handle gRPC do_get calls"""
79
+ def do_get(self, context, ticket):
80
+ """Return something to avoid needless errors"""
81
+ data = [
82
+ array([b"Vltava", struct.pack('<i', 105), b"FM"])
83
+ ]
84
+ table = Table.from_arrays(data, names=['a'])
85
+ return GeneratorStream(
86
+ table.schema,
87
+ self.number_batches(table),
88
+ options={})
89
+
90
+ @staticmethod
91
+ def number_batches(table):
92
+ for idx, batch in enumerate(table.to_batches()):
93
+ buf = struct.pack('<i', idx)
94
+ yield batch, buf
95
+
96
+
97
+ def test_influx_default_query_headers():
98
+ with HeaderCheckFlightServer(
99
+ auth_handler=NoopAuthHandler(),
100
+ middleware={"check": HeaderCheckServerMiddlewareFactory()}) as server:
101
+ global _req_headers
102
+ _req_headers = {}
103
+ client = InfluxDBClient3(
104
+ host=f'http://localhost:{server.port}',
105
+ org='test_org',
106
+ databse='test_db',
107
+ token='TEST_TOKEN'
108
+ )
109
+ client.query('SELECT * FROM test')
110
+ assert len(_req_headers) > 0
111
+ assert _req_headers['authorization'][0] == "Bearer TEST_TOKEN"
112
+ assert _req_headers['user-agent'][0].find(USER_AGENT) > -1
113
+ _req_headers = {}
114
+
115
+
116
+ class TestQuery(unittest.TestCase):
117
+
118
+ def setUp(self):
119
+ self.client = InfluxDBClient3(
120
+ host="localhost",
121
+ org="my_org",
122
+ database="my_db",
123
+ token="my_token"
124
+ )
125
+
126
+ def test_query_without_parameters(self):
127
+ mock_do_get = Mock()
128
+ self.client._query_api._do_get = mock_do_get
129
+
130
+ self.client.query('SELECT * FROM measurement')
131
+
132
+ expected_ticket = Ticket(
133
+ '{"database": "my_db", '
134
+ '"sql_query": "SELECT * FROM measurement", '
135
+ '"query_type": "sql"}'.encode('utf-8')
136
+ )
137
+
138
+ mock_do_get.assert_called_once_with(expected_ticket, ANY)
139
+
140
+ def test_query_with_parameters(self):
141
+ mock_do_get = Mock()
142
+ self.client._query_api._do_get = mock_do_get
143
+
144
+ self.client.query('SELECT * FROM measurement WHERE time > $time', query_parameters={"time": "2021-01-01"})
145
+
146
+ expected_ticket = Ticket(
147
+ '{"database": "my_db", '
148
+ '"sql_query": "SELECT * FROM measurement WHERE time > $time", '
149
+ '"query_type": "sql", "params": {"time": "2021-01-01"}}'.encode('utf-8')
150
+ )
151
+
152
+ mock_do_get.assert_called_once_with(expected_ticket, ANY)