Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: query parameters #88

Merged
merged 9 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ commands:
parameters:
python-image:
type: string
pytest-marker:
type: string
steps:
- restore_cache:
name: Restoring Pip Cache
Expand All @@ -19,8 +21,8 @@ commands:
mkdir test-reports || true
pip install . --user
pip install .\[dataframe\] --user
pip install pytest pytest-cov --user
pytest tests --junitxml=test-reports/junit.xml --cov=./ --cov-report xml:coverage.xml
pip install .\[test\] --user
pytest -m "<< parameters.pytest-marker >>" tests --junitxml=test-reports/junit.xml --cov=./ --cov-report xml:coverage.xml
- save_cache:
name: Saving Pip Cache
key: *cache-key
Expand All @@ -34,7 +36,10 @@ jobs:
parameters:
python-image:
type: string
default: &default-python "cimg/python:3.7"
default: &default-python "cimg/python:3.8"
pytest-marker:
type: string
default: "not integration"
docker:
- image: << parameters.python-image >>
environment:
Expand All @@ -43,6 +48,7 @@ jobs:
- checkout
- client-test:
python-image: << parameters.python-image >>
pytest-marker: << parameters.pytest-marker >>
- store_test_results:
path: test-reports
- run:
Expand All @@ -63,21 +69,26 @@ jobs:
PIPENV_VENV_IN_PROJECT: true
steps:
- checkout
- run:
name: Checks style consistency of setup.py.
command: |
pip install flake8 --user
flake8 setup.py
- run:
name: Checks style consistency across sources.
command: |
pip install flake8 --user
flake8 influxdb_client_3/
flake8 influxdb_client_3/
- run:
name: Checks style consistency across tests.
command: |
pip install flake8 --user
flake8 tests/
flake8 tests/
- run:
name: Checks style consistency across examples.
command: |
pip install flake8 --user
flake8 Examples/
flake8 Examples/
check-twine:
docker:
- image: *default-python
Expand Down Expand Up @@ -130,6 +141,16 @@ workflows:
- tests-python:
name: test-3.12
python-image: "cimg/python:3.12"
- tests-python:
requires:
- test-3.8
- test-3.9
- test-3.10
- test-3.11
- test-3.12
name: test-integration
python-image: *default-python
pytest-marker: "integration"

nightly:
when:
Expand Down
5 changes: 5 additions & 0 deletions .markdownlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"MD024": {
"siblings_only": true
},
}
23 changes: 19 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
<!-- markdownlint-disable MD024 -->
# Change Log

## 0.5.0 [unreleased]

### Features

1. [#88](https://github.com/InfluxCommunity/influxdb3-python/pull/88): Add support for named query parameters:
```python
from influxdb_client_3 import InfluxDBClient3

with InfluxDBClient3(host="https://us-east-1-1.aws.cloud2.influxdata.com",
token="my-token",
database="my-database") as client:

table = client.query("select * from cpu where host=$host", query_parameters={"host": "server01"})

print(table.to_pandas())

```

### Bugfix

- [#87](https://github.com/InfluxCommunity/influxdb3-python/pull/87): Fix examples to use `write_options` instead of the object name `WriteOptions`
1. [#87](https://github.com/InfluxCommunity/influxdb3-python/pull/87): Fix examples to use `write_options` instead of the object name `WriteOptions`

### Others

- [#84](https://github.com/InfluxCommunity/influxdb3-python/pull/84): Enable packaging type information - `py.typed`
1. [#84](https://github.com/InfluxCommunity/influxdb3-python/pull/84): Enable packaging type information - `py.typed`

## 0.4.0 [2024-04-17]

Expand All @@ -19,4 +34,4 @@

### Others

- [#80](https://github.com/InfluxCommunity/influxdb3-python/pull/80): Integrate code style check into CI
1. [#80](https://github.com/InfluxCommunity/influxdb3-python/pull/80): Integrate code style check into CI
69 changes: 45 additions & 24 deletions influxdb_client_3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,26 @@
elif isinstance(target, list) and isinstance(source, list):
# If both target and source are lists, concatenate them
target.extend(source)
else:
elif source is not None:

Check warning on line 72 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L72

Added line #L72 was not covered by tests
# For other types, simply replace the target with the source
target = source
return target


def _merge_options(defaults, exclude_keys=None, custom=None):

Check warning on line 78 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L78

Added line #L78 was not covered by tests
"""
Merge default option arguments with custom (user-provided) arguments,
excluding specific keys defined in exclude_keys.
"""
if custom is None or len(custom) == 0:
return defaults

Check warning on line 84 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L83-L84

Added lines #L83 - L84 were not covered by tests

if exclude_keys is None:
exclude_keys = []

Check warning on line 87 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L86-L87

Added lines #L86 - L87 were not covered by tests

return _deep_merge(defaults, {key: value for key, value in custom.items() if key not in exclude_keys})

Check warning on line 89 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L89

Added line #L89 was not covered by tests


class InfluxDBClient3:
def __init__(
self,
Expand Down Expand Up @@ -135,14 +149,6 @@
port = query_port_overwrite
self._flight_client = FlightClient(f"grpc+tls://{hostname}:{port}", **self._flight_client_options)

def _merge_options(self, defaults, custom={}):
"""
Merge default option arguments with custom (user-provided) arguments.
"""
if len(custom) == 0:
return defaults
return _deep_merge(defaults, {key: value for key, value in custom.items()})

def write(self, record=None, database=None, **kwargs):
"""
Write data to InfluxDB.
Expand Down Expand Up @@ -214,20 +220,23 @@
data_frame_tag_columns=tag_columns,
data_frame_timestamp_column=timestamp_column, **kwargs)

def query(self, query, language="sql", mode="all", database=None, **kwargs):
"""
Query data from InfluxDB.

:param query: The query string.
:type query: str
:param language: The query language; "sql" or "influxql" (default is "sql").
:type language: str
:param mode: The mode of fetching data (all, pandas, chunk, reader, schema).
:type mode: str
def query(self, query: str, language: str = "sql", mode: str = "all", database: str = None, **kwargs):

Check warning on line 223 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L223

Added line #L223 was not covered by tests
"""Query data from InfluxDB.

If you want to use query parameters, you can pass them as kwargs:

>>> client.query("select * from cpu where host=$host", query_parameters={"host": "server01"})

:param query: The query to execute on the database.
:param language: The query language to use. It should be one of "influxql" or "sql". Defaults to "sql".
:param mode: The mode to use for the query. It should be one of "all", "pandas", "polars", "chunk",
"reader" or "schema". Defaults to "all".
:param database: The database to query from. If not provided, uses the database provided during initialization.
:type database: str
:param kwargs: FlightClientCallOptions for the query.
:return: The queried data.
:param kwargs: Additional arguments to pass to the ``FlightCallOptions headers``. For example, it can be used to
set up per request headers.
:keyword query_parameters: The query parameters to use in the query.
It should be a ``dictionary`` of key-value pairs.
:return: The query result in the specified mode.
"""
if mode == "polars" and polars is False:
raise ImportError("Polars is not installed. Please install it with `pip install polars`.")
Expand All @@ -241,10 +250,22 @@
"headers": [(b"authorization", f"Bearer {self._token}".encode('utf-8'))],
"timeout": 300
}
opts = self._merge_options(optargs, kwargs)
opts = _merge_options(optargs, exclude_keys=['query_parameters'], custom=kwargs)

Check warning on line 253 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L253

Added line #L253 was not covered by tests
_options = FlightCallOptions(**opts)

ticket_data = {"database": database, "sql_query": query, "query_type": language}
#
# Ticket data
#
ticket_data = {

Check warning on line 259 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L259

Added line #L259 was not covered by tests
"database": database,
"sql_query": query,
"query_type": language
}
# add query parameters
query_parameters = kwargs.get("query_parameters", None)
if query_parameters:
ticket_data["params"] = query_parameters

Check warning on line 267 in influxdb_client_3/__init__.py

View check run for this annotation

Codecov / codecov/patch

influxdb_client_3/__init__.py#L265-L267

Added lines #L265 - L267 were not covered by tests

ticket = Ticket(json.dumps(ticket_data).encode('utf-8'))
flight_reader = self._flight_client.do_get(ticket, _options)

Expand Down
11 changes: 9 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import os
import re


requires = [
'reactivex >= 4.0.4',
'certifi >= 14.05.14',
Expand All @@ -15,6 +14,7 @@
with open("./README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()


def get_version_from_github_ref():
github_ref = os.environ.get("GITHUB_REF")
if not github_ref:
Expand All @@ -26,6 +26,7 @@ def get_version_from_github_ref():

return match.group(1)


def get_version():
# If running in GitHub Actions, get version from GITHUB_REF
version = get_version_from_github_ref()
Expand All @@ -35,6 +36,7 @@ def get_version():
# Fallback to a default version if not in GitHub Actions
return "v0.0.0"


setup(
name='influxdb3-python',
version=get_version(),
Expand All @@ -45,7 +47,12 @@ def get_version():
author_email='contact@influxdata.com',
url='https://github.com/InfluxCommunity/influxdb3-python',
packages=find_packages(exclude=['tests', 'tests.*', 'examples', 'examples.*']),
extras_require={'pandas': ['pandas'], 'polars': ['polars'], 'dataframe': ['pandas', 'polars']},
extras_require={
'pandas': ['pandas'],
'polars': ['polars'],
'dataframe': ['pandas', 'polars'],
'test': ['pytest', 'pytest-cov']
},
install_requires=requires,
python_requires='>=3.8',
classifiers=[
Expand Down
54 changes: 54 additions & 0 deletions tests/test_deep_merge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import unittest

import influxdb_client_3


class TestDeepMerge(unittest.TestCase):

def test_deep_merge_dicts_with_no_overlap(self):
target = {"a": 1, "b": 2}
source = {"c": 3, "d": 4}
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, {"a": 1, "b": 2, "c": 3, "d": 4})

def test_deep_merge_dicts_with_overlap(self):
target = {"a": 1, "b": 2}
source = {"b": 3, "c": 4}
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, {"a": 1, "b": 3, "c": 4})

def test_deep_merge_nested_dicts(self):
target = {"a": {"b": 1}}
source = {"a": {"c": 2}}
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, {"a": {"b": 1, "c": 2}})

def test_deep_merge_lists(self):
target = [1, 2]
source = [3, 4]
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, [1, 2, 3, 4])

def test_deep_merge_non_overlapping_types(self):
target = {"a": 1}
source = [2, 3]
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, [2, 3])

def test_deep_merge_none_to_flight(self):
target = {
"headers": [(b"authorization", "Bearer xyz".encode('utf-8'))],
"timeout": 300
}
source = None
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, target)

def test_deep_merge_empty_to_flight(self):
target = {
"headers": [(b"authorization", "Bearer xyz".encode('utf-8'))],
"timeout": 300
}
source = {}
result = influxdb_client_3._deep_merge(target, source)
self.assertEqual(result, target)
44 changes: 44 additions & 0 deletions tests/test_influxdb_client_3_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os
import time
import unittest

import pytest

from influxdb_client_3 import InfluxDBClient3


@pytest.mark.integration
@pytest.mark.skipif(
not all(
[
os.getenv('TESTING_INFLUXDB_URL'),
os.getenv('TESTING_INFLUXDB_TOKEN'),
os.getenv('TESTING_INFLUXDB_DATABASE'),
]
),
reason="Integration test environment variables not set.",
)
class TestInfluxDBClient3Integration(unittest.TestCase):

def setUp(self):
host = os.getenv('TESTING_INFLUXDB_URL')
token = os.getenv('TESTING_INFLUXDB_TOKEN')
database = os.getenv('TESTING_INFLUXDB_DATABASE')

self.client = InfluxDBClient3(host=host, database=database, token=token)

def tearDown(self):
if self.client:
self.client.close()

def test_write_and_query(self):
test_id = time.time_ns()
self.client.write(f"integration_test_python,type=used value=123.0,test_id={test_id}i")

sql = 'SELECT * FROM integration_test_python where type=$type and test_id=$test_id'

df = self.client.query(sql, mode="pandas", query_parameters={'type': 'used', 'test_id': test_id})

self.assertEqual(1, len(df))
self.assertEqual(test_id, df['test_id'][0])
self.assertEqual(123.0, df['value'][0])
Loading
Loading