From 70ce8cb765ec742a96b665e6d41c97e7cd59ba8a Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Fri, 13 Sep 2024 09:01:22 +0200 Subject: [PATCH 1/5] chore(release): prepare for next development iteration --- CHANGELOG.md | 2 ++ conda/meta.yaml | 6 +++--- influxdb_client/version.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 742634f7..ebaccef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.47.0 [unreleased] + ## 1.46.0 [2024-09-13] ### Bug Fixes diff --git a/conda/meta.yaml b/conda/meta.yaml index 0c626075..2595c51e 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,5 +1,5 @@ {% set name = "influxdb_client" %} -{% set version = "1.45.0" %} +{% set version = "1.46.0" %} package: @@ -7,8 +7,8 @@ package: version: {{ version }} source: - url: https://files.pythonhosted.org/packages/71/cd/a016f327d0669074526b36ae7c1bb84760e3c0d29911f6e8e4046a217f32/influxdb_client-1.45.0.tar.gz - sha256: e24aa0a838f58487b2382c654fa8183fb5ca504af70438a42ca20dd79669a2be + url: https://files.pythonhosted.org/packages/53/9e/4bd499eff06eab47f7995178623d508703d2b4fedab1a3544b04ef06fb0c/influxdb_client-1.46.0.tar.gz + sha256: d5b5f3787db8ad75e64bf069fdc4d441e43b1a1d57f2c11082af309ef0b9722c build: number: 0 diff --git a/influxdb_client/version.py b/influxdb_client/version.py index 413edc0d..76745307 100644 --- a/influxdb_client/version.py +++ b/influxdb_client/version.py @@ -1,3 +1,3 @@ """Version of the Client that is used in User-Agent header.""" -VERSION = '1.46.0' +VERSION = '1.47.0dev0' From 3d70dbd66e434d5ac58e8d018e566d3089223db3 Mon Sep 17 00:00:00 2001 From: Vitaly Chait Date: Tue, 24 Sep 2024 15:24:49 +0300 Subject: [PATCH 2/5] fix: url attribute type validation (#672) --- CHANGELOG.md | 3 +++ influxdb_client/client/_base.py | 2 ++ tests/test_InfluxDBClient.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebaccef3..6937e242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 1.47.0 [unreleased] +### Bug Fixes +1. [#672](https://github.com/influxdata/influxdb-client-python/pull/672): Adding type validation to url attribute in client object + ## 1.46.0 [2024-09-13] ### Bug Fixes diff --git a/influxdb_client/client/_base.py b/influxdb_client/client/_base.py index 8dcf75e9..d4f17901 100644 --- a/influxdb_client/client/_base.py +++ b/influxdb_client/client/_base.py @@ -53,6 +53,8 @@ def __init__(self, url, token, debug=None, timeout=10_000, enable_gzip=False, or self.default_tags = default_tags self.conf = _Configuration() + if not isinstance(self.url, str): + raise ValueError('"url" attribute is not str instance') if self.url.endswith("/"): self.conf.host = self.url[:-1] else: diff --git a/tests/test_InfluxDBClient.py b/tests/test_InfluxDBClient.py index 7fdf834f..228f391b 100644 --- a/tests/test_InfluxDBClient.py +++ b/tests/test_InfluxDBClient.py @@ -323,6 +323,35 @@ def test_version(self): version = self.client.version() self.assertTrue(len(version) > 0) + def test_url_attribute(self): + # Wrong URL attribute + wrong_types = [ + None, + True, False, + 123, 123.5, + dict({"url" : "http://localhost:8086"}), + list(["http://localhost:8086"]), + tuple(("http://localhost:8086")) + ] + correct_types = [ + "http://localhost:8086" + ] + for url_type in wrong_types: + try: + client_not_running = InfluxDBClient(url=url_type, token="my-token", debug=True) + status = True + except ValueError as e: + status = False + self.assertFalse(status) + for url_type in correct_types: + try: + client_not_running = InfluxDBClient(url=url_type, token="my-token", debug=True) + status = True + except ValueError as e: + status = False + self.assertTrue(status) + + def test_build(self): build = self.client.build() self.assertEqual('oss', build.lower()) From 28a4a048345f18d7dcc64ed85e5b9fe91899f0a9 Mon Sep 17 00:00:00 2001 From: Daniel <158782574+youarecode@users.noreply.github.com> Date: Tue, 8 Oct 2024 05:44:24 -0300 Subject: [PATCH 3/5] fix: type linting at client.flux_table.FluxTable (#677) --- CHANGELOG.md | 3 ++- influxdb_client/client/flux_table.py | 4 ++-- setup.py | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6937e242..1511f9f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## 1.47.0 [unreleased] ### Bug Fixes -1. [#672](https://github.com/influxdata/influxdb-client-python/pull/672): Adding type validation to url attribute in client object +1. [#672](https://github.com/influxdata/influxdb-client-python/pull/672): Add type validation to url attribute in client object +1. [#674](https://github.com/influxdata/influxdb-client-python/pull/674): Add type linting to client.flux_table.FluxTable, remove duplicated `from pathlib import Path` at setup.py ## 1.46.0 [2024-09-13] diff --git a/influxdb_client/client/flux_table.py b/influxdb_client/client/flux_table.py index 98a83159..5fd9a061 100644 --- a/influxdb_client/client/flux_table.py +++ b/influxdb_client/client/flux_table.py @@ -46,8 +46,8 @@ class FluxTable(FluxStructure): def __init__(self) -> None: """Initialize defaults.""" - self.columns = [] - self.records = [] + self.columns: List[FluxColumn] = [] + self.records: List[FluxRecord] = [] def get_group_key(self): """ diff --git a/setup.py b/setup.py index 6d4a34cb..cda0d087 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,6 @@ 'aiocsv>=1.2.2' ] -from pathlib import Path this_directory = Path(__file__).parent long_description = (this_directory / "README.md").read_text() From 7e01edbe6ce61e4c4bdd18addf8c3a64f32385a3 Mon Sep 17 00:00:00 2001 From: karel-rehor Date: Wed, 9 Oct 2024 09:57:10 +0200 Subject: [PATCH 4/5] fix: async write prec where DEFAULT_PRECISION should not be used (#675) * fix: (WIP) issue 669 write precision to default in async API * chore: fix lint issues * docs: update CHANGELOG.md * chore: improve indexing of range --- CHANGELOG.md | 6 +- influxdb_client/client/write_api_async.py | 27 ++-- tests/test_InfluxDBClientAsync.py | 149 ++++++++++++++++++++-- 3 files changed, 159 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1511f9f9..e7d08c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ ## 1.47.0 [unreleased] ### Bug Fixes -1. [#672](https://github.com/influxdata/influxdb-client-python/pull/672): Add type validation to url attribute in client object -1. [#674](https://github.com/influxdata/influxdb-client-python/pull/674): Add type linting to client.flux_table.FluxTable, remove duplicated `from pathlib import Path` at setup.py + +1. [#672](https://github.com/influxdata/influxdb-client-python/pull/672): Adding type validation to url attribute in client object +2. [#674](https://github.com/influxdata/influxdb-client-python/pull/674): Add type linting to client.flux_table.FluxTable, remove duplicated `from pathlib import Path` at setup.py +3. [#675](https://github.com/influxdata/influxdb-client-python/pull/675): Ensures WritePrecision in Point is preferred to `DEFAULT_PRECISION` ## 1.46.0 [2024-09-13] diff --git a/influxdb_client/client/write_api_async.py b/influxdb_client/client/write_api_async.py index e9e2018b..38937eca 100644 --- a/influxdb_client/client/write_api_async.py +++ b/influxdb_client/client/write_api_async.py @@ -1,5 +1,6 @@ """Collect and async write time series data to InfluxDB Cloud or InfluxDB OSS.""" import logging +from asyncio import ensure_future, gather from collections import defaultdict from typing import Union, Iterable, NamedTuple @@ -114,12 +115,20 @@ async def write(self, bucket: str, org: str = None, self._append_default_tags(record) payloads = defaultdict(list) - self._serialize(record, write_precision, payloads, precision_from_point=False, **kwargs) - - # joint list by \n - body = b'\n'.join(payloads[write_precision]) - response = await self._write_service.post_write_async(org=org, bucket=bucket, body=body, - precision=write_precision, async_req=False, - _return_http_data_only=False, - content_type="text/plain; charset=utf-8") - return response[1] in (201, 204) + self._serialize(record, write_precision, payloads, precision_from_point=True, **kwargs) + + futures = [] + for payload_precision, payload_line in payloads.items(): + futures.append(ensure_future + (self._write_service.post_write_async(org=org, bucket=bucket, + body=b'\n'.join(payload_line), + precision=payload_precision, async_req=False, + _return_http_data_only=False, + content_type="text/plain; charset=utf-8"))) + + results = await gather(*futures, return_exceptions=True) + for result in results: + if isinstance(result, Exception): + raise result + + return False not in [re[1] in (201, 204) for re in results] diff --git a/tests/test_InfluxDBClientAsync.py b/tests/test_InfluxDBClientAsync.py index 7f8c6214..cb0586b9 100644 --- a/tests/test_InfluxDBClientAsync.py +++ b/tests/test_InfluxDBClientAsync.py @@ -1,11 +1,15 @@ import asyncio +import dateutil.parser import logging +import math import re +import time import unittest import os from datetime import datetime, timezone from io import StringIO +import pandas import pytest import warnings from aioresponses import aioresponses @@ -199,30 +203,151 @@ async def test_write_empty_data(self): self.assertEqual(True, response) + def gen_fractional_utc(self, nano, precision) -> str: + raw_sec = nano / 1_000_000_000 + if precision == WritePrecision.NS: + rem = f"{nano % 1_000_000_000}".rjust(9,"0").rstrip("0") + return (datetime.fromtimestamp(math.floor(raw_sec), tz=timezone.utc) + .isoformat() + .replace("+00:00", "") + f".{rem}Z") + #f".{rem}Z")) + elif precision == WritePrecision.US: + # rem = f"{round(nano / 1_000) % 1_000_000}"#.ljust(6,"0") + return (datetime.fromtimestamp(round(raw_sec,6), tz=timezone.utc) + .isoformat() + .replace("+00:00","") + .strip("0") + "Z" + ) + elif precision == WritePrecision.MS: + #rem = f"{round(nano / 1_000_000) % 1_000}".rjust(3, "0") + return (datetime.fromtimestamp(round(raw_sec,3), tz=timezone.utc) + .isoformat() + .replace("+00:00","") + .strip("0") + "Z" + ) + elif precision == WritePrecision.S: + return (datetime.fromtimestamp(round(raw_sec), tz=timezone.utc) + .isoformat() + .replace("+00:00","Z")) + else: + raise ValueError(f"Unknown precision: {precision}") + + @async_test async def test_write_points_different_precision(self): + now_ns = time.time_ns() + now_us = now_ns / 1_000 + now_ms = now_us / 1_000 + now_s = now_ms / 1_000 + + now_date_s = self.gen_fractional_utc(now_ns, WritePrecision.S) + now_date_ms = self.gen_fractional_utc(now_ns, WritePrecision.MS) + now_date_us = self.gen_fractional_utc(now_ns, WritePrecision.US) + now_date_ns = self.gen_fractional_utc(now_ns, WritePrecision.NS) + + points = { + WritePrecision.S: [], + WritePrecision.MS: [], + WritePrecision.US: [], + WritePrecision.NS: [] + } + + expected = {} + measurement = generate_name("measurement") - _point1 = Point(measurement).tag("location", "Prague").field("temperature", 25.3) \ - .time(datetime.fromtimestamp(0, tz=timezone.utc), write_precision=WritePrecision.S) - _point2 = Point(measurement).tag("location", "New York").field("temperature", 24.3) \ - .time(datetime.fromtimestamp(1, tz=timezone.utc), write_precision=WritePrecision.MS) - _point3 = Point(measurement).tag("location", "Berlin").field("temperature", 24.3) \ - .time(datetime.fromtimestamp(2, tz=timezone.utc), write_precision=WritePrecision.NS) - await self.client.write_api().write(bucket="my-bucket", record=[_point1, _point2, _point3], + # basic date-time value + points[WritePrecision.S].append(Point(measurement).tag("method", "SecDateTime").field("temperature", 25.3) \ + .time(datetime.fromtimestamp(round(now_s), tz=timezone.utc), write_precision=WritePrecision.S)) + expected['SecDateTime'] = now_date_s + points[WritePrecision.MS].append(Point(measurement).tag("method", "MilDateTime").field("temperature", 24.3) \ + .time(datetime.fromtimestamp(round(now_s,3), tz=timezone.utc), write_precision=WritePrecision.MS)) + expected['MilDateTime'] = now_date_ms + points[WritePrecision.US].append(Point(measurement).tag("method", "MicDateTime").field("temperature", 24.3) \ + .time(datetime.fromtimestamp(round(now_s,6), tz=timezone.utc), write_precision=WritePrecision.US)) + expected['MicDateTime'] = now_date_us + # N.B. datetime does not handle nanoseconds +# points[WritePrecision.NS].append(Point(measurement).tag("method", "NanDateTime").field("temperature", 24.3) \ +# .time(datetime.fromtimestamp(now_s, tz=timezone.utc), write_precision=WritePrecision.NS)) + + # long timestamps based on POSIX time + points[WritePrecision.S].append(Point(measurement).tag("method", "SecPosix").field("temperature", 24.3) \ + .time(round(now_s), write_precision=WritePrecision.S)) + expected['SecPosix'] = now_date_s + points[WritePrecision.MS].append(Point(measurement).tag("method", "MilPosix").field("temperature", 24.3) \ + .time(round(now_ms), write_precision=WritePrecision.MS)) + expected['MilPosix'] = now_date_ms + points[WritePrecision.US].append(Point(measurement).tag("method", "MicPosix").field("temperature", 24.3) \ + .time(round(now_us), write_precision=WritePrecision.US)) + expected['MicPosix'] = now_date_us + points[WritePrecision.NS].append(Point(measurement).tag("method", "NanPosix").field("temperature", 24.3) \ + .time(now_ns, write_precision=WritePrecision.NS)) + expected['NanPosix'] = now_date_ns + + # ISO Zulu datetime with ms, us and ns e.g. "2024-09-27T13:17:16.412399728Z" + points[WritePrecision.S].append(Point(measurement).tag("method", "SecDTZulu").field("temperature", 24.3) \ + .time(now_date_s, write_precision=WritePrecision.S)) + expected['SecDTZulu'] = now_date_s + points[WritePrecision.MS].append(Point(measurement).tag("method", "MilDTZulu").field("temperature", 24.3) \ + .time(now_date_ms, write_precision=WritePrecision.MS)) + expected['MilDTZulu'] = now_date_ms + points[WritePrecision.US].append(Point(measurement).tag("method", "MicDTZulu").field("temperature", 24.3) \ + .time(now_date_us, write_precision=WritePrecision.US)) + expected['MicDTZulu'] = now_date_us + # This keeps resulting in micro second resolution in response +# points[WritePrecision.NS].append(Point(measurement).tag("method", "NanDTZulu").field("temperature", 24.3) \ +# .time(now_date_ns, write_precision=WritePrecision.NS)) + + recs = [x for x in [v for v in points.values()]] + + await self.client.write_api().write(bucket="my-bucket", record=recs, write_precision=WritePrecision.NS) query = f''' from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r["_measurement"] == "{measurement}") - |> keep(columns: ["_time"]) + |> keep(columns: ["method","_time"]) ''' query_api = self.client.query_api() + # ensure calls fully processed on server + await asyncio.sleep(1) + raw = await query_api.query_raw(query) - self.assertEqual(8, len(raw.splitlines())) - self.assertEqual(',,0,1970-01-01T00:00:02Z', raw.splitlines()[4]) - self.assertEqual(',,0,1970-01-01T00:00:01Z', raw.splitlines()[5]) - self.assertEqual(',,0,1970-01-01T00:00:00Z', raw.splitlines()[6]) + linesRaw = raw.splitlines()[4:] + + lines = [] + for lnr in linesRaw: + lines.append(lnr[2:].split(",")) + + def get_time_for_method(lines, method): + for l in lines: + if l[2] == method: + return l[1] + return "" + + self.assertEqual(15, len(raw.splitlines())) + + for key in expected: + t = get_time_for_method(lines,key) + comp_time = dateutil.parser.isoparse(get_time_for_method(lines,key)) + target_time = dateutil.parser.isoparse(expected[key]) + self.assertEqual(target_time.date(), comp_time.date()) + self.assertEqual(target_time.hour, comp_time.hour) + self.assertEqual(target_time.second,comp_time.second) + dif = abs(target_time.microsecond - comp_time.microsecond) + if key[:3] == "Sec": + # Already tested + pass + elif key[:3] == "Mil": + # may be slight rounding differences + self.assertLess(dif, 1500, f"failed to match timestamp for {key} {target_time} != {comp_time}") + elif key[:3] == "Mic": + # may be slight rounding differences + self.assertLess(dif, 150, f"failed to match timestamp for {key} {target_time} != {comp_time}") + elif key[:3] == "Nan": + self.assertEqual(expected[key], get_time_for_method(lines, key)) + else: + raise Exception(f"Unhandled key {key}") @async_test async def test_delete_api(self): From 06b71146b20d2f3e7d40eac4fe5d2d81e4b02c62 Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Tue, 22 Oct 2024 07:56:31 +0200 Subject: [PATCH 5/5] chore(release): release version 1.47.0 [skip CI] --- CHANGELOG.md | 2 +- influxdb_client/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d08c81..727c6ea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.47.0 [unreleased] +## 1.47.0 [2024-10-22] ### Bug Fixes diff --git a/influxdb_client/version.py b/influxdb_client/version.py index 76745307..19339327 100644 --- a/influxdb_client/version.py +++ b/influxdb_client/version.py @@ -1,3 +1,3 @@ """Version of the Client that is used in User-Agent header.""" -VERSION = '1.47.0dev0' +VERSION = '1.47.0' pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy