influxdb3-python 0.5.0__tar.gz → 0.6.1__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 (56) hide show
  1. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/PKG-INFO +1 -1
  2. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb3_python.egg-info/PKG-INFO +1 -1
  3. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb3_python.egg-info/SOURCES.txt +6 -1
  4. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/__init__.py +12 -47
  5. influxdb3_python-0.6.1/influxdb_client_3/query/__init__.py +1 -0
  6. influxdb3_python-0.6.1/influxdb_client_3/query/query_api.py +104 -0
  7. {influxdb3_python-0.5.0/influxdb_client_3/write_client → influxdb3_python-0.6.1/influxdb_client_3}/version.py +2 -1
  8. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/__init__.py +1 -1
  9. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/_sync/api_client.py +2 -2
  10. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/util/date_utils.py +5 -1
  11. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/write/point.py +1 -1
  12. influxdb3_python-0.6.1/tests/test_api_client.py +71 -0
  13. influxdb3_python-0.6.1/tests/test_date_helper.py +21 -0
  14. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/tests/test_influxdb_client_3.py +4 -4
  15. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/tests/test_influxdb_client_3_integration.py +1 -0
  16. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/tests/test_merge_options.py +1 -1
  17. influxdb3_python-0.6.1/tests/test_point.py +14 -0
  18. influxdb3_python-0.6.1/tests/test_query.py +152 -0
  19. influxdb3_python-0.5.0/tests/test_query.py +0 -53
  20. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/LICENSE +0 -0
  21. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/README.md +0 -0
  22. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb3_python.egg-info/dependency_links.txt +0 -0
  23. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb3_python.egg-info/requires.txt +0 -0
  24. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb3_python.egg-info/top_level.txt +0 -0
  25. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/py.typed +0 -0
  26. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/read_file.py +0 -0
  27. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
  28. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/_sync/rest.py +0 -0
  29. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/__init__.py +0 -0
  30. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/_base.py +0 -0
  31. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/exceptions.py +0 -0
  32. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/influxdb_client.py +0 -0
  33. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
  34. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
  35. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
  36. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
  37. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
  38. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/warnings.py +0 -0
  39. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
  40. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +0 -0
  41. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/write/retry.py +0 -0
  42. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/client/write_api.py +0 -0
  43. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/configuration.py +0 -0
  44. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/domain/__init__.py +0 -0
  45. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/domain/write_precision.py +0 -0
  46. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/extras.py +0 -0
  47. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/rest.py +0 -0
  48. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/service/__init__.py +0 -0
  49. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/service/_base_service.py +0 -0
  50. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/service/signin_service.py +0 -0
  51. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/service/signout_service.py +0 -0
  52. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/influxdb_client_3/write_client/service/write_service.py +0 -0
  53. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/setup.cfg +0 -0
  54. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/setup.py +0 -0
  55. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/tests/test_dataframe_serializer.py +0 -0
  56. {influxdb3_python-0.5.0 → influxdb3_python-0.6.1}/tests/test_deep_merge.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: Community Python client for InfluxDB 3.0
5
5
  Home-page: https://github.com/InfluxCommunity/influxdb3-python
6
6
  Author: InfluxData
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: influxdb3-python
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: Community Python client for InfluxDB 3.0
5
5
  Home-page: https://github.com/InfluxCommunity/influxdb3-python
6
6
  Author: InfluxData
@@ -9,11 +9,13 @@ influxdb3_python.egg-info/top_level.txt
9
9
  influxdb_client_3/__init__.py
10
10
  influxdb_client_3/py.typed
11
11
  influxdb_client_3/read_file.py
12
+ influxdb_client_3/version.py
13
+ influxdb_client_3/query/__init__.py
14
+ influxdb_client_3/query/query_api.py
12
15
  influxdb_client_3/write_client/__init__.py
13
16
  influxdb_client_3/write_client/configuration.py
14
17
  influxdb_client_3/write_client/extras.py
15
18
  influxdb_client_3/write_client/rest.py
16
- influxdb_client_3/write_client/version.py
17
19
  influxdb_client_3/write_client/_sync/__init__.py
18
20
  influxdb_client_3/write_client/_sync/api_client.py
19
21
  influxdb_client_3/write_client/_sync/rest.py
@@ -40,9 +42,12 @@ influxdb_client_3/write_client/service/_base_service.py
40
42
  influxdb_client_3/write_client/service/signin_service.py
41
43
  influxdb_client_3/write_client/service/signout_service.py
42
44
  influxdb_client_3/write_client/service/write_service.py
45
+ tests/test_api_client.py
43
46
  tests/test_dataframe_serializer.py
47
+ tests/test_date_helper.py
44
48
  tests/test_deep_merge.py
45
49
  tests/test_influxdb_client_3.py
46
50
  tests/test_influxdb_client_3_integration.py
47
51
  tests/test_merge_options.py
52
+ tests/test_point.py
48
53
  tests/test_query.py
@@ -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):
@@ -143,11 +138,15 @@ class InfluxDBClient3:
143
138
  **kwargs)
144
139
 
145
140
  self._write_api = _WriteApi(influxdb_client=self._client, **self._write_client_options)
146
- self._flight_client_options = flight_client_options or {}
147
141
 
148
142
  if query_port_overwrite is not None:
149
143
  port = query_port_overwrite
150
- self._flight_client = FlightClient(f"grpc+tls://{hostname}:{port}", **self._flight_client_options)
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)
151
150
 
152
151
  def write(self, record=None, database=None, **kwargs):
153
152
  """
@@ -245,48 +244,14 @@ class InfluxDBClient3:
245
244
  database = self._database
246
245
 
247
246
  try:
248
- # Create an authorization header
249
- optargs = {
250
- "headers": [(b"authorization", f"Bearer {self._token}".encode('utf-8'))],
251
- "timeout": 300
252
- }
253
- opts = _merge_options(optargs, exclude_keys=['query_parameters'], custom=kwargs)
254
- _options = FlightCallOptions(**opts)
255
-
256
- #
257
- # Ticket data
258
- #
259
- ticket_data = {
260
- "database": database,
261
- "sql_query": query,
262
- "query_type": language
263
- }
264
- # add query parameters
265
- query_parameters = kwargs.get("query_parameters", None)
266
- if query_parameters:
267
- ticket_data["params"] = query_parameters
268
-
269
- ticket = Ticket(json.dumps(ticket_data).encode('utf-8'))
270
- flight_reader = self._flight_client.do_get(ticket, _options)
271
-
272
- mode_func = {
273
- "all": flight_reader.read_all,
274
- "pandas": flight_reader.read_pandas,
275
- "polars": lambda: pl.from_arrow(flight_reader.read_all()),
276
- "chunk": lambda: flight_reader,
277
- "reader": flight_reader.to_reader,
278
- "schema": lambda: flight_reader.schema
279
-
280
- }.get(mode, flight_reader.read_all)
281
-
282
- return mode_func() if callable(mode_func) else mode_func
283
- except Exception as e:
247
+ return self._query_api.query(query=query, language=language, mode=mode, database=database, **kwargs)
248
+ except InfluxDBError as e:
284
249
  raise e
285
250
 
286
251
  def close(self):
287
252
  """Close the client and clean up resources."""
288
253
  self._write_api.close()
289
- self._flight_client.close()
254
+ self._query_api.close()
290
255
  self._client.close()
291
256
 
292
257
  def __enter__(self):
@@ -0,0 +1 @@
1
+ """Package for query module."""
@@ -0,0 +1,104 @@
1
+ """Query data in InfluxDB 3."""
2
+
3
+ # coding: utf-8
4
+ import json
5
+
6
+ from pyarrow.flight import FlightClient, Ticket, FlightCallOptions, FlightStreamReader
7
+ from influxdb_client_3.version import USER_AGENT
8
+
9
+
10
+ class QueryApi(object):
11
+ """
12
+ Implementation for '/api/v2/query' endpoint.
13
+
14
+ Example:
15
+ .. code-block:: python
16
+
17
+ from influxdb_client import InfluxDBClient
18
+
19
+
20
+ # Initialize instance of QueryApi
21
+ with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
22
+ query_api = client.query_api()
23
+ """
24
+
25
+ def __init__(self,
26
+ connection_string,
27
+ token,
28
+ flight_client_options) -> None:
29
+ """
30
+ Initialize defaults.
31
+
32
+ :param connection_string: Flight/gRPC connection string
33
+ :param token: access token
34
+ :param flight_client_options: Flight client options
35
+ """
36
+ self._token = token
37
+ self._flight_client_options = flight_client_options or {}
38
+ self._flight_client_options["generic_options"] = [
39
+ ("grpc.secondary_user_agent", USER_AGENT)
40
+ ]
41
+ self._flight_client = FlightClient(connection_string, **self._flight_client_options)
42
+
43
+ def query(self, query: str, language: str, mode: str, database: str, **kwargs):
44
+ """Query data from InfluxDB.
45
+
46
+ :param query: The query to execute on the database.
47
+ :param language: The query language.
48
+ :param mode: The mode to use for the query.
49
+ It should be one of "all", "pandas", "polars", "chunk", "reader" or "schema".
50
+ :param database: The database to query from.
51
+ :param kwargs: Additional arguments to pass to the ``FlightCallOptions headers``.
52
+ For example, it can be used to set up per request headers.
53
+ :keyword query_parameters: The query parameters to use in the query.
54
+ It should be a ``dictionary`` of key-value pairs.
55
+ :return: The query result in the specified mode.
56
+ """
57
+ from influxdb_client_3 import polars as has_polars, _merge_options as merge_options
58
+ try:
59
+ # Create an authorization header
60
+ optargs = {
61
+ "headers": [(b"authorization", f"Bearer {self._token}".encode('utf-8'))],
62
+ "timeout": 300
63
+ }
64
+ opts = merge_options(optargs, exclude_keys=['query_parameters'], custom=kwargs)
65
+ _options = FlightCallOptions(**opts)
66
+
67
+ #
68
+ # Ticket data
69
+ #
70
+ ticket_data = {
71
+ "database": database,
72
+ "sql_query": query,
73
+ "query_type": language
74
+ }
75
+ # add query parameters
76
+ query_parameters = kwargs.get("query_parameters", None)
77
+ if query_parameters:
78
+ ticket_data["params"] = query_parameters
79
+
80
+ ticket = Ticket(json.dumps(ticket_data).encode('utf-8'))
81
+ flight_reader = self._do_get(ticket, _options)
82
+
83
+ mode_funcs = {
84
+ "all": flight_reader.read_all,
85
+ "pandas": flight_reader.read_pandas,
86
+ "chunk": lambda: flight_reader,
87
+ "reader": flight_reader.to_reader,
88
+ "schema": lambda: flight_reader.schema
89
+ }
90
+ if has_polars:
91
+ import polars as pl
92
+ mode_funcs["polars"] = lambda: pl.from_arrow(flight_reader.read_all())
93
+ mode_func = mode_funcs.get(mode, flight_reader.read_all)
94
+
95
+ return mode_func() if callable(mode_func) else mode_func
96
+ except Exception as e:
97
+ raise e
98
+
99
+ def _do_get(self, ticket: Ticket, options: FlightCallOptions = None) -> FlightStreamReader:
100
+ return self._flight_client.do_get(ticket, options)
101
+
102
+ def close(self):
103
+ """Close the Flight client."""
104
+ self._flight_client.close()
@@ -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.1'
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
 
@@ -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)
@@ -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__':
@@ -39,6 +39,7 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
39
39
 
40
40
  df = self.client.query(sql, mode="pandas", query_parameters={'type': 'used', 'test_id': test_id})
41
41
 
42
+ self.assertIsNotNone(df)
42
43
  self.assertEqual(1, len(df))
43
44
  self.assertEqual(test_id, df['test_id'][0])
44
45
  self.assertEqual(123.0, df['value'][0])
@@ -3,7 +3,7 @@ import unittest
3
3
  import influxdb_client_3
4
4
 
5
5
 
6
- class MergeOptionsTests(unittest.TestCase):
6
+ class TestMergeOptions(unittest.TestCase):
7
7
 
8
8
  def test_merge_with_empty_custom(self):
9
9
  defaults = {"a": 1, "b": 2}
@@ -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)
@@ -1,53 +0,0 @@
1
- import unittest
2
- from unittest.mock import Mock, patch, ANY
3
-
4
- from pyarrow.flight import Ticket
5
-
6
- from influxdb_client_3 import InfluxDBClient3
7
-
8
-
9
- class QueryTests(unittest.TestCase):
10
-
11
- @patch('influxdb_client_3._InfluxDBClient')
12
- @patch('influxdb_client_3._WriteApi')
13
- @patch('influxdb_client_3.FlightClient')
14
- def setUp(self, mock_flight_client, mock_write_api, mock_influx_db_client):
15
- self.mock_influx_db_client = mock_influx_db_client
16
- self.mock_write_api = mock_write_api
17
- self.mock_flight_client = mock_flight_client
18
- self.client = InfluxDBClient3(
19
- host="localhost",
20
- org="my_org",
21
- database="my_db",
22
- token="my_token"
23
- )
24
- self.client._flight_client = mock_flight_client
25
- self.client._write_api = mock_write_api
26
-
27
- def test_query_without_parameters(self):
28
- mock_do_get = Mock()
29
- self.client._flight_client.do_get = mock_do_get
30
-
31
- self.client.query('SELECT * FROM measurement')
32
-
33
- expected_ticket = Ticket(
34
- '{"database": "my_db", '
35
- '"sql_query": "SELECT * FROM measurement", '
36
- '"query_type": "sql"}'.encode('utf-8')
37
- )
38
-
39
- mock_do_get.assert_called_once_with(expected_ticket, ANY)
40
-
41
- def test_query_with_parameters(self):
42
- mock_do_get = Mock()
43
- self.client._flight_client.do_get = mock_do_get
44
-
45
- self.client.query('SELECT * FROM measurement WHERE time > $time', query_parameters={"time": "2021-01-01"})
46
-
47
- expected_ticket = Ticket(
48
- '{"database": "my_db", '
49
- '"sql_query": "SELECT * FROM measurement WHERE time > $time", '
50
- '"query_type": "sql", "params": {"time": "2021-01-01"}}'.encode('utf-8')
51
- )
52
-
53
- mock_do_get.assert_called_once_with(expected_ticket, ANY)