influxdb3-python 0.10.0__tar.gz → 0.12.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 (71) hide show
  1. influxdb3_python-0.12.0/Examples/basic_ssl_example.py +104 -0
  2. influxdb3_python-0.12.0/Examples/query_async.py +87 -0
  3. {influxdb3_python-0.10.0/influxdb3_python.egg-info → influxdb3_python-0.12.0}/PKG-INFO +13 -2
  4. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0/influxdb3_python.egg-info}/PKG-INFO +13 -2
  5. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb3_python.egg-info/SOURCES.txt +2 -0
  6. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/__init__.py +46 -3
  7. influxdb3_python-0.12.0/influxdb_client_3/query/query_api.py +252 -0
  8. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/version.py +1 -1
  9. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_influxdb_client_3.py +26 -0
  10. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_influxdb_client_3_integration.py +94 -4
  11. influxdb3_python-0.12.0/tests/test_query.py +432 -0
  12. influxdb3_python-0.10.0/influxdb_client_3/query/query_api.py +0 -104
  13. influxdb3_python-0.10.0/tests/test_query.py +0 -152
  14. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/__init__.py +0 -0
  15. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/batching_example.py +0 -0
  16. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/cloud_dedicated_query.py +0 -0
  17. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/cloud_dedicated_write.py +0 -0
  18. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/config.py +0 -0
  19. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/flight_options_example.py +0 -0
  20. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/handle_http_error.py +0 -0
  21. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/pandas_write.py +0 -0
  22. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/Examples/query_type.py +0 -0
  23. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/LICENSE +0 -0
  24. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/README.md +0 -0
  25. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb3_python.egg-info/dependency_links.txt +0 -0
  26. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb3_python.egg-info/requires.txt +0 -0
  27. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb3_python.egg-info/top_level.txt +0 -0
  28. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/py.typed +0 -0
  29. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/query/__init__.py +0 -0
  30. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/read_file.py +0 -0
  31. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/__init__.py +0 -0
  32. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
  33. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/_sync/api_client.py +0 -0
  34. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/_sync/rest.py +0 -0
  35. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/__init__.py +0 -0
  36. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/_base.py +0 -0
  37. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/exceptions.py +0 -0
  38. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/influxdb_client.py +0 -0
  39. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
  40. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
  41. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/util/date_utils.py +0 -0
  42. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
  43. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
  44. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
  45. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/warnings.py +0 -0
  46. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
  47. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +0 -0
  48. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/write/point.py +0 -0
  49. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/write/polars_dataframe_serializer.py +0 -0
  50. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/write/retry.py +0 -0
  51. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/client/write_api.py +0 -0
  52. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/configuration.py +0 -0
  53. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/domain/__init__.py +0 -0
  54. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/domain/write_precision.py +0 -0
  55. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/extras.py +0 -0
  56. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/rest.py +0 -0
  57. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/service/__init__.py +0 -0
  58. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/service/_base_service.py +0 -0
  59. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/service/signin_service.py +0 -0
  60. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/service/signout_service.py +0 -0
  61. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/influxdb_client_3/write_client/service/write_service.py +0 -0
  62. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/setup.cfg +0 -0
  63. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/setup.py +0 -0
  64. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_api_client.py +0 -0
  65. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_dataframe_serializer.py +0 -0
  66. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_date_helper.py +0 -0
  67. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_deep_merge.py +0 -0
  68. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_merge_options.py +0 -0
  69. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_point.py +0 -0
  70. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_polars.py +0 -0
  71. {influxdb3_python-0.10.0 → influxdb3_python-0.12.0}/tests/test_write_file.py +0 -0
@@ -0,0 +1,104 @@
1
+ import os
2
+ import time
3
+
4
+ import pyarrow
5
+
6
+ from config import Config
7
+ from influxdb_client_3 import InfluxDBClient3
8
+
9
+ bad_cert = """-----BEGIN CERTIFICATE-----
10
+ MIIFDTCCAvWgAwIBAgIUYzpfisy9xLrhiZd+D9vOdzC3+iswDQYJKoZIhvcNAQEL
11
+ BQAwFjEUMBIGA1UEAwwLdGVzdGhvc3QuaW8wHhcNMjUwMjI4MTM1NTMyWhcNMzUw
12
+ MjI2MTM1NTMyWjAWMRQwEgYDVQQDDAt0ZXN0aG9zdC5pbzCCAiIwDQYJKoZIhvcN
13
+ AQEBBQADggIPADCCAgoCggIBAN1lwqXYP8UMvjb56SpUEj2OpoEDRfLeWrEiHkOl
14
+ xoymvJGaXZNEpDXo2TTdysCoYWEjz9IY6GlqSo2Yssf5BZkQwMOw7MdyRwCigzrh
15
+ OAKbyCfsvEgfNFrXEdSDpaxW++5SToeErudYXc+sBfnI1NB4W3GBGqqIvx8fqaB3
16
+ 1EU9ql2sKKxI0oYIQD/If9rQEyLFKeWdD8iT6YST1Vugkvd34NPmaqV5+pjdSb4z
17
+ a8olavwUoslqFUeILqIq+WZZbOlgCcJYKcBAmELRnsxGaABRtMwMZx+0D+oKo4Kl
18
+ QQtOcER+RHkBHyYFghZIBnzudfbP9NadknOz3AilJbJolXfXJqeQhRD8Ob49kkhe
19
+ OwjAppHnaZGWjYZMLIfnwwXBwkS7bSwF16Wot83cpL46Xvg6xcl12An4JaoF798Q
20
+ cXyYrWCgvbqjVR7694gxqLGzk138AKTDSbER1h1rfqCqkk7soE0oWCs7jiCk2XvD
21
+ 49qVfHtd50KYJ4/yP1XL0PmLL0Hw1kvOxLVkFENc1zkoYXJRt2Ec6j9dajmGlsFn
22
+ 0bLLap6UIlIGQFuvcLf4bvsIi9FICy2jBjaIdM4UAWbReG+52+180HEleAwi5bAN
23
+ HY61WVXc4X+N0E2y8HWc1QaRioU7R4XZ5HXKs7OTWkKFZUU2JDFHAKdiiAU78qLU
24
+ 7GApAgMBAAGjUzBRMB0GA1UdDgQWBBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAfBgNV
25
+ HSMEGDAWgBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAPBgNVHRMBAf8EBTADAQH/MA0G
26
+ CSqGSIb3DQEBCwUAA4ICAQC4TJNPx476qhiMi8anISv9lo9cnLju+qNhcz7wupBH
27
+ 3Go6bVQ7TCbSt2QpAyY64mdnRqHsXeGvZXCnabOpeKRDeAPBtRjc6yNKuXybqFtn
28
+ W3PZEs/OYc659TUA+MoBzSXYStN9yiiYXyVFqVn+Rw6kM9tKh0GgAU7f5P+8IGuR
29
+ gXJbCjkbdJO7JUiVGEEmkjUHyqFxMHaZ8V6uazs52qIFyt7OYQTeV9HdoW8D9vAt
30
+ GfzYwzRDzbsZeIJqqDzLe7NOyxEyqZHCbtNpGcOyaLOl7ZBS52WsqaUZtL+9PjqD
31
+ 2TWj4WUFkOWQpTvWKHqM6//Buv4GjnTBShQKm+h+rxcGkdRMF6/sKwxPbr39P3RJ
32
+ TMfJA3u5UuowT44VaA2jkQzqIbxH9+3EA+0qPbqPJchOSr0pHSncqvR9FYcr7ayN
33
+ b6UDFnjeliyEqqksUO0arbvaO9FfB0kH8lU1NOKaQNO++Xj69GZMC6s721cNdad0
34
+ qqcdtyXWeOBBchguYDrSUIgLnUTHEwwzOmcNQ36hO5eX282BJy3ZLT3JU6MJopjz
35
+ vkbDDAxSrpZMcaoAWSrxgJAETeYiO4YbfORIzPkwdUkEIr6XY02Pi7MdkDGQ5hiB
36
+ TavA8+oXRa4b9BR3bCWcg8S/t4uOTTLkeTcQbONPh5A5IRySLCU+CwqB+/+VlO8X
37
+ Aw==
38
+ -----END CERTIFICATE-----"""
39
+
40
+
41
+ def write_cert(cert, file_name):
42
+ f = open(file_name, "w")
43
+ f.write(cert)
44
+ f.close()
45
+
46
+
47
+ def remove_cert(file_name):
48
+ os.remove(file_name)
49
+
50
+
51
+ def print_results(results: list):
52
+ print("%-6s%-6s%-6s%-24s" % ("id", "speed", "ticks", "time"))
53
+ for result in results:
54
+ print("%-6s%-6.2f%-6i%-24s" % (result['id'], result['speed'], result['ticks'], result['time']))
55
+
56
+
57
+ def main() -> None:
58
+ print("Main")
59
+ temp_cert_file = "temp_cert.pem"
60
+ conf = Config()
61
+
62
+ write_and_query_with_explicit_sys_cert(conf)
63
+
64
+ write_cert(bad_cert, temp_cert_file)
65
+ query_with_verify_ssl_off(conf, temp_cert_file)
66
+ remove_cert(temp_cert_file)
67
+
68
+
69
+ def write_and_query_with_explicit_sys_cert(conf):
70
+ print("\nwrite and query with typical linux system cert\n")
71
+ with InfluxDBClient3(token=conf.token,
72
+ host=conf.host,
73
+ org=conf.org,
74
+ database=conf.database,
75
+ ssl_ca_cert="/etc/ssl/certs/ca-certificates.crt",
76
+ verify_ssl=True) as _client:
77
+ now = time.time_ns()
78
+ lp = f"escooter,id=zx80 speed=3.14,ticks=42i {now - (10 * 1_000_000_000)}"
79
+ _client.write(lp)
80
+
81
+ query = "SELECT * FROM \"escooter\" ORDER BY time DESC"
82
+ reader: pyarrow.Table = _client.query(query, mode="")
83
+ print_results(reader.to_pylist())
84
+
85
+
86
+ def query_with_verify_ssl_off(conf, cert):
87
+ print("\nquerying with verify_ssl off\n")
88
+
89
+ # Note that the passed root cert above is bad
90
+ # Switch verify_ssl to True to throw SSL_ERROR_SSL
91
+ with InfluxDBClient3(token=conf.token,
92
+ host=conf.host,
93
+ org=conf.org,
94
+ database=conf.database,
95
+ ssl_ca_cert=cert,
96
+ verify_ssl=False) as _client:
97
+
98
+ query = "SELECT * FROM \"escooter\" ORDER BY time DESC"
99
+ reader: pyarrow.Table = _client.query(query, mode="")
100
+ print_results(reader.to_pylist())
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()
@@ -0,0 +1,87 @@
1
+ import asyncio
2
+ import random
3
+ import time
4
+
5
+ import pandas
6
+
7
+ from influxdb_client_3 import InfluxDBClient3
8
+
9
+ from config import Config
10
+
11
+
12
+ async def fibio(iterations, grit=0.5):
13
+ """
14
+ example coroutine to run parallel with query_async
15
+ :param iterations:
16
+ :param grit:
17
+ :return:
18
+ """
19
+ n0 = 1
20
+ n1 = 1
21
+ vals = [n0, n1]
22
+ for _ in range(iterations):
23
+ val = n0 + n1
24
+ n0 = n1
25
+ n1 = val
26
+ print(val)
27
+ vals.append(val)
28
+ await asyncio.sleep(grit)
29
+ return vals
30
+
31
+
32
+ def write_data(client: InfluxDBClient3, measurement):
33
+ """
34
+ Synchronous write - only for preparing data
35
+ :param client:
36
+ :param measurement:
37
+ :return:
38
+ """
39
+ ids = ['s3b1', 'dq41', 'sgw22']
40
+ lp_template = f"{measurement},id=%s speed=%f,alt=%f,bearing=%f %d"
41
+ data_size = 10
42
+ data = []
43
+ interval = 10 * 1_000_000_000
44
+ ts = time.time_ns() - (interval * data_size)
45
+ for _ in range(data_size):
46
+ data.append(lp_template % (ids[random.randint(0, len(ids) - 1)],
47
+ random.random() * 300,
48
+ random.random() * 2000,
49
+ random.random() * 360, ts))
50
+ ts += interval
51
+
52
+ client.write(data)
53
+
54
+
55
+ async def query_data(client: InfluxDBClient3, measurement):
56
+ """
57
+ Query asynchronously - should not block other coroutines
58
+ :param client:
59
+ :param measurement:
60
+ :return:
61
+ """
62
+ query = f"SELECT * FROM \"{measurement}\" WHERE time >= now() - interval '5 minutes' ORDER BY time DESC"
63
+ print(f"query start: {pandas.Timestamp(time.time_ns())}")
64
+ table = await client.query_async(query)
65
+ print(f"query returned: {pandas.Timestamp(time.time_ns())}")
66
+ return table.to_pandas()
67
+
68
+
69
+ async def main():
70
+ config = Config()
71
+ client = InfluxDBClient3(
72
+ host=config.host,
73
+ token=config.token,
74
+ database=config.database,
75
+ org=config.org
76
+ )
77
+ measurement = 'example_uav'
78
+ write_data(client, measurement)
79
+
80
+ # run both coroutines simultaneously
81
+ result = await asyncio.gather(fibio(10, 0.2), query_data(client, measurement))
82
+ print(f"fibio sequence = {result[0]}")
83
+ print(f"data set =\n{result[1]}")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ asyncio.run(main())
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: influxdb3-python
3
- Version: 0.10.0
3
+ Version: 0.12.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
@@ -33,6 +33,17 @@ Requires-Dist: polars; extra == "dataframe"
33
33
  Provides-Extra: test
34
34
  Requires-Dist: pytest; extra == "test"
35
35
  Requires-Dist: pytest-cov; extra == "test"
36
+ Dynamic: author
37
+ Dynamic: author-email
38
+ Dynamic: classifier
39
+ Dynamic: description
40
+ Dynamic: description-content-type
41
+ Dynamic: home-page
42
+ Dynamic: license-file
43
+ Dynamic: provides-extra
44
+ Dynamic: requires-dist
45
+ Dynamic: requires-python
46
+ Dynamic: summary
36
47
 
37
48
  <p align="center">
38
49
  <img src="https://github.com/InfluxCommunity/influxdb3-python/blob/main/python-logo.png?raw=true" alt="Your Image" width="150px">
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: influxdb3-python
3
- Version: 0.10.0
3
+ Version: 0.12.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
@@ -33,6 +33,17 @@ Requires-Dist: polars; extra == "dataframe"
33
33
  Provides-Extra: test
34
34
  Requires-Dist: pytest; extra == "test"
35
35
  Requires-Dist: pytest-cov; extra == "test"
36
+ Dynamic: author
37
+ Dynamic: author-email
38
+ Dynamic: classifier
39
+ Dynamic: description
40
+ Dynamic: description-content-type
41
+ Dynamic: home-page
42
+ Dynamic: license-file
43
+ Dynamic: provides-extra
44
+ Dynamic: requires-dist
45
+ Dynamic: requires-python
46
+ Dynamic: summary
36
47
 
37
48
  <p align="center">
38
49
  <img src="https://github.com/InfluxCommunity/influxdb3-python/blob/main/python-logo.png?raw=true" alt="Your Image" width="150px">
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  setup.py
4
4
  Examples/__init__.py
5
+ Examples/basic_ssl_example.py
5
6
  Examples/batching_example.py
6
7
  Examples/cloud_dedicated_query.py
7
8
  Examples/cloud_dedicated_write.py
@@ -9,6 +10,7 @@ Examples/config.py
9
10
  Examples/flight_options_example.py
10
11
  Examples/handle_http_error.py
11
12
  Examples/pandas_write.py
13
+ Examples/query_async.py
12
14
  Examples/query_type.py
13
15
  influxdb3_python.egg-info/PKG-INFO
14
16
  influxdb3_python.egg-info/SOURCES.txt
@@ -2,7 +2,7 @@ import urllib.parse
2
2
  import pyarrow as pa
3
3
  import importlib.util
4
4
 
5
- from influxdb_client_3.query.query_api import QueryApi as _QueryApi
5
+ from influxdb_client_3.query.query_api import QueryApi as _QueryApi, QueryApiOptionsBuilder
6
6
  from influxdb_client_3.read_file import UploadFile
7
7
  from influxdb_client_3.write_client import InfluxDBClient as _InfluxDBClient, WriteOptions, Point
8
8
  from influxdb_client_3.write_client.client.exceptions import InfluxDBError
@@ -121,7 +121,7 @@ class InfluxDBClient3:
121
121
  possible.
122
122
  :key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128)
123
123
  :key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
124
- authentication.
124
+ authentication. (Applies to Write API only)
125
125
  :key int connection_pool_maxsize: Number of connections to save that can be reused by urllib3.
126
126
  Defaults to "multiprocessing.cpu_count() * 5".
127
127
  :key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
@@ -165,8 +165,18 @@ class InfluxDBClient3:
165
165
  connection_string = f"grpc+tls://{hostname}:{port}"
166
166
  else:
167
167
  connection_string = f"grpc+tcp://{hostname}:{port}"
168
+
169
+ q_opts_builder = QueryApiOptionsBuilder()
170
+ kw_keys = kwargs.keys()
171
+ if kw_keys.__contains__('ssl_ca_cert'):
172
+ q_opts_builder.root_certs(kwargs.get('ssl_ca_cert', None))
173
+ if kw_keys.__contains__('verify_ssl'):
174
+ q_opts_builder.tls_verify(kwargs.get('verify_ssl', True))
175
+ if kw_keys.__contains__('proxy'):
176
+ q_opts_builder.proxy(kwargs.get('proxy', None))
168
177
  self._query_api = _QueryApi(connection_string=connection_string, token=token,
169
- flight_client_options=flight_client_options)
178
+ flight_client_options=flight_client_options,
179
+ proxy=kwargs.get("proxy", None), options=q_opts_builder.build())
170
180
 
171
181
  def write(self, record=None, database=None, **kwargs):
172
182
  """
@@ -268,6 +278,39 @@ class InfluxDBClient3:
268
278
  except InfluxDBError as e:
269
279
  raise e
270
280
 
281
+ async def query_async(self, query: str, language: str = "sql", mode: str = "all", database: str = None, **kwargs):
282
+ """Query data from InfluxDB asynchronously.
283
+
284
+ If you want to use query parameters, you can pass them as kwargs:
285
+
286
+ >>> await client.query_async("select * from cpu where host=$host", query_parameters={"host": "server01"})
287
+
288
+ :param query: The query to execute on the database.
289
+ :param language: The query language to use. It should be one of "influxql" or "sql". Defaults to "sql".
290
+ :param mode: The mode to use for the query. It should be one of "all", "pandas", "polars", "chunk",
291
+ "reader" or "schema". Defaults to "all".
292
+ :param database: The database to query from. If not provided, uses the database provided during initialization.
293
+ :param kwargs: Additional arguments to pass to the ``FlightCallOptions headers``. For example, it can be used to
294
+ set up per request headers.
295
+ :keyword query_parameters: The query parameters to use in the query.
296
+ It should be a ``dictionary`` of key-value pairs.
297
+ :return: The query result in the specified mode.
298
+ """
299
+ if mode == "polars" and polars is False:
300
+ raise ImportError("Polars is not installed. Please install it with `pip install polars`.")
301
+
302
+ if database is None:
303
+ database = self._database
304
+
305
+ try:
306
+ return await self._query_api.query_async(query=query,
307
+ language=language,
308
+ mode=mode,
309
+ database=database,
310
+ **kwargs)
311
+ except InfluxDBError as e:
312
+ raise e
313
+
271
314
  def close(self):
272
315
  """Close the client and clean up resources."""
273
316
  self._write_api.close()
@@ -0,0 +1,252 @@
1
+ """Query data in InfluxDB 3."""
2
+ import asyncio
3
+ # coding: utf-8
4
+ import json
5
+
6
+ from pyarrow.flight import FlightClient, Ticket, FlightCallOptions, FlightStreamReader
7
+
8
+ from influxdb_client_3.version import USER_AGENT
9
+
10
+
11
+ class QueryApiOptions(object):
12
+ """
13
+ Structure for encapsulating options for the QueryApi
14
+
15
+ Attributes
16
+ ----------
17
+ tls_root_certs (bytes): contents of an SSL root certificate or chain read as bytes
18
+ tls_verify (bool): whether to verify SSL certificates or not
19
+ proxy (str): URL to a proxy server
20
+ flight_client_options (dict): base set of flight client options passed to internal pyarrow.flight.FlightClient
21
+ """
22
+ tls_root_certs: bytes = None
23
+ tls_verify: bool = None
24
+ proxy: str = None
25
+ flight_client_options: dict = None
26
+
27
+ def __init__(self, root_certs_path, verify, proxy, flight_client_options):
28
+ """
29
+ Initialize a set of QueryApiOptions
30
+
31
+ :param root_certs_path: path to a certificate .pem file.
32
+ :param verify: whether to verify SSL certificates or not.
33
+ :param proxy: URL of a proxy server, if required.
34
+ :param flight_client_options: set of flight_client_options
35
+ to be passed to internal pyarrow.flight.FlightClient.
36
+ """
37
+ if root_certs_path:
38
+ self.tls_root_certs = self._read_certs(root_certs_path)
39
+ self.tls_verify = verify
40
+ self.proxy = proxy
41
+ self.flight_client_options = flight_client_options
42
+
43
+ def _read_certs(self, path):
44
+ with open(path, "rb") as certs_file:
45
+ return certs_file.read()
46
+
47
+
48
+ class QueryApiOptionsBuilder(object):
49
+ """
50
+ Helper class to make adding QueryApiOptions more dynamic.
51
+
52
+ Example:
53
+
54
+ .. code-block:: python
55
+
56
+ options = QueryApiOptionsBuilder()\
57
+ .proxy("http://internal.tunnel.proxy:8080") \
58
+ .root_certs("/home/fred/.etc/ssl/alt_certs.pem") \
59
+ .tls_verify(True) \
60
+ .build()
61
+
62
+ client = QueryApi(connection, token, None, None, options)
63
+ """
64
+ _root_certs_path = None
65
+ _tls_verify = True
66
+ _proxy = None
67
+ _flight_client_options = None
68
+
69
+ def root_certs(self, path):
70
+ self._root_certs_path = path
71
+ return self
72
+
73
+ def tls_verify(self, verify):
74
+ self._tls_verify = verify
75
+ return self
76
+
77
+ def proxy(self, proxy):
78
+ self._proxy = proxy
79
+ return self
80
+
81
+ def flight_client_options(self, flight_client_options):
82
+ self._flight_client_options = flight_client_options
83
+ return self
84
+
85
+ def build(self):
86
+ """Build a QueryApiOptions object with previously set values"""
87
+ return QueryApiOptions(
88
+ root_certs_path=self._root_certs_path,
89
+ verify=self._tls_verify,
90
+ proxy=self._proxy,
91
+ flight_client_options=self._flight_client_options
92
+ )
93
+
94
+
95
+ class QueryApi(object):
96
+ """
97
+ Implementation for '/api/v2/query' endpoint.
98
+
99
+ Example:
100
+
101
+ .. code-block:: python
102
+
103
+ from influxdb_client import InfluxDBClient
104
+
105
+
106
+ # Initialize instance of QueryApi
107
+ with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
108
+ query_api = client.query_api()
109
+ """
110
+
111
+ def __init__(self,
112
+ connection_string,
113
+ token,
114
+ flight_client_options,
115
+ proxy=None, options=None) -> None:
116
+ """
117
+ Initialize defaults.
118
+
119
+ :param connection_string: Flight/gRPC connection string
120
+ :param token: access token
121
+ :param flight_client_options: Flight client options
122
+ """
123
+ self._token = token
124
+ self._flight_client_options = flight_client_options or {}
125
+ default_user_agent = ("grpc.secondary_user_agent", USER_AGENT)
126
+ if "generic_options" in self._flight_client_options:
127
+ if "grpc.secondary_user_agent" not in dict(self._flight_client_options["generic_options"]).keys():
128
+ self._flight_client_options["generic_options"].append(default_user_agent)
129
+ else:
130
+ self._flight_client_options["generic_options"] = [default_user_agent]
131
+ self._proxy = proxy
132
+ from influxdb_client_3 import _merge_options as merge_options
133
+ if options:
134
+ if options.flight_client_options:
135
+ self._flight_client_options = merge_options(self._flight_client_options,
136
+ None,
137
+ options.flight_client_options)
138
+ if ('generic_options' in options.flight_client_options and
139
+ 'grpc.secondary_user_agent' in dict(options.flight_client_options["generic_options"]).keys()):
140
+ self._flight_client_options['generic_options'].remove(default_user_agent)
141
+ if options.tls_root_certs:
142
+ self._flight_client_options["tls_root_certs"] = options.tls_root_certs
143
+ if options.proxy:
144
+ self._proxy = options.proxy
145
+ if options.tls_verify is not None:
146
+ self._flight_client_options["disable_server_verification"] = not options.tls_verify
147
+ if self._proxy:
148
+ self._flight_client_options["generic_options"].append(("grpc.http_proxy", self._proxy))
149
+ self._flight_client = FlightClient(connection_string, **self._flight_client_options)
150
+
151
+ def query(self, query: str, language: str, mode: str, database: str, **kwargs):
152
+ """Query data from InfluxDB.
153
+
154
+ :param query: The query to execute on the database.
155
+ :param language: The query language.
156
+ :param mode: The mode to use for the query.
157
+ It should be one of "all", "pandas", "polars", "chunk", "reader" or "schema".
158
+ :param database: The database to query from.
159
+ :param kwargs: Additional arguments to pass to the ``FlightCallOptions headers``.
160
+ For example, it can be used to set up per request headers.
161
+ :keyword query_parameters: The query parameters to use in the query.
162
+ It should be a ``dictionary`` of key-value pairs.
163
+ :return: The query result in the specified mode.
164
+ """
165
+ try:
166
+ ticket, _options = self._prepare_query(query, language, database, **kwargs)
167
+
168
+ flight_reader = self._do_get(ticket, _options)
169
+
170
+ return self._translate_stream_reader(flight_reader, mode)
171
+ except Exception as e:
172
+ raise e
173
+
174
+ async def query_async(self, query: str, language: str, mode: str, database: str, **kwargs):
175
+ """Query data from InfluxDB asynchronously.
176
+
177
+ Wraps internal FlightClient.doGet call in its own executor, so that the event_loop will not be blocked.
178
+
179
+ :param query: The query to execute on the database.
180
+ :param language: The query language.
181
+ :param mode: The mode to use for the query.
182
+ It should be one of "all", "pandas", "polars", "chunk", "reader" or "schema".
183
+ :param database: The database to query from.
184
+ :param kwargs: Additional arguments to pass to the ``FlightCallOptions headers``.
185
+ For example, it can be used to set up per request headers.
186
+ :keyword query_parameters: The query parameters to use in the query.
187
+ It should be a ``dictionary`` of key-value pairs.
188
+ :return: The query result in the specified mode.
189
+ """
190
+ try:
191
+ ticket, options = self._prepare_query(query, language, database, **kwargs)
192
+ loop = asyncio.get_running_loop()
193
+ _flight_reader = await loop.run_in_executor(None,
194
+ self._flight_client.do_get, ticket, options)
195
+ return await loop.run_in_executor(None, self._translate_stream_reader,
196
+ _flight_reader,
197
+ mode)
198
+ except Exception as e:
199
+ raise e
200
+
201
+ def _translate_stream_reader(self, reader: FlightStreamReader, mode: str):
202
+ from influxdb_client_3 import polars as has_polars
203
+ try:
204
+ mode_funcs = {
205
+ "all": reader.read_all,
206
+ "pandas": reader.read_pandas,
207
+ "chunk": lambda: reader,
208
+ "reader": reader.to_reader,
209
+ "schema": lambda: reader.schema
210
+ }
211
+ if has_polars:
212
+ import polars as pl
213
+ mode_funcs["polars"] = lambda: pl.from_arrow(reader.read_all())
214
+ mode_func = mode_funcs.get(mode, reader.read_all)
215
+
216
+ return mode_func() if callable(mode_func) else mode_func
217
+ except Exception as e:
218
+ raise e
219
+
220
+ def _prepare_query(self, query: str, language: str, database: str, **kwargs):
221
+ from influxdb_client_3 import _merge_options as merge_options
222
+ # Create an authorization header
223
+ optargs = {
224
+ "headers": [(b"authorization", f"Bearer {self._token}".encode('utf-8'))],
225
+ "timeout": 300
226
+ }
227
+ opts = merge_options(optargs, exclude_keys=['query_parameters'], custom=kwargs)
228
+ _options = FlightCallOptions(**opts)
229
+
230
+ #
231
+ # Ticket data
232
+ #
233
+ ticket_data = {
234
+ "database": database,
235
+ "sql_query": query,
236
+ "query_type": language
237
+ }
238
+ # add query parameters
239
+ query_parameters = kwargs.get("query_parameters", None)
240
+ if query_parameters:
241
+ ticket_data["params"] = query_parameters
242
+
243
+ ticket = Ticket(json.dumps(ticket_data).encode('utf-8'))
244
+
245
+ return ticket, _options
246
+
247
+ def _do_get(self, ticket: Ticket, options: FlightCallOptions = None) -> FlightStreamReader:
248
+ return self._flight_client.do_get(ticket, options)
249
+
250
+ def close(self):
251
+ """Close the Flight client."""
252
+ self._flight_client.close()
@@ -1,4 +1,4 @@
1
1
  """Version of the Client that is used in User-Agent header."""
2
2
 
3
- VERSION = '0.10.0'
3
+ VERSION = '0.12.0'
4
4
  USER_AGENT = f'influxdb3-python/{VERSION}'
@@ -2,6 +2,8 @@ import unittest
2
2
  from unittest.mock import patch
3
3
 
4
4
  from influxdb_client_3 import InfluxDBClient3
5
+ from tests.util import asyncio_run
6
+ from tests.util.mocks import ConstantFlightServer, ConstantData
5
7
 
6
8
 
7
9
  class TestInfluxDBClient3(unittest.TestCase):
@@ -48,6 +50,30 @@ class TestInfluxDBClient3(unittest.TestCase):
48
50
  )
49
51
  self.assertEqual(client._client.auth_header_value, "my_scheme my_token")
50
52
 
53
+ @asyncio_run
54
+ async def test_query_async(self):
55
+ with ConstantFlightServer() as server:
56
+ client = InfluxDBClient3(
57
+ host=f"http://localhost:{server.port}",
58
+ org="my_org",
59
+ database="my_db",
60
+ token="my_token",
61
+ )
62
+
63
+ query = "SELECT * FROM my_data"
64
+
65
+ table = await client.query_async(query=query, language="sql")
66
+
67
+ result_list = table.to_pylist()
68
+
69
+ cd = ConstantData()
70
+ for item in cd.to_list():
71
+ assert item in result_list
72
+
73
+ assert {'data': 'database', 'reference': 'my_db', 'value': -1.0} in result_list
74
+ assert {'data': 'sql_query', 'reference': query, 'value': -1.0} in result_list
75
+ assert {'data': 'query_type', 'reference': 'sql', 'value': -1.0} in result_list
76
+
51
77
 
52
78
  if __name__ == '__main__':
53
79
  unittest.main()