-
Notifications
You must be signed in to change notification settings - Fork 263
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
Enable using clickhouse HTTP interface #319
base: main
Are you sure you want to change the base?
Changes from all commits
4d2b49e
c9a2dd0
9a3c217
de8502a
564ce66
cc4e563
8682063
04e999c
4554473
9008da5
b50984d
21a7f00
c1761e2
ac47799
66ccd27
4932238
b851c6e
8e097c6
e06bbdd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,8 +13,10 @@ | |
import os | ||
from typing import Optional | ||
|
||
import clickhouse_connect | ||
from clickhouse_connect.driver.exceptions import Error as ClickhouseConnectError | ||
import clickhouse_driver | ||
from clickhouse_driver.errors import Error | ||
from clickhouse_driver.errors import Error as ClickhouseDriverError | ||
|
||
from testcontainers.core.generic import DbContainer | ||
from testcontainers.core.utils import raise_for_deprecated_parameter | ||
|
@@ -23,38 +25,62 @@ | |
|
||
class ClickHouseContainer(DbContainer): | ||
""" | ||
ClickHouse database container. | ||
ClickHouse database container. This testcontainer defaults to exposing the TCP port of | ||
ClickHouse. If you want to use the HTTP interface, specify port 8123 to be exposed. | ||
|
||
Example: | ||
|
||
The example spins up a ClickHouse database and connects to it using the | ||
:code:`clickhouse-driver`. | ||
This example shows how to spin up ClickHouse. | ||
It demonstrates how to connect to the *TCP* interface using :code:`clickhouse-driver` | ||
and how to connect to the *HTTP* interface using :code:`clickhouse-connect`, the | ||
official client library. | ||
|
||
.. doctest:: | ||
|
||
>>> import clickhouse_driver | ||
>>> from testcontainers.clickhouse import ClickHouseContainer | ||
|
||
>>> with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: | ||
>>> # clickhouse_driver is a client lib that uses the TCP interface | ||
>>> import clickhouse_driver | ||
>>> # ClickHouseContainer exports the TCP port by default | ||
>>> with ClickHouseContainer(image="clickhouse/clickhouse-server:21.8") as clickhouse: | ||
... client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) | ||
... client.execute("select 'working'") | ||
[('working',)] | ||
|
||
>>> # clickhouse_connect is the official client lib, based on the HTTP interface | ||
>>> import clickhouse_connect | ||
>>> # If you want to use the HTTP interface, port 8123 needs to be exposed | ||
>>> with ClickHouseContainer(port=8123) as clickhouse: | ||
... client = clickhouse_connect.get_client(dsn=clickhouse.get_connection_url()) | ||
... client.query("select 'working'").result_rows | ||
[('working',)] | ||
pofl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
def __init__(self, image: str = "clickhouse/clickhouse-server:latest", port: int = 9000, | ||
username: Optional[str] = None, password: Optional[str] = None, | ||
dbname: Optional[str] = None, **kwargs) -> None: | ||
|
||
def __init__( | ||
self, | ||
image: str = "clickhouse/clickhouse-server:latest", | ||
port: int = 9000, | ||
username: Optional[str] = None, | ||
password: Optional[str] = None, | ||
dbname: Optional[str] = None, | ||
**kwargs | ||
) -> None: | ||
raise_for_deprecated_parameter(kwargs, "user", "username") | ||
super().__init__(image=image, **kwargs) | ||
self.username = username or os.environ.get("CLICKHOUSE_USER", "test") | ||
self.password = password or os.environ.get("CLICKHOUSE_PASSWORD", "test") | ||
self.dbname = dbname or os.environ.get("CLICKHOUSE_DB", "test") | ||
self.username: str = username or os.environ.get("CLICKHOUSE_USER", "test") | ||
self.password: str = password or os.environ.get("CLICKHOUSE_PASSWORD", "test") | ||
self.dbname: str = dbname or os.environ.get("CLICKHOUSE_DB", "test") | ||
self.port = port | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to just expose both ports? Then the user can connect with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that would make sense. Can you advise me on code style: How would you express that as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
self.with_exposed_ports(self.port) | ||
|
||
@wait_container_is_ready(Error, EOFError) | ||
@wait_container_is_ready(ClickhouseDriverError, ClickhouseConnectError, EOFError) | ||
def _connect(self) -> None: | ||
with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: | ||
client.execute("SELECT version()") | ||
if self.port == 8123: | ||
with clickhouse_connect.get_client(dsn=self.get_connection_url()) as client: | ||
client.command("SELECT version()") | ||
else: | ||
with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: | ||
client.execute("SELECT version()") | ||
|
||
def _configure(self) -> None: | ||
self.with_env("CLICKHOUSE_USER", self.username) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,17 @@ | ||
import clickhouse_connect | ||
import clickhouse_driver | ||
from testcontainers.clickhouse import ClickHouseContainer | ||
|
||
|
||
def test_docker_run_clickhouse(): | ||
clickhouse_container = ClickHouseContainer() | ||
with clickhouse_container as clickhouse: | ||
def test_clickhouse_tcp_interface(): | ||
with ClickHouseContainer() as clickhouse: | ||
client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) | ||
result = client.execute("select 'working'") | ||
assert result == [("working",)] | ||
|
||
assert result == [('working',)] | ||
|
||
def test_clickhouse_http_interface(): | ||
with ClickHouseContainer(port=8123) as clickhouse: | ||
client = clickhouse_connect.get_client(dsn=clickhouse.get_connection_url()) | ||
result = client.query("select 'working'").result_rows | ||
assert result == [("working",)] | ||
Comment on lines
+13
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have previously not noticed this test file. I have extended it with the new HTTP interface. However, I would actually vote for dropping this test file because it adds no value when the doctests in the other file exist. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good question. We've got some duplicate code across the documentation and in the tests, in part to keep track of test coverage. If we could also run the doctests through pytest with test coverage, that'd be great. It sounds like it's possible, but I haven't looked into it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the updated requirements, let's update the lock files. Could you run
make requirements
from the root directory and push the changes?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see there have been some changes to requirements management. Is there anything I should do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can run
python get_requirements.py --pr=[your PR number]
to update the requirements. Full details here. You may have to rebase onmain
and wait for CI to run before the script will work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am getting an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the CI needs to have completed first, and GitHub doesn't allow us to start the run automatically for first-time contributors. I've started it now. We should probably also improve that error message.