Skip to content

Commit 13742a5

Browse files
authored
feat: support influxdb (#413)
Thanks to the team for this great testing utility 🙏 This PR brings the possibility to spawn a Docker instance of InfluxDB (timeseries-oriented database) for integration testing. This PR supports both 1.x and 2.x versions of InfluxDB, which rely on different query languages and require the use of different Python client libraries. Therefore: - there is a root `InfluxDbContainer` class in `testcontainers/influxdb.py` for the common mechanisms (health check, connection url, etc.) - there are 2 separate classes, one for each InfluxDB major version: `InfluxDb1Container` class in `testcontainers/influxdb1/__init__.py`, `InfluxDb2Container` in `testcontainers/influxdb2/__init__.py` that are meant to be used by the testcontainers users. Each one has its own `.get_client()` method involving the dedicated Python client library Unit and integration tests (meant to be examples for the testcontainers end-users) are provided in the PR.
1 parent 7b58a50 commit 13742a5

File tree

9 files changed

+1118
-600
lines changed

9 files changed

+1118
-600
lines changed

INDEX.rst

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
2020
modules/clickhouse/README
2121
modules/elasticsearch/README
2222
modules/google/README
23+
modules/influxdb/README
2324
modules/kafka/README
2425
modules/keycloak/README
2526
modules/localstack/README

modules/influxdb/README.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.. autoclass:: testcontainers.influxdb.InfluxDbContainer
2+
.. title:: testcontainers.influxdb.InfluxDbContainer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
"""
15+
testcontainers/influxdb provides means to spawn an InfluxDB instance within a Docker container.
16+
17+
- this influxdb.py module provides the common mechanism to spawn an InfluxDB container.
18+
You are not likely to use this module directly.
19+
- import the InfluxDb1Container class from the influxdb1/__init__.py module to spawn
20+
a container for an InfluxDB 1.x instance
21+
- import the InfluxDb2Container class from the influxdb2/__init__.py module to spawn
22+
a container for an InfluxDB 2.x instance
23+
24+
The 2 containers are separated in different modules for 2 reasons:
25+
- because the Docker images are not designed to be used in the same way
26+
- because the InfluxDB clients are different for 1.x and 2.x versions,
27+
so you won't have to install dependencies that you do not need
28+
"""
29+
from typing import Optional
30+
31+
from requests import get
32+
from requests.exceptions import ConnectionError, ReadTimeout
33+
34+
from testcontainers.core.container import DockerContainer
35+
from testcontainers.core.waiting_utils import wait_container_is_ready
36+
37+
38+
class InfluxDbContainer(DockerContainer):
39+
"""
40+
Abstract class for Docker containers of InfluxDB v1 and v2.
41+
42+
Concrete implementations for InfluxDB 1.x and 2.x are separated iun different packages
43+
because their respective clients rely on different Python libraries which we don't want
44+
to import at the same time.
45+
"""
46+
47+
def __init__(
48+
self,
49+
# Docker image name
50+
image: str,
51+
# in the container, the default port for influxdb is often 8086 and not likely to change
52+
container_port: int = 8086,
53+
# specifies the port on the host machine where influxdb is exposed; a random available port otherwise
54+
host_port: Optional[int] = None,
55+
**docker_client_kw,
56+
):
57+
super().__init__(image=image, **docker_client_kw)
58+
self.container_port = container_port
59+
self.host_port = host_port
60+
self.with_bind_ports(self.container_port, self.host_port)
61+
62+
def get_url(self) -> str:
63+
"""
64+
Returns the url to interact with the InfluxDB container (health check, REST API, etc.)
65+
"""
66+
host = self.get_container_host_ip()
67+
port = self.get_exposed_port(self.container_port)
68+
69+
return f"http://{host}:{port}"
70+
71+
@wait_container_is_ready(ConnectionError, ReadTimeout)
72+
def _health_check(self) -> dict:
73+
"""
74+
Performs a health check on the running InfluxDB container.
75+
The call is retried until it works thanks to the @wait_container_is_ready decorator.
76+
See its documentation for the max number of retries or the timeout.
77+
"""
78+
79+
url = self.get_url()
80+
response = get(f"{url}/health", timeout=1)
81+
response.raise_for_status()
82+
83+
return response.json()
84+
85+
def get_influxdb_version(self) -> str:
86+
"""
87+
Returns the version of the InfluxDB service, as returned by the healthcheck.
88+
"""
89+
90+
return self._health_check().get("version")
91+
92+
def start(self) -> "InfluxDbContainer":
93+
"""
94+
Spawns a container of the InfluxDB Docker image, ready to be used.
95+
"""
96+
super().start()
97+
self._health_check()
98+
99+
return self
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
from typing import Optional
15+
16+
from influxdb import InfluxDBClient
17+
18+
from testcontainers.influxdb import InfluxDbContainer
19+
20+
21+
class InfluxDb1Container(InfluxDbContainer):
22+
"""
23+
Docker container for InfluxDB 1.x.
24+
Official Docker images for InfluxDB are hosted at https://hub.docker.com/_/influxdb/.
25+
26+
Example:
27+
28+
.. doctest::
29+
30+
>>> from testcontainers.influxdb1 import InfluxDbContainer
31+
32+
>>> with InfluxDbContainer() as influxdb:
33+
... version = influxdb.get_version()
34+
"""
35+
36+
def __init__(
37+
self,
38+
image: str = "influxdb:1.8",
39+
# in the container, the default port for influxdb is often 8086 and not likely to change
40+
container_port: int = 8086,
41+
# specifies the port on the host machine where influxdb is exposed; a random available port otherwise
42+
host_port: Optional[int] = None,
43+
**docker_client_kw,
44+
):
45+
super().__init__(image, container_port, host_port, **docker_client_kw)
46+
47+
def get_client(self, **client_kwargs):
48+
"""
49+
Returns an instance of the influxdb client, for InfluxDB 1.x versions.
50+
Note that this client is not maintained anymore, but it is the only
51+
official client available for 1.x InfluxDB versions:
52+
- https://github.com/influxdata/influxdb-python
53+
- https://pypi.org/project/influxdb/
54+
55+
To some extent, you can use the v2 client with InfluxDB v1.8+:
56+
- https://github.com/influxdata/influxdb-client-python#influxdb-18-api-compatibility
57+
"""
58+
59+
return InfluxDBClient(self.get_container_host_ip(), self.get_exposed_port(self.container_port), **client_kwargs)
60+
61+
def start(self) -> "InfluxDb1Container":
62+
"""
63+
Overridden for better typing reason
64+
"""
65+
return super().start()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
from os import getenv
15+
from typing import Optional
16+
17+
from influxdb_client import InfluxDBClient, Organization
18+
19+
from testcontainers.influxdb import InfluxDbContainer
20+
21+
22+
class InfluxDb2Container(InfluxDbContainer):
23+
"""
24+
Docker container for InfluxDB 2.x.
25+
Official Docker images for InfluxDB are hosted at https://hub.docker.com/_/influxdb/.
26+
27+
Example:
28+
29+
.. doctest::
30+
31+
>>> from testcontainers.influxdb2 import InfluxDb2Container
32+
33+
>>> with InfluxDb2Container() as influxdb2:
34+
... version = influxdb2.get_version()
35+
"""
36+
37+
def __init__(
38+
self,
39+
image: str = "influxdb:latest",
40+
# in the container, the default port for influxdb is often 8086 and not likely to change
41+
container_port: int = 8086,
42+
# specifies the port on the host machine where influxdb is exposed; a random available port otherwise
43+
host_port: Optional[int] = None,
44+
# parameters used by the InfluxDSB 2.x Docker container when spawned in setup mode
45+
# (which is likely what you want). In setup mode, init_mode should be "setup" and all
46+
# the other parameters should be set (via this constructor or their respective
47+
# environment variables); retention does not need to be explicitely set.
48+
init_mode: Optional[str] = None,
49+
admin_token: Optional[str] = None,
50+
username: Optional[str] = None,
51+
password: Optional[str] = None,
52+
org_name: Optional[str] = None,
53+
bucket: Optional[str] = None,
54+
retention: Optional[str] = None,
55+
**docker_client_kw,
56+
):
57+
super().__init__(image, container_port, host_port, **docker_client_kw)
58+
59+
configuration = {
60+
"DOCKER_INFLUXDB_INIT_MODE": init_mode,
61+
"DOCKER_INFLUXDB_INIT_ADMIN_TOKEN": admin_token,
62+
"DOCKER_INFLUXDB_INIT_USERNAME": username,
63+
"DOCKER_INFLUXDB_INIT_PASSWORD": password,
64+
"DOCKER_INFLUXDB_INIT_ORG": org_name,
65+
"DOCKER_INFLUXDB_INIT_BUCKET": bucket,
66+
"DOCKER_INFLUXDB_INIT_RETENTION": retention,
67+
}
68+
for env_key, constructor_param in configuration.items():
69+
env_value = constructor_param or getenv(env_key)
70+
if env_value:
71+
self.with_env(env_key, env_value)
72+
73+
def start(self) -> "InfluxDb2Container":
74+
"""
75+
Overridden for better typing reason
76+
"""
77+
return super().start()
78+
79+
def get_client(
80+
self, token: Optional[str] = None, org_name: Optional[str] = None, **influxdb_client_kwargs
81+
) -> tuple[InfluxDBClient, Organization]:
82+
"""
83+
Returns an instance of the influxdb client with the associated test organization created
84+
when the container is spawn; for InfluxDB 2.x versions.
85+
- https://github.com/influxdata/influxdb-client-python
86+
- https://pypi.org/project/influxdb-client/
87+
88+
This InfluxDB client requires to specify the organization when using most of the API's endpoints,
89+
as an Organisation instance rather than its name or id (deprecated). As a convenience, this
90+
client getter can also retrieve and return the organization instance along with the client.
91+
Otherwise, None is returned in place of the organization instance.
92+
93+
This organization is created when spawning the container in setup mode (which is likely what you
94+
want) by giving its name to the 'org_name' parameter constructor.
95+
"""
96+
97+
influxclient = InfluxDBClient(self.get_url(), token=token, **influxdb_client_kwargs)
98+
99+
if org_name is None:
100+
return influxclient, None
101+
102+
orgs = influxclient.organizations_api().find_organizations(org=org_name)
103+
if len(orgs) == 0:
104+
raise ValueError(f"Could not retrieved the Organization corresponding to name '{org_name}'")
105+
106+
return influxclient, orgs[0]

modules/influxdb/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)