influxdb3-python 0.17.0__tar.gz → 0.18.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.
- influxdb3_python-0.18.0/Examples/query_with_middleware.py +33 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/PKG-INFO +4 -1
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/README.md +3 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb3_python.egg-info/PKG-INFO +4 -1
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb3_python.egg-info/SOURCES.txt +1 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/exceptions/exceptions.py +32 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/query/query_api.py +14 -1
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/version.py +1 -1
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/write_api.py +5 -8
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_api_client.py +52 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_influxdb_client_3_integration.py +99 -2
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_polars.py +0 -2
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_query.py +41 -2
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/basic_ssl_example.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/batching_example.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/cloud_dedicated_query.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/cloud_dedicated_write.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/config.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/flight_options_example.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/handle_http_error.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/handle_query_error.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/pandas_write.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/query_async.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/query_type.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/Examples/timeouts.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/LICENSE +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb3_python.egg-info/dependency_links.txt +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb3_python.egg-info/requires.txt +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb3_python.egg-info/top_level.txt +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/exceptions/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/py.typed +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/query/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/read_file.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/_sync/api_client.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/_sync/rest.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/_base.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/influxdb_client.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/util/date_utils.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/warnings.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/write/point.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/write/polars_dataframe_serializer.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/write/retry.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/configuration.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/domain/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/domain/write_precision.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/domain/write_precision_converter.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/extras.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/rest.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/service/__init__.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/service/_base_service.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/service/signin_service.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/service/signout_service.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/service/write_service.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/pyproject.toml +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/setup.cfg +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/setup.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_dataframe_serializer.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_date_helper.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_deep_merge.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_flush.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_influxdb_client_3.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_merge_options.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_point.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_write_file.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_write_local_server.py +0 -0
- {influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_write_precision_converter.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pyarrow import flight
|
|
2
|
+
|
|
3
|
+
from config import Config
|
|
4
|
+
from influxdb_client_3 import InfluxDBClient3, flight_client_options
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# This middleware will add an additional attribute `some-attribute` to the header
|
|
8
|
+
class ModifyHeaderClientMiddleware(flight.ClientMiddleware):
|
|
9
|
+
def sending_headers(self):
|
|
10
|
+
return {
|
|
11
|
+
"some-attribute": "some-value",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def received_headers(self, headers):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ModifyHeaderClientMiddlewareFactory(flight.ClientMiddlewareFactory):
|
|
19
|
+
def start_call(self, info):
|
|
20
|
+
return ModifyHeaderClientMiddleware()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
config = Config()
|
|
24
|
+
middleware = [ModifyHeaderClientMiddlewareFactory()]
|
|
25
|
+
client = InfluxDBClient3(
|
|
26
|
+
host=config.host,
|
|
27
|
+
token=config.token,
|
|
28
|
+
database=config.database,
|
|
29
|
+
flight_client_options=flight_client_options(middleware=middleware)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
df = client.query(query="select * from cpu11 limit 10", mode="pandas")
|
|
33
|
+
print(len(df))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: influxdb3-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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
|
|
@@ -53,6 +53,9 @@ Dynamic: summary
|
|
|
53
53
|
</p>
|
|
54
54
|
|
|
55
55
|
<p align="center">
|
|
56
|
+
<a href="https://influxdb3-python.readthedocs.io/en/latest/">
|
|
57
|
+
<img src="https://img.shields.io/readthedocs/influxdb3-python/latest" alt="Readthedocs document">
|
|
58
|
+
</a>
|
|
56
59
|
<a href="https://pypi.org/project/influxdb3-python/">
|
|
57
60
|
<img src="https://img.shields.io/pypi/v/influxdb3-python.svg" alt="PyPI version">
|
|
58
61
|
</a>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
|
+
<a href="https://influxdb3-python.readthedocs.io/en/latest/">
|
|
8
|
+
<img src="https://img.shields.io/readthedocs/influxdb3-python/latest" alt="Readthedocs document">
|
|
9
|
+
</a>
|
|
7
10
|
<a href="https://pypi.org/project/influxdb3-python/">
|
|
8
11
|
<img src="https://img.shields.io/pypi/v/influxdb3-python.svg" alt="PyPI version">
|
|
9
12
|
</a>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: influxdb3-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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
|
|
@@ -53,6 +53,9 @@ Dynamic: summary
|
|
|
53
53
|
</p>
|
|
54
54
|
|
|
55
55
|
<p align="center">
|
|
56
|
+
<a href="https://influxdb3-python.readthedocs.io/en/latest/">
|
|
57
|
+
<img src="https://img.shields.io/readthedocs/influxdb3-python/latest" alt="Readthedocs document">
|
|
58
|
+
</a>
|
|
56
59
|
<a href="https://pypi.org/project/influxdb3-python/">
|
|
57
60
|
<img src="https://img.shields.io/pypi/v/influxdb3-python.svg" alt="PyPI version">
|
|
58
61
|
</a>
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/exceptions/exceptions.py
RENAMED
|
@@ -63,9 +63,41 @@ class InfluxDBError(InfluxDB3ClientError):
|
|
|
63
63
|
def get(d, key):
|
|
64
64
|
if not key or d is None:
|
|
65
65
|
return d
|
|
66
|
+
if not isinstance(d, dict):
|
|
67
|
+
return None
|
|
66
68
|
return get(d.get(key[0]), key[1:])
|
|
67
69
|
try:
|
|
68
70
|
node = json.loads(response.data)
|
|
71
|
+
if isinstance(node, dict):
|
|
72
|
+
# InfluxDB v3 error format: { "code": "...", "message": "..." }
|
|
73
|
+
code = node.get("code")
|
|
74
|
+
message = node.get("message")
|
|
75
|
+
if message:
|
|
76
|
+
return f"{code}: {message}" if code else message
|
|
77
|
+
# InfluxDB v3 write error format:
|
|
78
|
+
# {
|
|
79
|
+
# "error": "...",
|
|
80
|
+
# "data": [ { "error_message": "...", "line_number": 2, "original_line": "..." }, ... ]
|
|
81
|
+
# }
|
|
82
|
+
error_text = node.get("error")
|
|
83
|
+
data = node.get("data")
|
|
84
|
+
if error_text and isinstance(data, list):
|
|
85
|
+
details = []
|
|
86
|
+
for item in data:
|
|
87
|
+
if not isinstance(item, dict):
|
|
88
|
+
continue
|
|
89
|
+
line_number = item.get("line_number")
|
|
90
|
+
error_message = item.get("error_message")
|
|
91
|
+
original_line = item.get("original_line")
|
|
92
|
+
if line_number is not None and error_message and original_line:
|
|
93
|
+
details.append(
|
|
94
|
+
f"\tline {line_number}: {error_message} ({original_line})"
|
|
95
|
+
)
|
|
96
|
+
elif error_message:
|
|
97
|
+
details.append(f"\t{error_message}")
|
|
98
|
+
if details:
|
|
99
|
+
return error_text + ":\n" + "\n".join(details)
|
|
100
|
+
return error_text
|
|
69
101
|
for key in [['message'], ['data', 'error_message'], ['error']]:
|
|
70
102
|
value = get(node, key)
|
|
71
103
|
if value is not None:
|
|
@@ -20,6 +20,7 @@ class QueryApiOptions(object):
|
|
|
20
20
|
flight_client_options (dict): base set of flight client options passed to internal pyarrow.flight.FlightClient
|
|
21
21
|
timeout(float): timeout in seconds to wait for a response
|
|
22
22
|
disable_grpc_compression (bool): disable gRPC compression for query responses
|
|
23
|
+
middleware (list): list of middleware functions to be applied to Flight calls
|
|
23
24
|
"""
|
|
24
25
|
_DEFAULT_TIMEOUT = 300.0
|
|
25
26
|
tls_root_certs: bytes = None
|
|
@@ -28,13 +29,15 @@ class QueryApiOptions(object):
|
|
|
28
29
|
flight_client_options: dict = None
|
|
29
30
|
timeout: float = None
|
|
30
31
|
disable_grpc_compression: bool = False
|
|
32
|
+
middleware: list = None
|
|
31
33
|
|
|
32
34
|
def __init__(self, root_certs_path: str,
|
|
33
35
|
verify: bool,
|
|
34
36
|
proxy: str,
|
|
35
37
|
flight_client_options: dict,
|
|
36
38
|
timeout: float = _DEFAULT_TIMEOUT,
|
|
37
|
-
disable_grpc_compression: bool = False
|
|
39
|
+
disable_grpc_compression: bool = False,
|
|
40
|
+
middleware: list = None):
|
|
38
41
|
"""
|
|
39
42
|
Initialize a set of QueryApiOptions
|
|
40
43
|
|
|
@@ -45,6 +48,7 @@ class QueryApiOptions(object):
|
|
|
45
48
|
to be passed to internal pyarrow.flight.FlightClient.
|
|
46
49
|
:param timeout: timeout in seconds to wait for a response.
|
|
47
50
|
:param disable_grpc_compression: disable gRPC compression for query responses.
|
|
51
|
+
:param middleware: list of middleware functions to be applied to Flight calls.
|
|
48
52
|
"""
|
|
49
53
|
if root_certs_path:
|
|
50
54
|
self.tls_root_certs = self._read_certs(root_certs_path)
|
|
@@ -53,6 +57,7 @@ class QueryApiOptions(object):
|
|
|
53
57
|
self.flight_client_options = flight_client_options
|
|
54
58
|
self.timeout = timeout
|
|
55
59
|
self.disable_grpc_compression = disable_grpc_compression
|
|
60
|
+
self.middleware = middleware
|
|
56
61
|
|
|
57
62
|
def _read_certs(self, path: str) -> bytes:
|
|
58
63
|
with open(path, "rb") as certs_file:
|
|
@@ -81,6 +86,7 @@ class QueryApiOptionsBuilder(object):
|
|
|
81
86
|
_flight_client_options: dict = None
|
|
82
87
|
_timeout: float = None
|
|
83
88
|
_disable_grpc_compression: bool = False
|
|
89
|
+
_middleware: list = None
|
|
84
90
|
|
|
85
91
|
def root_certs(self, path: str):
|
|
86
92
|
self._root_certs_path = path
|
|
@@ -107,6 +113,10 @@ class QueryApiOptionsBuilder(object):
|
|
|
107
113
|
self._disable_grpc_compression = disable
|
|
108
114
|
return self
|
|
109
115
|
|
|
116
|
+
def middleware(self, middleware: list):
|
|
117
|
+
self._middleware = middleware
|
|
118
|
+
return self
|
|
119
|
+
|
|
110
120
|
def build(self) -> QueryApiOptions:
|
|
111
121
|
"""Build a QueryApiOptions object with previously set values"""
|
|
112
122
|
return QueryApiOptions(
|
|
@@ -116,6 +126,7 @@ class QueryApiOptionsBuilder(object):
|
|
|
116
126
|
flight_client_options=self._flight_client_options,
|
|
117
127
|
timeout=self._timeout,
|
|
118
128
|
disable_grpc_compression=self._disable_grpc_compression,
|
|
129
|
+
middleware=self._middleware
|
|
119
130
|
)
|
|
120
131
|
|
|
121
132
|
|
|
@@ -181,6 +192,8 @@ class QueryApi(object):
|
|
|
181
192
|
self._flight_client_options["generic_options"].append(
|
|
182
193
|
("grpc.compression_enabled_algorithms_bitset", 1)
|
|
183
194
|
)
|
|
195
|
+
if options.middleware:
|
|
196
|
+
self._flight_client_options["middleware"] = options.middleware
|
|
184
197
|
if self._proxy:
|
|
185
198
|
self._flight_client_options["generic_options"].append(("grpc.http_proxy", self._proxy))
|
|
186
199
|
self._flight_client = FlightClient(connection_string, **self._flight_client_options)
|
|
@@ -296,9 +296,7 @@ class WriteApi(_BaseWriteApi):
|
|
|
296
296
|
|
|
297
297
|
if self._write_options.write_type is WriteType.asynchronous:
|
|
298
298
|
message = """The 'WriteType.asynchronous' is deprecated and will be removed in future major version.
|
|
299
|
-
|
|
300
|
-
You can use native asynchronous version of the client:
|
|
301
|
-
- https://influxdb-client.readthedocs.io/en/stable/usage.html#how-to-use-asyncio
|
|
299
|
+
You can use native asynchronous version of the client:
|
|
302
300
|
"""
|
|
303
301
|
# TODO above message has link to Influxdb2 API __NOT__ Influxdb3 API !!! - illustrates different API
|
|
304
302
|
warnings.warn(message, DeprecationWarning)
|
|
@@ -393,12 +391,9 @@ You can use native asynchronous version of the client:
|
|
|
393
391
|
|
|
394
392
|
_async_req = True if self._write_options.write_type == WriteType.asynchronous else False
|
|
395
393
|
|
|
396
|
-
# Filter out serializer-specific kwargs before passing to _post_write
|
|
397
|
-
http_kwargs = {k: v for k, v in kwargs.items() if k not in SERIALIZER_KWARGS}
|
|
398
|
-
|
|
399
394
|
def write_payload(payload):
|
|
400
395
|
final_string = b'\n'.join(payload[1])
|
|
401
|
-
return self._post_write(_async_req, bucket, org, final_string, payload[0], no_sync, **
|
|
396
|
+
return self._post_write(_async_req, bucket, org, final_string, payload[0], no_sync, **kwargs)
|
|
402
397
|
|
|
403
398
|
results = list(map(write_payload, payloads.items()))
|
|
404
399
|
if not _async_req:
|
|
@@ -588,11 +583,13 @@ You can use native asynchronous version of the client:
|
|
|
588
583
|
return _BatchResponse(data=batch_item)
|
|
589
584
|
|
|
590
585
|
def _post_write(self, _async_req, bucket, org, body, precision, no_sync, **kwargs):
|
|
586
|
+
# Filter out serializer-specific kwargs before passing to _post_write
|
|
587
|
+
http_kwargs = {k: v for k, v in kwargs.items() if k not in SERIALIZER_KWARGS}
|
|
591
588
|
return self._write_service.post_write(org=org, bucket=bucket, body=body, precision=precision,
|
|
592
589
|
no_sync=no_sync,
|
|
593
590
|
async_req=_async_req,
|
|
594
591
|
content_type="text/plain; charset=utf-8",
|
|
595
|
-
**
|
|
592
|
+
**http_kwargs)
|
|
596
593
|
|
|
597
594
|
def _to_response(self, data: _BatchItem, delay: timedelta):
|
|
598
595
|
|
|
@@ -124,6 +124,58 @@ class ApiClientTests(unittest.TestCase):
|
|
|
124
124
|
self._test_api_error(response_body)
|
|
125
125
|
self.assertEqual(response_body, err.exception.message)
|
|
126
126
|
|
|
127
|
+
def test_api_error_v3_with_detail(self):
|
|
128
|
+
cases = [
|
|
129
|
+
# all details available
|
|
130
|
+
(
|
|
131
|
+
"two-line details",
|
|
132
|
+
'{"error":"partial write of line protocol occurred","data":['
|
|
133
|
+
'{"error_message":"invalid column type for column \'v\', expected iox::column_type::field::float, '
|
|
134
|
+
'got iox::column_type::field::uinteger","line_number":2,"original_line":"**.DBG.remote_***"},'
|
|
135
|
+
'{"error_message":"invalid column type for column \'v\', expected iox::column_type::field::float, '
|
|
136
|
+
'got iox::column_type::field::uinteger","line_number":3,"original_line":"***.INF.remote_***"}'
|
|
137
|
+
']}',
|
|
138
|
+
"partial write of line protocol occurred:\n"
|
|
139
|
+
"\tline 2: invalid column type for column 'v', expected iox::column_type::field::float, "
|
|
140
|
+
"got iox::column_type::field::uinteger (**.DBG.remote_***)\n"
|
|
141
|
+
"\tline 3: invalid column type for column 'v', expected iox::column_type::field::float, "
|
|
142
|
+
"got iox::column_type::field::uinteger (***.INF.remote_***)",
|
|
143
|
+
),
|
|
144
|
+
# error_message only (no line_number/original_line)
|
|
145
|
+
(
|
|
146
|
+
"message-only detail",
|
|
147
|
+
'{"error":"partial write of line protocol occurred","data":['
|
|
148
|
+
'{"error_message":"only error message"}]}',
|
|
149
|
+
"partial write of line protocol occurred:\n"
|
|
150
|
+
"\tonly error message",
|
|
151
|
+
),
|
|
152
|
+
# non-dict item in data list is skipped
|
|
153
|
+
(
|
|
154
|
+
"non-dict item skipped",
|
|
155
|
+
'{"error":"partial write of line protocol occurred","data":[null,'
|
|
156
|
+
'{"error_message":"bad line","line_number":2,"original_line":"bad lp"}]}',
|
|
157
|
+
"partial write of line protocol occurred:\n"
|
|
158
|
+
"\tline 2: bad line (bad lp)",
|
|
159
|
+
),
|
|
160
|
+
# details empty -> return error_text
|
|
161
|
+
(
|
|
162
|
+
"no detail fields",
|
|
163
|
+
'{"error":"partial write of line protocol occurred","data":[{"line_number":2}]}',
|
|
164
|
+
"partial write of line protocol occurred",
|
|
165
|
+
),
|
|
166
|
+
# data is not a dict when resolving fallback keys
|
|
167
|
+
(
|
|
168
|
+
"data not dict for fallback",
|
|
169
|
+
'{"error":"data not list","data":"oops"}',
|
|
170
|
+
"data not list",
|
|
171
|
+
),
|
|
172
|
+
]
|
|
173
|
+
for name, response_body, expected in cases:
|
|
174
|
+
with self.subTest(name):
|
|
175
|
+
with self.assertRaises(InfluxDBError) as err:
|
|
176
|
+
self._test_api_error(response_body)
|
|
177
|
+
self.assertEqual(expected, err.exception.message)
|
|
178
|
+
|
|
127
179
|
def test_api_error_headers(self):
|
|
128
180
|
body = '{"error": "test error"}'
|
|
129
181
|
body_dic = json.loads(body)
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/tests/test_influxdb_client_3_integration.py
RENAMED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import pyarrow
|
|
4
|
-
import pytest
|
|
5
3
|
import random
|
|
6
4
|
import string
|
|
7
5
|
import time
|
|
8
6
|
import unittest
|
|
9
7
|
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import pyarrow
|
|
10
|
+
import pytest
|
|
10
11
|
from urllib3.exceptions import MaxRetryError, TimeoutError as Url3TimeoutError
|
|
11
12
|
|
|
12
13
|
from influxdb_client_3 import InfluxDBClient3, write_client_options, WriteOptions, \
|
|
13
14
|
WriteType, InfluxDB3ClientQueryError
|
|
15
|
+
from influxdb_client_3.write_client.rest import ApiException
|
|
14
16
|
from influxdb_client_3.exceptions import InfluxDBError
|
|
15
17
|
from tests.util import asyncio_run, lp_to_py_object
|
|
16
18
|
|
|
@@ -48,6 +50,68 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
|
|
|
48
50
|
if self.client:
|
|
49
51
|
self.client.close()
|
|
50
52
|
|
|
53
|
+
def test_write_dataframe(self):
|
|
54
|
+
measurement = f'test{random_hex(3)}'.lower()
|
|
55
|
+
df = pd.DataFrame({
|
|
56
|
+
'time': pd.to_datetime(['2024-01-01', '2024-01-02']),
|
|
57
|
+
'city': ['London', 'Paris'],
|
|
58
|
+
'temperature': [15.0, 18.5]
|
|
59
|
+
})
|
|
60
|
+
self.client.write_dataframe(df, measurement=measurement, timestamp_column='time', tags=['city'])
|
|
61
|
+
self.client.flush()
|
|
62
|
+
|
|
63
|
+
result = self.client.query(query=f"select * from {measurement}", mode="pandas")
|
|
64
|
+
|
|
65
|
+
self.assertIsNotNone(result)
|
|
66
|
+
self.assertEqual(2, len(result.get('city')))
|
|
67
|
+
self.assertEqual(2, len(result.get('temperature')))
|
|
68
|
+
|
|
69
|
+
def test_write_dataframe_with_batch(self):
|
|
70
|
+
self.client = InfluxDBClient3(host=self.host,
|
|
71
|
+
database=self.database,
|
|
72
|
+
token=self.token,
|
|
73
|
+
write_client_options=write_client_options(
|
|
74
|
+
write_options=WriteOptions(batch_size=100)
|
|
75
|
+
))
|
|
76
|
+
measurement = f'test{random_hex(3)}'.lower()
|
|
77
|
+
df = pd.DataFrame({
|
|
78
|
+
'time': pd.to_datetime(['2024-01-01', '2024-01-02']),
|
|
79
|
+
'city': ['London', 'Paris'],
|
|
80
|
+
'temperature': [15.0, 18.5]
|
|
81
|
+
})
|
|
82
|
+
self.client.write_dataframe(
|
|
83
|
+
df,
|
|
84
|
+
measurement=measurement,
|
|
85
|
+
timestamp_column='time',
|
|
86
|
+
tags=['city']
|
|
87
|
+
)
|
|
88
|
+
self.client.flush()
|
|
89
|
+
|
|
90
|
+
result = self.client.query(query=f"select * from {measurement}", mode="pandas")
|
|
91
|
+
|
|
92
|
+
self.assertIsNotNone(result)
|
|
93
|
+
self.assertEqual(2, len(result.get('city')))
|
|
94
|
+
self.assertEqual(2, len(result.get('temperature')))
|
|
95
|
+
|
|
96
|
+
def test_write_csv_file_with_batch(self):
|
|
97
|
+
client = InfluxDBClient3(host=self.host,
|
|
98
|
+
database=self.database,
|
|
99
|
+
token=self.token,
|
|
100
|
+
write_client_options=write_client_options(
|
|
101
|
+
write_options=WriteOptions(batch_size=100)
|
|
102
|
+
))
|
|
103
|
+
measurement = f'test{random_hex(3)}'.lower()
|
|
104
|
+
client.write_file(
|
|
105
|
+
measurement_name=measurement,
|
|
106
|
+
file='tests/data/iot.csv',
|
|
107
|
+
timestamp_column='time', tag_columns=["name"])
|
|
108
|
+
client.flush()
|
|
109
|
+
|
|
110
|
+
result = client.query(query=f"select * from {measurement}", mode="pandas")
|
|
111
|
+
self.assertIsNotNone(result)
|
|
112
|
+
self.assertEqual(3, len(result.get('building')))
|
|
113
|
+
self.assertEqual(3, len(result.get('temperature')))
|
|
114
|
+
|
|
51
115
|
def test_write_and_query(self):
|
|
52
116
|
test_id = time.time_ns()
|
|
53
117
|
self.client.write(f"integration_test_python,type=used value=123.0,test_id={test_id}i")
|
|
@@ -61,6 +125,39 @@ class TestInfluxDBClient3Integration(unittest.TestCase):
|
|
|
61
125
|
self.assertEqual(test_id, df['test_id'][0])
|
|
62
126
|
self.assertEqual(123.0, df['value'][0])
|
|
63
127
|
|
|
128
|
+
def test_v3_error(self):
|
|
129
|
+
measurement = f'test{random_hex(3)}'.lower()
|
|
130
|
+
lp = "\n".join([
|
|
131
|
+
f"{measurement} v=1i 1770291280",
|
|
132
|
+
f"{measurement} v=1 1770291281",
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
with InfluxDBClient3(
|
|
136
|
+
host=self.host,
|
|
137
|
+
database=self.database,
|
|
138
|
+
token=self.token,
|
|
139
|
+
write_client_options=write_client_options(
|
|
140
|
+
write_options=WriteOptions(
|
|
141
|
+
write_type=WriteType.synchronous,
|
|
142
|
+
no_sync=True
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
) as client:
|
|
146
|
+
try:
|
|
147
|
+
client.write(lp)
|
|
148
|
+
self.fail("Expected InfluxDBError from invalid line protocol.")
|
|
149
|
+
except ApiException as err:
|
|
150
|
+
if "Server doesn't support write with no_sync=true" in str(err):
|
|
151
|
+
self.skipTest("no_sync not supported by this server.")
|
|
152
|
+
msg = err.message
|
|
153
|
+
self.assertIn("partial write of line protocol occurred", msg)
|
|
154
|
+
self.assertIn((
|
|
155
|
+
"invalid column type for column 'v', expected iox::column_type::field::integer, "
|
|
156
|
+
"got iox::column_type::field::float"
|
|
157
|
+
), msg)
|
|
158
|
+
self.assertIn("line 2", msg)
|
|
159
|
+
self.assertIn(measurement, msg)
|
|
160
|
+
|
|
64
161
|
def test_auth_error_token(self):
|
|
65
162
|
self.client = InfluxDBClient3(host=self.host, database=self.database, token='fake token')
|
|
66
163
|
test_id = time.time_ns()
|
|
@@ -96,6 +96,4 @@ class TestWritePolars(unittest.TestCase):
|
|
|
96
96
|
async_req=ANY,
|
|
97
97
|
content_type=ANY,
|
|
98
98
|
urlopen_kw=ANY,
|
|
99
|
-
data_frame_measurement_name='measurement',
|
|
100
|
-
data_frame_timestamp_column='time',
|
|
101
99
|
body=b'measurement temperature=22.4 1722470400000000000\nmeasurement temperature=21.8 1722474000000000000')
|
|
@@ -12,7 +12,7 @@ from pyarrow.flight import (
|
|
|
12
12
|
Ticket
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
from influxdb_client_3 import InfluxDBClient3
|
|
15
|
+
from influxdb_client_3 import InfluxDBClient3, flight_client_options
|
|
16
16
|
from influxdb_client_3.query.query_api import QueryApiOptionsBuilder, QueryApi
|
|
17
17
|
from influxdb_client_3.version import USER_AGENT
|
|
18
18
|
from tests.util import asyncio_run
|
|
@@ -25,7 +25,8 @@ from tests.util.mocks import (
|
|
|
25
25
|
HeaderCheckServerMiddlewareFactory,
|
|
26
26
|
NoopAuthHandler,
|
|
27
27
|
get_req_headers,
|
|
28
|
-
set_req_headers
|
|
28
|
+
set_req_headers, ModifyHeaderClientMiddlewareFactory,
|
|
29
|
+
HeaderCheckServerMiddlewareFactory1
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
|
|
@@ -175,11 +176,13 @@ Aw==
|
|
|
175
176
|
cert_chain = 'mTLS_explicit_chain'
|
|
176
177
|
self.create_cert_file(cert_file)
|
|
177
178
|
test_flight_client_options = {'private_key': private_key, 'cert_chain': cert_chain}
|
|
179
|
+
middleware = [ModifyHeaderClientMiddlewareFactory()]
|
|
178
180
|
options = QueryApiOptionsBuilder()\
|
|
179
181
|
.proxy(proxy_name) \
|
|
180
182
|
.root_certs(cert_file) \
|
|
181
183
|
.tls_verify(False) \
|
|
182
184
|
.flight_client_options(test_flight_client_options) \
|
|
185
|
+
.middleware(middleware) \
|
|
183
186
|
.build()
|
|
184
187
|
|
|
185
188
|
client = QueryApi(connection,
|
|
@@ -195,6 +198,7 @@ Aw==
|
|
|
195
198
|
assert client._flight_client_options['private_key'] == private_key
|
|
196
199
|
assert client._flight_client_options['cert_chain'] == cert_chain
|
|
197
200
|
assert client._proxy == proxy_name
|
|
201
|
+
assert client._flight_client_options['middleware'] == middleware
|
|
198
202
|
fc_opts = client._flight_client_options
|
|
199
203
|
assert dict(fc_opts['generic_options'])['grpc.secondary_user_agent'].startswith('influxdb3-python/')
|
|
200
204
|
assert dict(fc_opts['generic_options'])['grpc.http_proxy'] == proxy_name
|
|
@@ -311,6 +315,41 @@ Aw==
|
|
|
311
315
|
assert _req_headers['authorization'] == [f"Bearer {token}"]
|
|
312
316
|
set_req_headers({})
|
|
313
317
|
|
|
318
|
+
def test_query_with_middleware_success(self):
|
|
319
|
+
with HeaderCheckFlightServer(
|
|
320
|
+
auth_handler=NoopAuthHandler(),
|
|
321
|
+
middleware={"check": HeaderCheckServerMiddlewareFactory1()}) as server:
|
|
322
|
+
|
|
323
|
+
middleware = [ModifyHeaderClientMiddlewareFactory()]
|
|
324
|
+
client = InfluxDBClient3(
|
|
325
|
+
host=f'http://localhost:{server.port}',
|
|
326
|
+
org='test_org',
|
|
327
|
+
databse='test_db',
|
|
328
|
+
token='TEST_TOKEN',
|
|
329
|
+
flight_client_options=flight_client_options(middleware=middleware)
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
df = client.query(query='SELECT * FROM test', mode="pandas")
|
|
333
|
+
self.assertIsNotNone(df)
|
|
334
|
+
|
|
335
|
+
def test_query_with_missing_middleware(self):
|
|
336
|
+
with HeaderCheckFlightServer(
|
|
337
|
+
auth_handler=NoopAuthHandler(),
|
|
338
|
+
middleware={"check": HeaderCheckServerMiddlewareFactory1()}) as server:
|
|
339
|
+
|
|
340
|
+
client = InfluxDBClient3(
|
|
341
|
+
host=f'http://localhost:{server.port}',
|
|
342
|
+
org='test_org',
|
|
343
|
+
databse='test_db',
|
|
344
|
+
token='TEST_TOKEN'
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
client.query(query='SELECT * FROM test', mode="pandas")
|
|
349
|
+
self.fail("Should have failed due to missing middleware")
|
|
350
|
+
except Exception as e:
|
|
351
|
+
assert "Invalid header value from middleware" in str(e)
|
|
352
|
+
|
|
314
353
|
@asyncio_run
|
|
315
354
|
async def test_query_async_pandas(self):
|
|
316
355
|
with ConstantFlightServer() as server:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb3_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/exceptions/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/__init__.py
RENAMED
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/_sync/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/_sync/rest.py
RENAMED
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/client/_base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/configuration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{influxdb3_python-0.17.0 → influxdb3_python-0.18.0}/influxdb_client_3/write_client/extras.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|