diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3e86086..bfd27d38 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-## [v5.3.1] - Unreleased
+## [v5.3.2] - 2024-04-17
+
+### Changed
+- Correctly serialize nanosecond dataframe timestamps (#926)
+
+## [v5.3.1] - 2022-11-14
### Added
- Add support for custom headers in the InfluxDBClient (#710 thx @nathanielatom)
diff --git a/README.rst b/README.rst
index a40ed148..048db045 100644
--- a/README.rst
+++ b/README.rst
@@ -15,36 +15,45 @@ InfluxDB-Python
:target: https://pypi.python.org/pypi/influxdb
:alt: PyPI Status
-InfluxDB-Python is a client for interacting with InfluxDB_.
-Development of this library is maintained by:
+.. important::
-+-----------+-------------------------------+
-| Github ID | URL |
-+===========+===============================+
-| @aviau | (https://github.com/aviau) |
-+-----------+-------------------------------+
-| @xginn8 | (https://github.com/xginn8) |
-+-----------+-------------------------------+
-| @sebito91 | (https://github.com/sebito91) |
-+-----------+-------------------------------+
+ **This project is no longer in development**
+
+ This v1 client library is for interacting with `InfluxDB 1.x `_ and 1.x-compatible endpoints in `InfluxDB 2.x `_.
+ Use it to:
+
+ - Write data in line protocol.
+ - Query data with `InfluxQL `_.
+
+ If you use `InfluxDB 2.x (TSM storage engine) `_ and `Flux `_, see the `v2 client library `_.
+
+ If you use `InfluxDB 3.0 `_, see the `v3 client library `_.
+
+ For new projects, consider using InfluxDB 3.0 and v3 client libraries.
+
+Description
+===========
+
+InfluxDB-python, the InfluxDB Python Client (1.x), is a client library for interacting with `InfluxDB 1.x `_ instances.
.. _readme-about:
-InfluxDB is an open-source distributed time series database, find more about InfluxDB_ at https://docs.influxdata.com/influxdb/latest
+`InfluxDB`_ is the time series platform designed to handle high write and query loads.
.. _installation:
-InfluxDB pre v1.1.0 users
--------------------------
-This module is tested with InfluxDB versions: v1.2.4, v1.3.9, v1.4.3, v1.5.4, v1.6.4, and 1.7.4.
+For InfluxDB pre-v1.1.0 users
+-----------------------------
-Those users still on InfluxDB v0.8.x users may still use the legacy client by importing ``from influxdb.influxdb08 import InfluxDBClient``.
+This module is tested with InfluxDB versions v1.2.4, v1.3.9, v1.4.3, v1.5.4, v1.6.4, and 1.7.4.
-Installation
-------------
+Users on InfluxDB v0.8.x may still use the legacy client by importing ``from influxdb.influxdb08 import InfluxDBClient``.
+
+For InfluxDB v1.1+ users
+------------------------
Install, upgrade and uninstall influxdb-python with these commands::
@@ -152,21 +161,33 @@ We are also lurking on the following:
Development
-----------
+The v1 client libraries for InfluxDB 1.x were typically developed and maintained by InfluxDB community members. If you are an InfluxDB v1 user interested in maintaining this client library (at a minimum, keeping it updated with security patches) please contact the InfluxDB team at on the `Community Forums `_ or
+`InfluxData Slack `_.
+
All development is done on Github_. Use Issues_ to report
problems or submit contributions.
.. _Github: https://github.com/influxdb/influxdb-python/
.. _Issues: https://github.com/influxdb/influxdb-python/issues
-Please note that we WILL get to your questions/issues/concerns as quickly as possible. We maintain many
-software repositories and sometimes things may get pushed to the backburner. Please don't take offense,
-we will do our best to reply as soon as possible!
+Please note that we will answer you question as quickly as possible.
+
+Maintainers:
++-----------+-------------------------------+
+| Github ID | URL |
++===========+===============================+
+| @aviau | (https://github.com/aviau) |
++-----------+-------------------------------+
+| @xginn8 | (https://github.com/xginn8) |
++-----------+-------------------------------+
+| @sebito91 | (https://github.com/sebito91) |
++-----------+-------------------------------+
Source code
-----------
-The source code is currently available on Github: https://github.com/influxdata/influxdb-python
+The source code for the InfluxDB Python Client (1.x) is currently available on Github: https://github.com/influxdata/influxdb-python
TODO
@@ -175,6 +196,6 @@ TODO
The TODO/Roadmap can be found in Github bug tracker: https://github.com/influxdata/influxdb-python/issues
-.. _InfluxDB: https://influxdata.com/time-series-platform/influxdb/
+.. _InfluxDB: https://influxdata.com/
.. _Sphinx: http://sphinx.pocoo.org/
.. _Tox: https://tox.readthedocs.org
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 231c776c..efc22f88 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -117,7 +117,8 @@
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
-html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+# Calling get_html_theme_path is deprecated.
+# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index b4ada447..841ad8b1 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -33,7 +33,7 @@ Tutorials - UDP
:language: python
Tutorials - Authorization by Token
-===============
+==================================
.. literalinclude:: ../../examples/tutorial_authorization.py
:language: python
diff --git a/influxdb/__init__.py b/influxdb/__init__.py
index 59916c26..e66f80ea 100644
--- a/influxdb/__init__.py
+++ b/influxdb/__init__.py
@@ -18,4 +18,4 @@
]
-__version__ = '5.3.1'
+__version__ = '5.3.2'
diff --git a/influxdb/_dataframe_client.py b/influxdb/_dataframe_client.py
index e7ae9c17..907db2cb 100644
--- a/influxdb/_dataframe_client.py
+++ b/influxdb/_dataframe_client.py
@@ -372,10 +372,10 @@ def _convert_dataframe_to_lines(self,
# Make array of timestamp ints
if isinstance(dataframe.index, pd.PeriodIndex):
- time = ((dataframe.index.to_timestamp().values.astype(np.int64) /
+ time = ((dataframe.index.to_timestamp().values.astype(np.int64) //
precision_factor).astype(np.int64).astype(str))
else:
- time = ((pd.to_datetime(dataframe.index).values.astype(np.int64) /
+ time = ((pd.to_datetime(dataframe.index).values.astype(np.int64) //
precision_factor).astype(np.int64).astype(str))
# If tag columns exist, make an array of formatted tag keys and values
diff --git a/influxdb/client.py b/influxdb/client.py
index 51a64ac3..c535a3f1 100644
--- a/influxdb/client.py
+++ b/influxdb/client.py
@@ -20,6 +20,7 @@
import msgpack
import requests
import requests.exceptions
+from requests.adapters import HTTPAdapter
from six.moves.urllib.parse import urlparse
from influxdb.line_protocol import make_lines, quote_ident, quote_literal
@@ -87,6 +88,11 @@ class InfluxDBClient(object):
:param headers: headers to add to Requests, will add 'Content-Type'
and 'Accept' unless these are already present, defaults to {}
:type headers: dict
+ :param socket_options: use custom tcp socket options,
+ If not specified, then defaults are loaded from
+ ``HTTPConnection.default_socket_options``
+ :type socket_options: list
+
:raises ValueError: if cert is provided but ssl is disabled (set to False)
"""
@@ -109,6 +115,7 @@ def __init__(self,
gzip=False,
session=None,
headers=None,
+ socket_options=None,
):
"""Construct a new InfluxDBClient object."""
self.__host = host
@@ -128,9 +135,10 @@ def __init__(self,
session = requests.Session()
self._session = session
- adapter = requests.adapters.HTTPAdapter(
+ adapter = _SocketOptionsAdapter(
pool_connections=int(pool_size),
- pool_maxsize=int(pool_size)
+ pool_maxsize=int(pool_size),
+ socket_options=socket_options
)
if use_udp:
@@ -179,7 +187,7 @@ def __init__(self,
def __enter__(self):
"""Enter function as used by context manager."""
- pass
+ return self
def __exit__(self, _exc_type, _exc_value, _traceback):
"""Exit function as used by context manager."""
@@ -328,7 +336,10 @@ def request(self, url, method='GET', params=None, data=None, stream=False,
_try = 0
while retry:
try:
- auth = (self._username, self._password)
+ if "Authorization" in headers:
+ auth = (None, None)
+ else:
+ auth = (self._username, self._password)
response = self._session.request(
method=method,
url=url,
@@ -384,7 +395,7 @@ def write(self, data, params=None, expected_response_code=204,
:param data: the data to be written
:type data: (if protocol is 'json') dict
(if protocol is 'line') sequence of line protocol strings
- or single string
+ or single string
:param params: additional parameters for the request, defaults to None
:type params: dict
:param expected_response_code: the expected response code of the write
@@ -560,8 +571,9 @@ def write_points(self,
:param points: the list of points to be written in the database
:type points: list of dictionaries, each dictionary represents a point
:type points: (if protocol is 'json') list of dicts, where each dict
- represents a point.
- (if protocol is 'line') sequence of line protocol strings.
+ represents a point.
+ (if protocol is 'line') sequence of line protocol strings.
+
:param time_precision: Either 's', 'm', 'ms' or 'u', defaults to None
:type time_precision: str
:param database: the database to write the points to. Defaults to
@@ -626,7 +638,7 @@ def _batches(iterable, size):
# http://code.activestate.com/recipes/303279-getting-items-in-batches/
iterator = iter(iterable)
while True:
- try: # Try get the first element in the iterator...
+ try: # Try get the first element in the iterator...
head = (next(iterator),)
except StopIteration:
return # ...so that we can stop if there isn't one
@@ -1249,3 +1261,16 @@ def _msgpack_parse_hook(code, data):
timestamp += datetime.timedelta(microseconds=(epoch_ns / 1000))
return timestamp.isoformat() + 'Z'
return msgpack.ExtType(code, data)
+
+
+class _SocketOptionsAdapter(HTTPAdapter):
+ """_SocketOptionsAdapter injects socket_options into HTTP Adapter."""
+
+ def __init__(self, *args, **kwargs):
+ self.socket_options = kwargs.pop("socket_options", None)
+ super(_SocketOptionsAdapter, self).__init__(*args, **kwargs)
+
+ def init_poolmanager(self, *args, **kwargs):
+ if self.socket_options is not None:
+ kwargs["socket_options"] = self.socket_options
+ super(_SocketOptionsAdapter, self).init_poolmanager(*args, **kwargs)
diff --git a/influxdb/tests/client_test.py b/influxdb/tests/client_test.py
index 1f9d704a..115fbc48 100644
--- a/influxdb/tests/client_test.py
+++ b/influxdb/tests/client_test.py
@@ -33,6 +33,7 @@
import requests_mock
from nose.tools import raises
+from urllib3.connection import HTTPConnection
from influxdb import InfluxDBClient
from influxdb.resultset import ResultSet
@@ -1498,6 +1499,40 @@ def test_auth_token(self):
self.assertEqual(m.last_request.headers["Authorization"],
"my-token")
+ def test_custom_socket_options(self):
+ """Test custom socket options."""
+ test_socket_options = HTTPConnection.default_socket_options + \
+ [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
+ (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 60),
+ (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 15)]
+
+ cli = InfluxDBClient(username=None, password=None,
+ socket_options=test_socket_options)
+
+ self.assertEquals(cli._session.adapters.get("http://").socket_options,
+ test_socket_options)
+ self.assertEquals(cli._session.adapters.get("http://").poolmanager.
+ connection_pool_kw.get("socket_options"),
+ test_socket_options)
+
+ connection_pool = cli._session.adapters.get("http://").poolmanager \
+ .connection_from_url(
+ url="http://localhost:8086")
+ new_connection = connection_pool._new_conn()
+ self.assertEquals(new_connection.socket_options, test_socket_options)
+
+ def test_none_socket_options(self):
+ """Test default socket options."""
+ cli = InfluxDBClient(username=None, password=None)
+ self.assertEquals(cli._session.adapters.get("http://").socket_options,
+ None)
+ connection_pool = cli._session.adapters.get("http://").poolmanager \
+ .connection_from_url(
+ url="http://localhost:8086")
+ new_connection = connection_pool._new_conn()
+ self.assertEquals(new_connection.socket_options,
+ HTTPConnection.default_socket_options)
+
class FakeClient(InfluxDBClient):
"""Set up a fake client instance of InfluxDBClient."""
diff --git a/influxdb/tests/dataframe_client_test.py b/influxdb/tests/dataframe_client_test.py
index f6db3c22..87b8e0d8 100644
--- a/influxdb/tests/dataframe_client_test.py
+++ b/influxdb/tests/dataframe_client_test.py
@@ -877,7 +877,7 @@ def test_query_into_dataframe(self):
{"measurement": "network",
"tags": {"direction": ""},
"columns": ["time", "value"],
- "values":[["2009-11-10T23:00:00Z", 23422]]
+ "values": [["2009-11-10T23:00:00Z", 23422]]
},
{"measurement": "network",
"tags": {"direction": "in"},
@@ -1274,3 +1274,75 @@ def test_query_custom_index(self):
self.assertListEqual(["time", "host"],
list(_data_frame.index.names))
+
+ def test_dataframe_nanosecond_precision(self):
+ """Test nanosecond precision."""
+ for_df_dict = {
+ "nanFloats": [1.1, float('nan'), 3.3, 4.4],
+ "onlyFloats": [1.1, 2.2, 3.3, 4.4],
+ "strings": ['one_one', 'two_two', 'three_three', 'four_four']
+ }
+ df = pd.DataFrame.from_dict(for_df_dict)
+ df['time'] = ['2019-10-04 06:27:19.850557111+00:00',
+ '2019-10-04 06:27:19.850557184+00:00',
+ '2019-10-04 06:27:42.251396864+00:00',
+ '2019-10-04 06:27:42.251396974+00:00']
+ df['time'] = pd.to_datetime(df['time'], unit='ns')
+ df = df.set_index('time')
+
+ expected = (
+ b'foo nanFloats=1.1,onlyFloats=1.1,strings="one_one" 1570170439850557111\n' # noqa E501 line too long
+ b'foo onlyFloats=2.2,strings="two_two" 1570170439850557184\n' # noqa E501 line too long
+ b'foo nanFloats=3.3,onlyFloats=3.3,strings="three_three" 1570170462251396864\n' # noqa E501 line too long
+ b'foo nanFloats=4.4,onlyFloats=4.4,strings="four_four" 1570170462251396974\n' # noqa E501 line too long
+ )
+
+ with requests_mock.Mocker() as m:
+ m.register_uri(
+ requests_mock.POST,
+ "http://localhost:8086/write",
+ status_code=204
+ )
+
+ cli = DataFrameClient(database='db')
+ cli.write_points(df, 'foo', time_precision='n')
+
+ self.assertEqual(m.last_request.body, expected)
+
+ def test_dataframe_nanosecond_precision_one_microsecond(self):
+ """Test nanosecond precision within one microsecond."""
+ # 1 microsecond = 1000 nanoseconds
+ start = np.datetime64('2019-10-04T06:27:19.850557000')
+ end = np.datetime64('2019-10-04T06:27:19.850558000')
+
+ # generate timestamps with nanosecond precision
+ timestamps = np.arange(
+ start,
+ end + np.timedelta64(1, 'ns'),
+ np.timedelta64(1, 'ns')
+ )
+ # generate values
+ values = np.arange(0.0, len(timestamps))
+
+ df = pd.DataFrame({'value': values}, index=timestamps)
+ with requests_mock.Mocker() as m:
+ m.register_uri(
+ requests_mock.POST,
+ "http://localhost:8086/write",
+ status_code=204
+ )
+
+ cli = DataFrameClient(database='db')
+ cli.write_points(df, 'foo', time_precision='n')
+
+ lines = m.last_request.body.decode('utf-8').split('\n')
+ self.assertEqual(len(lines), 1002)
+
+ for index, line in enumerate(lines):
+ if index == 1001:
+ self.assertEqual(line, '')
+ continue
+ self.assertEqual(
+ line,
+ f"foo value={index}.0 157017043985055{7000 + index:04}"
+ )
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..1b68d94e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools>=42", "wheel"]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 548b17c8..a3df3154 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
python-dateutil>=2.6.0
-pytz
+pytz>=2016.10
requests>=2.17.0
six>=1.10.0
-msgpack
+msgpack>=0.5.0
diff --git a/setup.py b/setup.py
index d44875f6..8ac7d1a7 100755
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,11 @@
with open('requirements.txt', 'r') as f:
requires = [x.strip() for x in f if x.strip()]
+# Debugging: Print the requires values
+print("install_requires values:")
+for req in requires:
+ print(f"- {req}")
+
with open('test-requirements.txt', 'r') as f:
test_requires = [x.strip() for x in f if x.strip()]
diff --git a/tox.ini b/tox.ini
index 1e59b415..a1005abb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,8 +12,8 @@ deps = -r{toxinidir}/requirements.txt
py35: numpy==1.14.6
py36: pandas==0.23.4
py36: numpy==1.15.4
- py37: pandas==0.24.2
- py37: numpy==1.16.2
+ py37: pandas>=0.24.2
+ py37: numpy>=1.16.2
# Only install pandas with non-pypy interpreters
# Testing all combinations would be too expensive
commands = nosetests -v --with-doctest {posargs}
@@ -38,9 +38,9 @@ commands = nosetests -v --with-coverage --cover-html --cover-package=influxdb
[testenv:docs]
deps = -r{toxinidir}/requirements.txt
- pandas==0.24.2
- numpy==1.16.2
- Sphinx==1.8.5
+ pandas>=0.24.2
+ numpy>=1.16.2
+ Sphinx>=1.8.5
sphinx_rtd_theme
commands = sphinx-build -b html docs/source docs/build