Skip to content

Commit db73679

Browse files
committed
feat: add influxdb3 container
1 parent b12ae13 commit db73679

File tree

6 files changed

+305
-5
lines changed

6 files changed

+305
-5
lines changed

modules/influxdb/testcontainers/influxdb.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
a container for an InfluxDB 1.x instance
2121
- import the InfluxDb2Container class from the influxdb2/__init__.py module to spawn
2222
a container for an InfluxDB 2.x instance
23+
- import the InfluxDb3Container class from the influxdb3/__init__.py module to spawn
24+
a container for an InfluxDB 3 Core instance
2325
24-
The 2 containers are separated in different modules for 2 reasons:
26+
The 3 containers are separated in different modules for 3 reasons:
2527
- 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,
28+
- because the InfluxDB clients are different for 1.x, 2.x and 3.x versions,
2729
so you won't have to install dependencies that you do not need
2830
"""
2931

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
from typing import TYPE_CHECKING, Optional, override
14+
15+
import requests
16+
17+
from testcontainers.core.container import DockerContainer
18+
from testcontainers.core.wait_strategies import HttpWaitStrategy
19+
20+
if TYPE_CHECKING:
21+
from influxdb_client_3 import InfluxDBClient3
22+
23+
24+
class InfluxDb3Container(DockerContainer):
25+
"""
26+
Docker container for InfluxDB 3 Core.
27+
Official Docker images for InfluxDB are hosted at https://hub.docker.com/_/influxdb/.
28+
29+
Example:
30+
31+
.. doctest::
32+
33+
>>> from testcontainers.influxdb3 import InfluxDb3Container
34+
35+
>>> with InfluxDb3Container() as influxdb3:
36+
... url = influxdb3.get_url()
37+
"""
38+
39+
INFLUXDB3_PORT = 8181
40+
41+
def __init__(
42+
self,
43+
image: str = "influxdb:3-core",
44+
container_port: int = INFLUXDB3_PORT,
45+
host_port: Optional[int] = None,
46+
is_auth_disabled: bool = False,
47+
**kw,
48+
):
49+
"""
50+
Initialize InfluxDB 3 container.
51+
52+
Args:
53+
image: Docker image to use. Defaults to ``influxdb:3-core``.
54+
container_port: Port inside the container. Defaults to ``8181``.
55+
host_port: Port on the host to bind to. If ``None``, a random port is assigned.
56+
is_auth_disabled: If ``True``, disables authentication. Defaults to ``False``.
57+
**kw: Additional keyword arguments passed to ``DockerContainer``.
58+
"""
59+
super().__init__(image=image, **kw)
60+
61+
self.container_port: int = container_port
62+
self.host_port: Optional[int] = host_port
63+
_ = self.with_bind_ports(self.container_port, self.host_port)
64+
65+
self._is_auth_disabled: bool = is_auth_disabled
66+
self.token: Optional[str] = None
67+
68+
_ = self.with_command(
69+
"influxdb3 serve --node-id local01 --object-store file --data-dir /home/influxdb3/.influxdb3"
70+
)
71+
72+
if self._is_auth_disabled:
73+
healthy_status_code = 200
74+
_ = self.with_env("INFLUXDB3_START_WITHOUT_AUTH", str(self._is_auth_disabled).lower())
75+
else:
76+
healthy_status_code = 401
77+
78+
_ = self.waiting_for(HttpWaitStrategy(self.container_port, "/health").for_status_code(healthy_status_code))
79+
80+
def get_url(self) -> str:
81+
"""
82+
Get the URL to connect to the InfluxDB 3 instance.
83+
84+
Returns:
85+
The HTTP URL of the running InfluxDB 3 container.
86+
"""
87+
host = self.get_container_host_ip()
88+
port = self.get_exposed_port(self.container_port)
89+
return f"http://{host}:{port}"
90+
91+
def _create_token(self) -> str:
92+
url = f"{self.get_url()}/api/v3/configure/token/admin"
93+
response = requests.post(
94+
url, headers={"Accept": "application/json", "Content-Type": "application/json"}, timeout=10
95+
)
96+
response.raise_for_status()
97+
return response.json()["token"]
98+
99+
def get_client(self, database: str, **kw) -> "InfluxDBClient3":
100+
"""
101+
Get an InfluxDB 3 client connected to this container.
102+
103+
Args:
104+
database: Database name to connect to.
105+
**kw: Additional keyword arguments passed to ``InfluxDBClient3``.
106+
107+
Returns:
108+
An ``InfluxDBClient3`` instance connected to this container.
109+
"""
110+
from influxdb_client_3 import InfluxDBClient3
111+
112+
return InfluxDBClient3(
113+
host=self.get_url(),
114+
token=self.token,
115+
database=database,
116+
**kw,
117+
)
118+
119+
@override
120+
def start(self) -> "InfluxDb3Container":
121+
"""
122+
Start the InfluxDB 3 container and initialize authentication.
123+
124+
Returns:
125+
The started ``InfluxDb3Container`` instance.
126+
"""
127+
_ = super().start()
128+
if not self._is_auth_disabled:
129+
self.token = self._create_token()
130+
return self

modules/influxdb/tests/test_influxdb.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
from testcontainers.influxdb import InfluxDbContainer
2323
from testcontainers.influxdb1 import InfluxDb1Container
2424
from testcontainers.influxdb2 import InfluxDb2Container
25+
from testcontainers.influxdb3 import InfluxDb3Container
2526

2627

2728
@mark.parametrize(
2829
["image", "influxdb_container_class", "exposed_port"],
2930
[
30-
("influxdb:2.7", InfluxDb1Container, 8086),
31-
("influxdb:1.8", InfluxDb2Container, 8086),
31+
("influxdb:2.7", InfluxDb2Container, 8086),
32+
("influxdb:1.8", InfluxDb1Container, 8086),
33+
("influxdb:3-core", InfluxDb3Container, 8181),
3234
],
3335
)
3436
def test_influxdbcontainer_get_url(image: str, influxdb_container_class: Type[InfluxDbContainer], exposed_port: int):
@@ -136,3 +138,25 @@ def test_influxdb2container_get_client():
136138
assert len(results) == 2, "2 datapoints were retrieved"
137139
assert results[0] == ["influxdbcontainer", "ratio", datetime.fromisoformat("1978-11-30T09:30:00+00:00"), 0.42]
138140
assert results[1] == ["influxdbcontainer", "ratio", datetime.fromisoformat("1978-12-25T10:30:00+00:00"), 0.55]
141+
142+
143+
def test_influxdb3container_get_client():
144+
"""
145+
This is a test example showing how you could use testcontainers/influxdb for InfluxDB 3 Core versions with SQL queries
146+
"""
147+
with InfluxDb3Container("influxdb:3-core") as influxdb3_container:
148+
client = influxdb3_container.get_client(database="testcontainers")
149+
150+
from influxdb_client_3 import Point
151+
152+
point = Point("influxdbcontainer").tag("source", "test").field("ratio", 0.42)
153+
client.write(point)
154+
155+
table = client.query("SELECT source, ratio FROM influxdbcontainer", language="sql")
156+
rows = table.to_pylist()
157+
158+
assert len(rows) == 1, "1 datapoint was retrieved"
159+
assert rows[0]["source"] == "test"
160+
assert rows[0]["ratio"] == 0.42
161+
162+
client.close()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 testcontainers.influxdb3 import InfluxDb3Container
15+
16+
17+
def test_influxdb3_container_starts_with_auth():
18+
with InfluxDb3Container("influxdb:3-core") as container:
19+
token = container.token
20+
assert token is not None, "Token should be generated when auth is enabled"
21+
assert token.startswith("apiv3_"), "Token should start with apiv3_"
22+
23+
24+
def test_influxdb3_container_starts_without_auth():
25+
with InfluxDb3Container("influxdb:3-core", is_auth_disabled=True) as container:
26+
token = container.token
27+
assert token is None, "Token should be None when auth is disabled"
28+
29+
30+
def test_influxdb3_container_get_url():
31+
with InfluxDb3Container("influxdb:3-core", host_port=8181) as container:
32+
url = container.get_url()
33+
assert "8181" in url, "URL should contain the exposed port"
34+
assert url.startswith("http://"), "URL should start with http://"
35+
36+
37+
def test_influxdb3_write_and_query_data():
38+
with InfluxDb3Container("influxdb:3-core") as container:
39+
client = container.get_client(database="test1")
40+
41+
client.write("temperature,location=west value=55.15")
42+
43+
query = "SELECT time, location, value FROM temperature"
44+
table = client.query(query=query, language="sql")
45+
rows = table.to_pylist()
46+
47+
assert len(rows) == 1, "Should have retrieved 1 row"
48+
assert rows[0]["location"] == "west"
49+
assert rows[0]["value"] == 55.15
50+
51+
client.close()
52+
53+
54+
def test_influxdb3_write_and_query_data_no_auth():
55+
with InfluxDb3Container("influxdb:3-core", is_auth_disabled=True) as container:
56+
client = container.get_client(database="test2")
57+
58+
client.write("humidity,location=east value=72.3")
59+
60+
query = "SELECT time, location, value FROM humidity"
61+
table = client.query(query=query, language="sql")
62+
rows = table.to_pylist()
63+
64+
assert len(rows) == 1, "Should have retrieved 1 row"
65+
assert rows[0]["location"] == "east"
66+
assert rows[0]["value"] == 72.3
67+
68+
client.close()

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ google = [
7171
influxdb = [
7272
"influxdb>=5",
7373
"influxdb-client>=1",
74+
"influxdb3-python>=0.1.0",
7475
]
7576
k3s = ["kubernetes", "pyyaml>=6.0.3"]
7677
kafka = []

0 commit comments

Comments
 (0)