influxdb3-python 0.18.0__tar.gz → 0.19.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 → influxdb3_python-0.19.0}/PKG-INFO +28 -4
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/README.md +25 -1
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb3_python.egg-info/PKG-INFO +28 -4
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/version.py +1 -1
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/_base.py +2 -1
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/write/dataframe_serializer.py +7 -6
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/write/point.py +50 -4
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/write/polars_dataframe_serializer.py +33 -22
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/write_api.py +14 -2
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/domain/write_precision.py +5 -5
- influxdb3_python-0.19.0/pyproject.toml +3 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/setup.py +2 -2
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_api_client.py +0 -2
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_dataframe_serializer.py +24 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_influxdb_client_3.py +63 -5
- influxdb3_python-0.19.0/tests/test_point.py +67 -0
- influxdb3_python-0.19.0/tests/test_polars.py +213 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_query.py +4 -3
- influxdb3_python-0.18.0/pyproject.toml +0 -3
- influxdb3_python-0.18.0/tests/test_point.py +0 -14
- influxdb3_python-0.18.0/tests/test_polars.py +0 -99
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/basic_ssl_example.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/batching_example.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/cloud_dedicated_query.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/cloud_dedicated_write.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/config.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/flight_options_example.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/handle_http_error.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/handle_query_error.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/pandas_write.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/query_async.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/query_type.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/query_with_middleware.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/Examples/timeouts.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/LICENSE +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb3_python.egg-info/SOURCES.txt +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb3_python.egg-info/dependency_links.txt +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb3_python.egg-info/requires.txt +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb3_python.egg-info/top_level.txt +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/exceptions/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/exceptions/exceptions.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/py.typed +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/query/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/query/query_api.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/read_file.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/_sync/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/_sync/api_client.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/_sync/rest.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/influxdb_client.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/logging_handler.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/util/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/util/date_utils.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/util/date_utils_pandas.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/util/helpers.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/util/multiprocessing_helper.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/warnings.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/write/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/write/retry.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/configuration.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/domain/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/domain/write_precision_converter.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/extras.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/rest.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/service/__init__.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/service/_base_service.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/service/signin_service.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/service/signout_service.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/service/write_service.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/setup.cfg +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_date_helper.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_deep_merge.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_flush.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_influxdb_client_3_integration.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_merge_options.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_write_file.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_write_local_server.py +0 -0
- {influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/tests/test_write_precision_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: influxdb3-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: Community Python client for InfluxDB 3.0
|
|
5
5
|
Home-page: https://github.com/InfluxCommunity/influxdb3-python
|
|
6
6
|
Author: InfluxData
|
|
@@ -8,13 +8,13 @@ Author-email: contact@influxdata.com
|
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: reactivex>=4.0.4
|
|
@@ -98,7 +98,7 @@ pip install influxdb3-python
|
|
|
98
98
|
|
|
99
99
|
Note: This does not include Pandas support. If you would like to use key features such as `to_pandas()` and `write_file()` you will need to install `pandas` separately.
|
|
100
100
|
|
|
101
|
-
*Note: Please make sure you are using 3.
|
|
101
|
+
*Note: Please make sure you are using 3.9 or above. For the best performance use 3.11+*
|
|
102
102
|
|
|
103
103
|
# Usage
|
|
104
104
|
One of the easiest ways to get started is to checkout the ["Pokemon Trainer Cookbook"](https://github.com/InfluxCommunity/influxdb3-python/blob/main/Examples/pokemon-trainer/cookbook.ipynb). This scenario takes you through the basics of both the client library and Pyarrow.
|
|
@@ -126,6 +126,30 @@ You can write data using the Point class, or supplying line protocol.
|
|
|
126
126
|
point = Point("measurement").tag("location", "london").field("temperature", 42)
|
|
127
127
|
client.write(point)
|
|
128
128
|
```
|
|
129
|
+
|
|
130
|
+
### Control tag order for first-write column order (InfluxDB 3 Enterprise)
|
|
131
|
+
```python
|
|
132
|
+
from influxdb_client_3 import InfluxDBClient3, Point, WriteOptions, WriteType, write_client_options
|
|
133
|
+
|
|
134
|
+
point = Point("cpu") \
|
|
135
|
+
.tag("host", "server-a") \
|
|
136
|
+
.tag("region", "us-east") \
|
|
137
|
+
.tag("rack", "r1") \
|
|
138
|
+
.field("usage", 0.42)
|
|
139
|
+
|
|
140
|
+
write_options = WriteOptions(
|
|
141
|
+
write_type=WriteType.synchronous,
|
|
142
|
+
tag_order=["region", "host"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
client = InfluxDBClient3(
|
|
146
|
+
token="your-token",
|
|
147
|
+
host="your-host",
|
|
148
|
+
database="your-database",
|
|
149
|
+
write_client_options=write_client_options(write_options=write_options),
|
|
150
|
+
)
|
|
151
|
+
client.write(point)
|
|
152
|
+
```
|
|
129
153
|
### Using Line Protocol
|
|
130
154
|
```python
|
|
131
155
|
point = "measurement fieldname=0"
|
|
@@ -49,7 +49,7 @@ pip install influxdb3-python
|
|
|
49
49
|
|
|
50
50
|
Note: This does not include Pandas support. If you would like to use key features such as `to_pandas()` and `write_file()` you will need to install `pandas` separately.
|
|
51
51
|
|
|
52
|
-
*Note: Please make sure you are using 3.
|
|
52
|
+
*Note: Please make sure you are using 3.9 or above. For the best performance use 3.11+*
|
|
53
53
|
|
|
54
54
|
# Usage
|
|
55
55
|
One of the easiest ways to get started is to checkout the ["Pokemon Trainer Cookbook"](https://github.com/InfluxCommunity/influxdb3-python/blob/main/Examples/pokemon-trainer/cookbook.ipynb). This scenario takes you through the basics of both the client library and Pyarrow.
|
|
@@ -77,6 +77,30 @@ You can write data using the Point class, or supplying line protocol.
|
|
|
77
77
|
point = Point("measurement").tag("location", "london").field("temperature", 42)
|
|
78
78
|
client.write(point)
|
|
79
79
|
```
|
|
80
|
+
|
|
81
|
+
### Control tag order for first-write column order (InfluxDB 3 Enterprise)
|
|
82
|
+
```python
|
|
83
|
+
from influxdb_client_3 import InfluxDBClient3, Point, WriteOptions, WriteType, write_client_options
|
|
84
|
+
|
|
85
|
+
point = Point("cpu") \
|
|
86
|
+
.tag("host", "server-a") \
|
|
87
|
+
.tag("region", "us-east") \
|
|
88
|
+
.tag("rack", "r1") \
|
|
89
|
+
.field("usage", 0.42)
|
|
90
|
+
|
|
91
|
+
write_options = WriteOptions(
|
|
92
|
+
write_type=WriteType.synchronous,
|
|
93
|
+
tag_order=["region", "host"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
client = InfluxDBClient3(
|
|
97
|
+
token="your-token",
|
|
98
|
+
host="your-host",
|
|
99
|
+
database="your-database",
|
|
100
|
+
write_client_options=write_client_options(write_options=write_options),
|
|
101
|
+
)
|
|
102
|
+
client.write(point)
|
|
103
|
+
```
|
|
80
104
|
### Using Line Protocol
|
|
81
105
|
```python
|
|
82
106
|
point = "measurement fieldname=0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: influxdb3-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: Community Python client for InfluxDB 3.0
|
|
5
5
|
Home-page: https://github.com/InfluxCommunity/influxdb3-python
|
|
6
6
|
Author: InfluxData
|
|
@@ -8,13 +8,13 @@ Author-email: contact@influxdata.com
|
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: reactivex>=4.0.4
|
|
@@ -98,7 +98,7 @@ pip install influxdb3-python
|
|
|
98
98
|
|
|
99
99
|
Note: This does not include Pandas support. If you would like to use key features such as `to_pandas()` and `write_file()` you will need to install `pandas` separately.
|
|
100
100
|
|
|
101
|
-
*Note: Please make sure you are using 3.
|
|
101
|
+
*Note: Please make sure you are using 3.9 or above. For the best performance use 3.11+*
|
|
102
102
|
|
|
103
103
|
# Usage
|
|
104
104
|
One of the easiest ways to get started is to checkout the ["Pokemon Trainer Cookbook"](https://github.com/InfluxCommunity/influxdb3-python/blob/main/Examples/pokemon-trainer/cookbook.ipynb). This scenario takes you through the basics of both the client library and Pyarrow.
|
|
@@ -126,6 +126,30 @@ You can write data using the Point class, or supplying line protocol.
|
|
|
126
126
|
point = Point("measurement").tag("location", "london").field("temperature", 42)
|
|
127
127
|
client.write(point)
|
|
128
128
|
```
|
|
129
|
+
|
|
130
|
+
### Control tag order for first-write column order (InfluxDB 3 Enterprise)
|
|
131
|
+
```python
|
|
132
|
+
from influxdb_client_3 import InfluxDBClient3, Point, WriteOptions, WriteType, write_client_options
|
|
133
|
+
|
|
134
|
+
point = Point("cpu") \
|
|
135
|
+
.tag("host", "server-a") \
|
|
136
|
+
.tag("region", "us-east") \
|
|
137
|
+
.tag("rack", "r1") \
|
|
138
|
+
.field("usage", 0.42)
|
|
139
|
+
|
|
140
|
+
write_options = WriteOptions(
|
|
141
|
+
write_type=WriteType.synchronous,
|
|
142
|
+
tag_order=["region", "host"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
client = InfluxDBClient3(
|
|
146
|
+
token="your-token",
|
|
147
|
+
host="your-host",
|
|
148
|
+
database="your-database",
|
|
149
|
+
write_client_options=write_client_options(write_options=write_options),
|
|
150
|
+
)
|
|
151
|
+
client.write(point)
|
|
152
|
+
```
|
|
129
153
|
### Using Line Protocol
|
|
130
154
|
```python
|
|
131
155
|
point = "measurement fieldname=0"
|
{influxdb3_python-0.18.0 → influxdb3_python-0.19.0}/influxdb_client_3/write_client/client/_base.py
RENAMED
|
@@ -246,7 +246,8 @@ class _BaseWriteApi(object):
|
|
|
246
246
|
elif isinstance(record, Point):
|
|
247
247
|
precision_from_point = kwargs.get('precision_from_point', True)
|
|
248
248
|
precision = record.write_precision if precision_from_point else write_precision
|
|
249
|
-
self._serialize(record.to_line_protocol(precision=precision
|
|
249
|
+
self._serialize(record.to_line_protocol(precision=precision, tag_order=kwargs.get('tag_order')),
|
|
250
|
+
precision, payload, **kwargs)
|
|
250
251
|
|
|
251
252
|
elif isinstance(record, dict):
|
|
252
253
|
self._serialize(Point.from_dict(record, write_precision=write_precision, **kwargs),
|
|
@@ -10,7 +10,7 @@ import re
|
|
|
10
10
|
|
|
11
11
|
from influxdb_client_3.write_client.domain import WritePrecision
|
|
12
12
|
from influxdb_client_3.write_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, _ESCAPE_MEASUREMENT, \
|
|
13
|
-
DEFAULT_WRITE_PRECISION
|
|
13
|
+
DEFAULT_WRITE_PRECISION, ordered_tag_keys
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger('influxdb_client.client.write.dataframe_serializer')
|
|
16
16
|
|
|
@@ -130,8 +130,8 @@ class DataframeSerializer:
|
|
|
130
130
|
|
|
131
131
|
# keys holds a list of string keys.
|
|
132
132
|
keys = []
|
|
133
|
-
#
|
|
134
|
-
|
|
133
|
+
# tag_segments holds map of tag key -> tag f-string segment.
|
|
134
|
+
tag_segments = {}
|
|
135
135
|
# fields holds a list of field f-string segments ordered alphabetically by field key
|
|
136
136
|
fields = []
|
|
137
137
|
# field_indexes holds the index into each row of all the fields.
|
|
@@ -188,7 +188,7 @@ class DataframeSerializer:
|
|
|
188
188
|
}}"""
|
|
189
189
|
else:
|
|
190
190
|
key_value = f',{key_format}={{str({val_format}).translate(_ESCAPE_KEY)}}'
|
|
191
|
-
|
|
191
|
+
tag_segments[key] = key_value
|
|
192
192
|
continue
|
|
193
193
|
elif timestamp_column is not None and key in timestamp_column:
|
|
194
194
|
timestamp_index = field_index
|
|
@@ -225,7 +225,8 @@ class DataframeSerializer:
|
|
|
225
225
|
|
|
226
226
|
measurement_name = str(data_frame_measurement_name).translate(_ESCAPE_MEASUREMENT)
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
tag_keys = ordered_tag_keys(list(tag_segments.keys()), kwargs.get('tag_order'))
|
|
229
|
+
tag_string = ''.join(tag_segments[tag_key] for tag_key in tag_keys)
|
|
229
230
|
fields = ''.join(fields)
|
|
230
231
|
timestamp = '{p[%s].value}' % timestamp_index
|
|
231
232
|
if precision == WritePrecision.US:
|
|
@@ -235,7 +236,7 @@ class DataframeSerializer:
|
|
|
235
236
|
elif precision == WritePrecision.S:
|
|
236
237
|
timestamp = '{int(p[%s].value / 1e9)}' % timestamp_index
|
|
237
238
|
|
|
238
|
-
f = eval(f'lambda p: f"""{{measurement_name}}{
|
|
239
|
+
f = eval(f'lambda p: f"""{{measurement_name}}{tag_string} {fields} {timestamp}"""', {
|
|
239
240
|
'measurement_name': measurement_name,
|
|
240
241
|
'_ESCAPE_KEY': _ESCAPE_KEY,
|
|
241
242
|
'_ESCAPE_STRING': _ESCAPE_STRING,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import math
|
|
4
4
|
import warnings
|
|
5
5
|
from builtins import int
|
|
6
|
+
from collections.abc import Iterable
|
|
6
7
|
from datetime import datetime, timedelta, timezone
|
|
7
8
|
from decimal import Decimal
|
|
8
9
|
from numbers import Integral
|
|
@@ -215,11 +216,12 @@ class Point(object):
|
|
|
215
216
|
self._fields[field] = value
|
|
216
217
|
return self
|
|
217
218
|
|
|
218
|
-
def to_line_protocol(self, precision=None):
|
|
219
|
+
def to_line_protocol(self, precision=None, tag_order=None):
|
|
219
220
|
"""
|
|
220
221
|
Create LineProtocol.
|
|
221
222
|
|
|
222
223
|
:param precision: required precision of LineProtocol. If it's not set then use the precision from ``Point``.
|
|
224
|
+
:param tag_order: optional list of tag names to prioritize in serialized output
|
|
223
225
|
"""
|
|
224
226
|
_measurement = _escape_key(self._name, _ESCAPE_MEASUREMENT)
|
|
225
227
|
if _measurement.startswith("#"):
|
|
@@ -229,7 +231,7 @@ The output Line protocol will be interpret as a comment by InfluxDB. For more in
|
|
|
229
231
|
- https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#comments
|
|
230
232
|
"""
|
|
231
233
|
warnings.warn(message, SyntaxWarning)
|
|
232
|
-
_tags = _append_tags(self._tags)
|
|
234
|
+
_tags = _append_tags(self._tags, tag_order)
|
|
233
235
|
_fields = _append_fields(self._fields, self._field_types)
|
|
234
236
|
if not _fields:
|
|
235
237
|
return ""
|
|
@@ -252,9 +254,10 @@ The output Line protocol will be interpret as a comment by InfluxDB. For more in
|
|
|
252
254
|
return self.to_line_protocol()
|
|
253
255
|
|
|
254
256
|
|
|
255
|
-
def _append_tags(tags):
|
|
257
|
+
def _append_tags(tags, tag_order=None):
|
|
256
258
|
_return = []
|
|
257
|
-
for tag_key
|
|
259
|
+
for tag_key in ordered_tag_keys(sorted(tags.keys()), tag_order):
|
|
260
|
+
tag_value = tags.get(tag_key)
|
|
258
261
|
|
|
259
262
|
if tag_value is None:
|
|
260
263
|
continue
|
|
@@ -267,6 +270,49 @@ def _append_tags(tags):
|
|
|
267
270
|
return f"{',' if _return else ''}{','.join(_return)} "
|
|
268
271
|
|
|
269
272
|
|
|
273
|
+
def sanitize_tag_order(tag_order):
|
|
274
|
+
if tag_order is None:
|
|
275
|
+
return []
|
|
276
|
+
|
|
277
|
+
if isinstance(tag_order, (str, bytes)):
|
|
278
|
+
raise TypeError("tag_order must be an iterable of strings, not str/bytes")
|
|
279
|
+
|
|
280
|
+
if not isinstance(tag_order, Iterable):
|
|
281
|
+
raise TypeError("tag_order must be an iterable of strings")
|
|
282
|
+
|
|
283
|
+
sanitized = []
|
|
284
|
+
seen = set()
|
|
285
|
+
for tag in tag_order:
|
|
286
|
+
if tag is None or tag == "":
|
|
287
|
+
continue
|
|
288
|
+
if not isinstance(tag, str):
|
|
289
|
+
raise TypeError("tag_order entries must be strings")
|
|
290
|
+
if tag in seen:
|
|
291
|
+
continue
|
|
292
|
+
seen.add(tag)
|
|
293
|
+
sanitized.append(tag)
|
|
294
|
+
return sanitized
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def ordered_tag_keys(existing_keys, tag_order=None):
|
|
298
|
+
ordered_keys = list(existing_keys)
|
|
299
|
+
if not tag_order:
|
|
300
|
+
return ordered_keys
|
|
301
|
+
|
|
302
|
+
remaining = set(ordered_keys)
|
|
303
|
+
prioritized = []
|
|
304
|
+
for tag_key in tag_order:
|
|
305
|
+
if not tag_key:
|
|
306
|
+
continue
|
|
307
|
+
if tag_key not in remaining:
|
|
308
|
+
continue
|
|
309
|
+
remaining.remove(tag_key)
|
|
310
|
+
prioritized.append(tag_key)
|
|
311
|
+
|
|
312
|
+
prioritized.extend([tag_key for tag_key in ordered_keys if tag_key in remaining])
|
|
313
|
+
return prioritized
|
|
314
|
+
|
|
315
|
+
|
|
270
316
|
def _append_fields(fields, field_types):
|
|
271
317
|
_return = []
|
|
272
318
|
|
|
@@ -7,7 +7,8 @@ Much of the code here is inspired by that in the aioinflux packet found here: ht
|
|
|
7
7
|
import logging
|
|
8
8
|
import math
|
|
9
9
|
|
|
10
|
-
from influxdb_client_3.write_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, DEFAULT_WRITE_PRECISION
|
|
10
|
+
from influxdb_client_3.write_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, DEFAULT_WRITE_PRECISION, \
|
|
11
|
+
ordered_tag_keys
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger('influxdb_client.client.write.polars_dataframe_serializer')
|
|
13
14
|
|
|
@@ -36,6 +37,7 @@ class PolarsDataframeSerializer:
|
|
|
36
37
|
self.chunk_size = chunk_size
|
|
37
38
|
self.measurement_name = kwargs.get("data_frame_measurement_name", "measurement")
|
|
38
39
|
self.tag_columns = kwargs.get("data_frame_tag_columns", [])
|
|
40
|
+
self.tag_order = kwargs.get("tag_order", None)
|
|
39
41
|
self.timestamp_column = kwargs.get("data_frame_timestamp_column", None)
|
|
40
42
|
self.timestamp_timezone = kwargs.get("data_frame_timestamp_timezone", None)
|
|
41
43
|
|
|
@@ -62,34 +64,43 @@ class PolarsDataframeSerializer:
|
|
|
62
64
|
return str(value).translate(_ESCAPE_STRING)
|
|
63
65
|
|
|
64
66
|
def to_line_protocol(self, row):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
tag_values = {}
|
|
68
|
+
tag_keys = []
|
|
69
|
+
for col in self.tag_columns:
|
|
70
|
+
value = row[self.column_indices[col]]
|
|
71
|
+
if value is None or value == "":
|
|
72
|
+
continue
|
|
73
|
+
if col not in tag_values:
|
|
74
|
+
tag_keys.append(col)
|
|
75
|
+
tag_values[col] = value
|
|
67
76
|
|
|
77
|
+
if self.point_settings.defaultTags:
|
|
78
|
+
for key, value in self.point_settings.defaultTags.items():
|
|
79
|
+
if value is None or value == "":
|
|
80
|
+
continue
|
|
81
|
+
if key in tag_values:
|
|
82
|
+
continue
|
|
83
|
+
tag_keys.append(key)
|
|
84
|
+
tag_values[key] = value
|
|
85
|
+
|
|
86
|
+
final_tag_keys = ordered_tag_keys(tag_keys, self.tag_order)
|
|
68
87
|
tags = ",".join(
|
|
69
|
-
f'{self.escape_key(
|
|
70
|
-
for
|
|
71
|
-
if row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
|
|
88
|
+
f'{self.escape_key(key)}={self.escape_key(tag_values[key])}'
|
|
89
|
+
for key in final_tag_keys
|
|
72
90
|
)
|
|
73
91
|
|
|
74
|
-
if self.point_settings.defaultTags:
|
|
75
|
-
default_tags = ",".join(
|
|
76
|
-
f'{self.escape_key(key)}={self.escape_key(value)}'
|
|
77
|
-
for key, value in self.point_settings.defaultTags.items()
|
|
78
|
-
)
|
|
79
|
-
# Ensure there's a comma between existing tags and default tags if both are present
|
|
80
|
-
if tags and default_tags:
|
|
81
|
-
tags += ","
|
|
82
|
-
tags += default_tags
|
|
83
|
-
|
|
84
92
|
# add escape symbols for special characters to tags
|
|
85
93
|
|
|
86
94
|
fields = ",".join(
|
|
87
|
-
f"{col}=\"{self.escape_value(row[self.column_indices[col]])}\"" if isinstance(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
f"{self.escape_key(col)}=\"{self.escape_value(row[self.column_indices[col]])}\"" if isinstance(
|
|
96
|
+
row[self.column_indices[col]],
|
|
97
|
+
str)
|
|
98
|
+
else f"{self.escape_key(col)}={str(row[self.column_indices[col]]).lower()}" if isinstance(
|
|
99
|
+
row[self.column_indices[col]],
|
|
100
|
+
bool) # Check for bool first
|
|
101
|
+
else f"{self.escape_key(col)}={row[self.column_indices[col]]}i" if isinstance(row[self.column_indices[col]],
|
|
102
|
+
int)
|
|
103
|
+
else f"{self.escape_key(col)}={row[self.column_indices[col]]}"
|
|
93
104
|
for col in self.column_indices
|
|
94
105
|
if col not in self.tag_columns + [self.timestamp_column] and
|
|
95
106
|
row[self.column_indices[col]] is not None and row[self.column_indices[col]] != ""
|
|
@@ -22,7 +22,7 @@ from reactivex.subject import Subject
|
|
|
22
22
|
from influxdb_client_3.write_client.client._base import _BaseWriteApi, _HAS_DATACLASS
|
|
23
23
|
from influxdb_client_3.write_client.client.util.helpers import get_org_query_param
|
|
24
24
|
from influxdb_client_3.write_client.client.write.dataframe_serializer import DataframeSerializer
|
|
25
|
-
from influxdb_client_3.write_client.client.write.point import Point, DEFAULT_WRITE_PRECISION
|
|
25
|
+
from influxdb_client_3.write_client.client.write.point import Point, DEFAULT_WRITE_PRECISION, sanitize_tag_order
|
|
26
26
|
from influxdb_client_3.write_client.client.write.retry import WritesRetry
|
|
27
27
|
from influxdb_client_3.write_client.domain import WritePrecision
|
|
28
28
|
from influxdb_client_3.write_client.rest import _UTF_8_encoding
|
|
@@ -43,6 +43,8 @@ SERIALIZER_KWARGS = {
|
|
|
43
43
|
'record_time_key',
|
|
44
44
|
'record_tag_keys',
|
|
45
45
|
'record_field_keys',
|
|
46
|
+
# Point serialization-specific kwargs
|
|
47
|
+
'tag_order',
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
logger = logging.getLogger('influxdb_client_3.write_client.client.write_api')
|
|
@@ -81,6 +83,7 @@ class WriteOptions(object):
|
|
|
81
83
|
max_close_wait=300_000,
|
|
82
84
|
write_precision=DEFAULT_WRITE_PRECISION,
|
|
83
85
|
no_sync=DEFAULT_WRITE_NO_SYNC,
|
|
86
|
+
tag_order=None,
|
|
84
87
|
timeout=DEFAULT_WRITE_TIMEOUT,
|
|
85
88
|
write_scheduler=ThreadPoolScheduler(max_workers=1)) -> None:
|
|
86
89
|
"""
|
|
@@ -100,6 +103,7 @@ class WriteOptions(object):
|
|
|
100
103
|
:param max_close_wait: the maximum time to wait for writes to be flushed if close() is called
|
|
101
104
|
:param write_precision: precision to use when writing points to InfluxDB
|
|
102
105
|
:param no_sync: skip waiting for WAL persistence on write
|
|
106
|
+
:param tag_order: optional list of tag names used to prioritize tag serialization order
|
|
103
107
|
:param timeout: timeout to use when writing to the database in milliseconds. Default is 10_000
|
|
104
108
|
:param write_scheduler:
|
|
105
109
|
"""
|
|
@@ -117,6 +121,7 @@ class WriteOptions(object):
|
|
|
117
121
|
self.write_precision = write_precision
|
|
118
122
|
self.timeout = timeout
|
|
119
123
|
self.no_sync = no_sync
|
|
124
|
+
self.tag_order = sanitize_tag_order(tag_order)
|
|
120
125
|
|
|
121
126
|
def to_retry_strategy(self, **kwargs):
|
|
122
127
|
"""
|
|
@@ -380,6 +385,11 @@ class WriteApi(_BaseWriteApi):
|
|
|
380
385
|
if write_precision is None:
|
|
381
386
|
write_precision = self._write_options.write_precision
|
|
382
387
|
|
|
388
|
+
if 'tag_order' in kwargs:
|
|
389
|
+
kwargs['tag_order'] = sanitize_tag_order(kwargs.get('tag_order'))
|
|
390
|
+
else:
|
|
391
|
+
kwargs['tag_order'] = self._write_options.tag_order
|
|
392
|
+
|
|
383
393
|
if self._write_options.write_type is WriteType.batching:
|
|
384
394
|
return self._write_batching(bucket, org, record,
|
|
385
395
|
write_precision, **kwargs)
|
|
@@ -520,7 +530,9 @@ class WriteApi(_BaseWriteApi):
|
|
|
520
530
|
precision, **kwargs)
|
|
521
531
|
|
|
522
532
|
elif isinstance(data, Point):
|
|
523
|
-
self._write_batching(bucket, org,
|
|
533
|
+
self._write_batching(bucket, org,
|
|
534
|
+
data.to_line_protocol(tag_order=kwargs.get('tag_order')),
|
|
535
|
+
data.write_precision, **kwargs)
|
|
524
536
|
|
|
525
537
|
elif isinstance(data, dict):
|
|
526
538
|
self._write_batching(bucket, org, Point.from_dict(data, write_precision=precision, **kwargs),
|
|
@@ -30,7 +30,7 @@ class WritePrecision(object):
|
|
|
30
30
|
def __init__(self): # noqa: E501,D401,D403
|
|
31
31
|
"""WritePrecision - a model defined in OpenAPI.""" # noqa: E501 self.discriminator = None
|
|
32
32
|
|
|
33
|
-
def to_dict(self):
|
|
33
|
+
def to_dict(self): # pragma: no cover
|
|
34
34
|
"""Return the model properties as a dict."""
|
|
35
35
|
result = {}
|
|
36
36
|
|
|
@@ -54,21 +54,21 @@ class WritePrecision(object):
|
|
|
54
54
|
|
|
55
55
|
return result
|
|
56
56
|
|
|
57
|
-
def to_str(self):
|
|
57
|
+
def to_str(self): # pragma: no cover
|
|
58
58
|
"""Return the string representation of the model."""
|
|
59
59
|
return pprint.pformat(self.to_dict())
|
|
60
60
|
|
|
61
|
-
def __repr__(self):
|
|
61
|
+
def __repr__(self): # pragma: no cover
|
|
62
62
|
"""For `print` and `pprint`."""
|
|
63
63
|
return self.to_str()
|
|
64
64
|
|
|
65
|
-
def __eq__(self, other):
|
|
65
|
+
def __eq__(self, other): # pragma: no cover
|
|
66
66
|
"""Return true if both objects are equal."""
|
|
67
67
|
if not isinstance(other, WritePrecision):
|
|
68
68
|
return False
|
|
69
69
|
|
|
70
70
|
return self.__dict__ == other.__dict__
|
|
71
71
|
|
|
72
|
-
def __ne__(self, other):
|
|
72
|
+
def __ne__(self, other): # pragma: no cover
|
|
73
73
|
"""Return true if both objects are not equal."""
|
|
74
74
|
return not self == other
|
|
@@ -60,16 +60,16 @@ setup(
|
|
|
60
60
|
]
|
|
61
61
|
},
|
|
62
62
|
install_requires=requires,
|
|
63
|
-
python_requires='>=3.
|
|
63
|
+
python_requires='>=3.9',
|
|
64
64
|
classifiers=[
|
|
65
65
|
'Development Status :: 4 - Beta',
|
|
66
66
|
'Intended Audience :: Developers',
|
|
67
67
|
'License :: OSI Approved :: MIT License',
|
|
68
|
-
'Programming Language :: Python :: 3.8',
|
|
69
68
|
'Programming Language :: Python :: 3.9',
|
|
70
69
|
'Programming Language :: Python :: 3.10',
|
|
71
70
|
'Programming Language :: Python :: 3.11',
|
|
72
71
|
'Programming Language :: Python :: 3.12',
|
|
73
72
|
'Programming Language :: Python :: 3.13',
|
|
73
|
+
'Programming Language :: Python :: 3.14',
|
|
74
74
|
]
|
|
75
75
|
)
|
|
@@ -56,7 +56,6 @@ class ApiClientTests(unittest.TestCase):
|
|
|
56
56
|
return response.HTTPResponse(status=200, version=4, reason="OK", decode_content=False, request_url=url)
|
|
57
57
|
|
|
58
58
|
def test_default_headers(self):
|
|
59
|
-
global _package
|
|
60
59
|
conf = Configuration()
|
|
61
60
|
client = ApiClient(conf,
|
|
62
61
|
header_name="Authorization",
|
|
@@ -69,7 +68,6 @@ class ApiClientTests(unittest.TestCase):
|
|
|
69
68
|
@mock.patch("influxdb_client_3.write_client._sync.rest.RESTClientObject.request",
|
|
70
69
|
side_effect=mock_rest_request)
|
|
71
70
|
def test_call_api(self, mock_post):
|
|
72
|
-
global _package
|
|
73
71
|
global _sentHeaders
|
|
74
72
|
_sentHeaders = {}
|
|
75
73
|
|
|
@@ -276,6 +276,30 @@ class TestDataFrameSerializer(unittest.TestCase):
|
|
|
276
276
|
self.assertEqual(1, len(points))
|
|
277
277
|
self.assertEqual("h2o,a=a,b=b,c=c level=2i 1586048400000000000", points[0])
|
|
278
278
|
|
|
279
|
+
points = data_frame_to_list_of_points(data_frame=data_frame,
|
|
280
|
+
point_settings=PointSettings(),
|
|
281
|
+
data_frame_measurement_name='h2o',
|
|
282
|
+
data_frame_tag_columns={"c", "a", "b"},
|
|
283
|
+
tag_order=["c", "a"])
|
|
284
|
+
|
|
285
|
+
self.assertEqual(1, len(points))
|
|
286
|
+
self.assertEqual("h2o,c=c,a=a,b=b level=2i 1586048400000000000", points[0])
|
|
287
|
+
|
|
288
|
+
ps = PointSettings(z="from-default", c="override-ignored")
|
|
289
|
+
points_with_defaults = data_frame_to_list_of_points(
|
|
290
|
+
data_frame=data_frame,
|
|
291
|
+
point_settings=ps,
|
|
292
|
+
data_frame_measurement_name='h2o',
|
|
293
|
+
data_frame_tag_columns={"c", "a", "b"},
|
|
294
|
+
tag_order=["z", "c", "a"],
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
self.assertEqual(1, len(points_with_defaults))
|
|
298
|
+
self.assertEqual(
|
|
299
|
+
"h2o,z=from-default,c=c,a=a,b=b level=2i 1586048400000000000",
|
|
300
|
+
points_with_defaults[0]
|
|
301
|
+
)
|
|
302
|
+
|
|
279
303
|
def test_escape_text_value(self):
|
|
280
304
|
now = pd.Timestamp('2020-04-05 00:00+00:00')
|
|
281
305
|
an_hour_ago = now - timedelta(hours=1)
|