From 00e658e69445f8011cd5ea067e096f310950045e Mon Sep 17 00:00:00 2001 From: DecFox Date: Fri, 26 Apr 2024 12:53:17 +0530 Subject: [PATCH] feat(pytest-clickhouse): add pytest-clickhouse plugin This creates a pytest plugin for clickhouse using the clickhouse docker image as the executor --- pytest-clickhouse/LICENSE.txt | 9 ++ pytest-clickhouse/README.md | 21 +++++ .../pytest_clickhouse-0.0.1-py3-none-any.whl | Bin 0 -> 7665 bytes .../dist/pytest_clickhouse-0.0.1.tar.gz | Bin 0 -> 4819 bytes pytest-clickhouse/pyproject.toml | 87 ++++++++++++++++++ .../pytest_clickhouse/__about__.py | 1 + .../pytest_clickhouse/__init__.py | 0 pytest-clickhouse/pytest_clickhouse/config.py | 32 +++++++ .../pytest_clickhouse/executor.py | 70 ++++++++++++++ .../pytest_clickhouse/executor_noop.py | 20 ++++ .../pytest_clickhouse/factories/__init__.py | 5 + .../pytest_clickhouse/factories/client.py | 28 ++++++ .../pytest_clickhouse/factories/noprocess.py | 34 +++++++ .../pytest_clickhouse/factories/process.py | 33 +++++++ .../pytest_clickhouse/janitor.py | 44 +++++++++ pytest-clickhouse/pytest_clickhouse/plugin.py | 58 ++++++++++++ pytest-clickhouse/tests/__init__.py | 3 + pytest-clickhouse/tests/test_executor.py | 0 pytest-clickhouse/tests/test_janitor.py | 0 pytest-clickhouse/tests/test_noopexecutor.py | 0 20 files changed, 445 insertions(+) create mode 100644 pytest-clickhouse/LICENSE.txt create mode 100644 pytest-clickhouse/README.md create mode 100644 pytest-clickhouse/dist/pytest_clickhouse-0.0.1-py3-none-any.whl create mode 100644 pytest-clickhouse/dist/pytest_clickhouse-0.0.1.tar.gz create mode 100644 pytest-clickhouse/pyproject.toml create mode 100644 pytest-clickhouse/pytest_clickhouse/__about__.py create mode 100644 pytest-clickhouse/pytest_clickhouse/__init__.py create mode 100644 pytest-clickhouse/pytest_clickhouse/config.py create mode 100644 pytest-clickhouse/pytest_clickhouse/executor.py create mode 100644 pytest-clickhouse/pytest_clickhouse/executor_noop.py create mode 100644 pytest-clickhouse/pytest_clickhouse/factories/__init__.py create mode 100644 pytest-clickhouse/pytest_clickhouse/factories/client.py create mode 100644 pytest-clickhouse/pytest_clickhouse/factories/noprocess.py create mode 100644 pytest-clickhouse/pytest_clickhouse/factories/process.py create mode 100644 pytest-clickhouse/pytest_clickhouse/janitor.py create mode 100644 pytest-clickhouse/pytest_clickhouse/plugin.py create mode 100644 pytest-clickhouse/tests/__init__.py create mode 100644 pytest-clickhouse/tests/test_executor.py create mode 100644 pytest-clickhouse/tests/test_janitor.py create mode 100644 pytest-clickhouse/tests/test_noopexecutor.py diff --git a/pytest-clickhouse/LICENSE.txt b/pytest-clickhouse/LICENSE.txt new file mode 100644 index 000000000..d32613ca6 --- /dev/null +++ b/pytest-clickhouse/LICENSE.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024-present DecFox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pytest-clickhouse/README.md b/pytest-clickhouse/README.md new file mode 100644 index 000000000..8d6d7d218 --- /dev/null +++ b/pytest-clickhouse/README.md @@ -0,0 +1,21 @@ +# pytest-clickhouse + +[![PyPI - Version](https://img.shields.io/pypi/v/pytest-clickhouse.svg)](https://pypi.org/project/pytest-clickhouse) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytest-clickhouse.svg)](https://pypi.org/project/pytest-clickhouse) + +----- + +**Table of Contents** + +- [Installation](#installation) +- [License](#license) + +## Installation + +```console +pip install pytest-clickhouse +``` + +## License + +`pytest-clickhouse` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/pytest-clickhouse/dist/pytest_clickhouse-0.0.1-py3-none-any.whl b/pytest-clickhouse/dist/pytest_clickhouse-0.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..1901830c6ecf788eee14cab5f7486d48a9d5f4e3 GIT binary patch literal 7665 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 K*8Xv_i|{X_nT75E literal 0 HcmV?d00001 diff --git a/pytest-clickhouse/dist/pytest_clickhouse-0.0.1.tar.gz b/pytest-clickhouse/dist/pytest_clickhouse-0.0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..3bd60affeb2d4c239cf99c971e81c9649ece1c47 GIT binary patch literal 4819 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 literal 0 HcmV?d00001 diff --git a/pytest-clickhouse/pyproject.toml b/pytest-clickhouse/pyproject.toml new file mode 100644 index 000000000..55531ceed --- /dev/null +++ b/pytest-clickhouse/pyproject.toml @@ -0,0 +1,87 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pytest-clickhouse" +dynamic = ["version"] +description = 'pytest plugin for clickhouse' + +dependencies = [ + "clickhouse-driver ~= 0.2.6", + "docker ~= 6.1.3", +] + +readme = "README.md" +requires-python = ">=3.8" +license = "BSD-3-Clause" +keywords = [] +authors = [ + { name = "OONI", email = "contact@ooni.org" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + +[project.urls] +Documentation = "https://docs.ooni.org" +Issues = "https://github.com/ooni/backend/issues" +Source = "https://github.com/ooni/backend" + +[project.entry-points.pytest11] +pytest_clickhouse = "pytest_clickhouse.plugin" + +[tool.hatch.version] +path = "pytest_clickhouse/__about__.py" + +[tool.hatch.envs.default] +dependencies = [ + "pytest", + "pytest-cov", + "black", + "click", +] +path=".venv/" + +[tool.hatch.envs.default.scripts] +test = "pytesli-lt {args:tests}" +test-cov = "pytest -s --full-trace --log-level=INFO --log-cevel=INFO -v --setup-show --cov=./ --cov-report=xml --cov-report=html --cov-report=term {args:tests}" +cov-report = ["coverage report"] +cov = ["test-cov", "cov-report"] + +[[tool.hatch.envs.all.matrix]] +python = ["3.8", "3.9", "3.10", "3.11", "3.12"] + +[tool.hatch.envs.types] +dependencies = [ + "mypy>=1.0.0", +] +[tool.hatch.envs.types.scripts] +check = "mypy --install-types --non-interactive {args:pytest_clickhouse tests}" + +[tool.coverage.run] +source_pkgs = ["pytest_clickhouse", "tests"] +branch = true +parallel = true +omit = [ + "pytest_clickhouse/__about__.py", +] + +[tool.coverage.paths] +pytest_clickhouse = ["pytest_clickhouse", "*/pytest-clickhouse/pytest_clickhouse"] +tests = ["tests", "*/pytest-clickhouse/tests"] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] diff --git a/pytest-clickhouse/pytest_clickhouse/__about__.py b/pytest-clickhouse/pytest_clickhouse/__about__.py new file mode 100644 index 000000000..f102a9cad --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/__about__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/pytest-clickhouse/pytest_clickhouse/__init__.py b/pytest-clickhouse/pytest_clickhouse/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytest-clickhouse/pytest_clickhouse/config.py b/pytest-clickhouse/pytest_clickhouse/config.py new file mode 100644 index 000000000..d555f4675 --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/config.py @@ -0,0 +1,32 @@ +from pathlib import Path +from typing import TypedDict, Optional, List, Union + +from pytest import FixtureRequest + + +class ClickhouseConfigDict(TypedDict): + """ + Typed Config Dictionary + """ + + image_name: str + host: str + port: Optional[str] + dbname: str + + +def get_config(request: FixtureRequest) -> ClickhouseConfigDict: + """ + Return a dictionary with config options + """ + + def get_clickhouse_option(option: str) -> any: + name = f"clickhouse_{option}" + return request.config.getoption(name) or request.config.getini(name) + + return ClickhouseConfigDict( + image_name=get_clickhouse_option("image"), + host=get_clickhouse_option("host"), + port=get_clickhouse_option("port"), + dbname=get_clickhouse_option("dbname"), + ) diff --git a/pytest-clickhouse/pytest_clickhouse/executor.py b/pytest-clickhouse/pytest_clickhouse/executor.py new file mode 100644 index 000000000..360fe5f51 --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/executor.py @@ -0,0 +1,70 @@ +from typing import Optional +import time + +import docker +from docker.models.containers import Container as DockerContainer + +class ClickhouseExecutor: + """ + Clickhouse executor running on docker + """ + + + def __init__(self, image_name: str, dbname: str): + self.image_name = image_name + self.container = None + self.clickhouse_url = "" + self.dbname = dbname + + + def start(self): + client = docker.from_env() + image_name = self.image_name + client.images.pull(image_name) + + # run a container with a random port mapping for ClickHouse default port 9000 + container = client.containers.run( + image_name, + ports={ + "9000/tcp": None + }, + detach=True, + ) + assert isinstance(container, DockerContainer) + self.container = container + + # obtain the port mapping + container.reload() + assert isinstance(container.attrs, dict) + host_port = container.attrs["NetworkSettings"]["Ports"]["9000/tcp"][0]["HostPort"] + + # construct the connection string + clickhouse_url = f"clickhouse://localhost:{host_port}" + self.clickhouse_url = clickhouse_url + + + def wait_for_clickhouse(self): + while 1: + if self.running(): + break + time.sleep(1) + + + def running(self) -> bool: + if self.container and self.container.status == 'running': + return True + return False + + + def terminate(self): + if self.container: + self.container.stop() + self.container.remove() + + + def __enter__(self): + self.start() + + + def __exit__(self): + self.terminate() diff --git a/pytest-clickhouse/pytest_clickhouse/executor_noop.py b/pytest-clickhouse/pytest_clickhouse/executor_noop.py new file mode 100644 index 000000000..224726b42 --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/executor_noop.py @@ -0,0 +1,20 @@ +from typing import Optional, Union + +class ClickhouseNoopExecutor: + """ + Clickhouse nooperator executor + """ + + + def __init__( + self, + host: str, + port: Union[str, int], + dbname: str + ): + self.host = host + self.port = int(port) + self.clickhouse_url = f"clickhouse://{self.host}:{self.port}" + self.dbname = dbname + + diff --git a/pytest-clickhouse/pytest_clickhouse/factories/__init__.py b/pytest-clickhouse/pytest_clickhouse/factories/__init__.py new file mode 100644 index 000000000..da18216db --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/factories/__init__.py @@ -0,0 +1,5 @@ +from pytest_clickhouse.factories.client import clickhouse +from pytest_clickhouse.factories.process import clickhouse_proc +from pytest_clickhouse.factories.noprocess import clickhouse_noproc + +__all__ = {"clickhouse_proc", "clickhouse_noproc", "clickhouse"} \ No newline at end of file diff --git a/pytest-clickhouse/pytest_clickhouse/factories/client.py b/pytest-clickhouse/pytest_clickhouse/factories/client.py new file mode 100644 index 000000000..771e5dd65 --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/factories/client.py @@ -0,0 +1,28 @@ +from typing import Callable, Union, Optional, List + +import pytest +from pytest import FixtureRequest +from clickhouse_driver import Client as Clickhouse + +from pytest_clickhouse.executor import ClickhouseExecutor +from pytest_clickhouse.executor_noop import ClickhouseNoopExecutor +from pytest_clickhouse.janitor import ClickhouseJanitor + +def clickhouse( + process_fixture_name: str, + dbname: Optional[str] = None, + url: Optional[str] = None, +) -> Callable[[FixtureRequest], Clickhouse]: + + @pytest.fixture + def clickhouse_factory(request: FixtureRequest) -> Clickhouse: + proc_fixture: Union[ClickhouseExecutor, ClickhouseNoopExecutor] = request.getfixturevalue( + process_fixture_name + ) + + clickhouse_url = url or proc_fixture.clickhouse_url + + client = Clickhouse.from_url(clickhouse_url) + yield client + + return clickhouse_factory \ No newline at end of file diff --git a/pytest-clickhouse/pytest_clickhouse/factories/noprocess.py b/pytest-clickhouse/pytest_clickhouse/factories/noprocess.py new file mode 100644 index 000000000..0148d875e --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/factories/noprocess.py @@ -0,0 +1,34 @@ +from typing import Callable, Optional + +import pytest +from pytest import FixtureRequest + +from pytest_clickhouse.config import get_config +from pytest_clickhouse.executor_noop import ClickhouseNoopExecutor +from pytest_clickhouse.janitor import ClickhouseJanitor + +def clickhouse_noproc( + host: Optional[str] = None, + port: Optional[str] = None, + dbname: Optional[str] = None, +) -> Callable[[FixtureRequest], ClickhouseNoopExecutor]: + + @pytest.fixture(scope="session") + def clickhouse_noproc_fixture( + request: FixtureRequest + ) -> ClickhouseNoopExecutor: + config = get_config(request) + clickhouse_noop_exec = ClickhouseNoopExecutor( + host=host or config["host"], + port=port or config["port"], + dbname=dbname or config["dbname"], + ) + + with clickhouse_noop_exec: + with ClickhouseJanitor( + dbname=clickhouse_noop_exec.dbname, + clickhouse_url=clickhouse_noop_exec.clickhouse_url + ): + yield clickhouse_noop_exec + + return clickhouse_noproc_fixture \ No newline at end of file diff --git a/pytest-clickhouse/pytest_clickhouse/factories/process.py b/pytest-clickhouse/pytest_clickhouse/factories/process.py new file mode 100644 index 000000000..b30c0c80b --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/factories/process.py @@ -0,0 +1,33 @@ +from typing import Callable, Iterator, Optional + +import pytest +from pytest import FixtureRequest + +from pytest_clickhouse.executor import ClickhouseExecutor +from pytest_clickhouse.config import get_config +from pytest_clickhouse.janitor import ClickhouseJanitor + +def clickhouse_proc( + image_name: Optional[str] = None, + dbname: Optional[str] = None, +) -> Callable[[FixtureRequest], ClickhouseExecutor]: + + @pytest.fixture(scope="session") + def clickhouse_proc_fixture( + request: FixtureRequest + ) -> ClickhouseExecutor: + config = get_config(request) + clickhouse_executor = ClickhouseExecutor( + image_name=image_name or config["image_name"], + dbname=dbname or config["dbname"] + ) + + with clickhouse_executor: + clickhouse_executor.wait_for_clickhouse() + with ClickhouseJanitor( + dbname=clickhouse_executor.dbname, + clickhouse_url=clickhouse_executor.clickhouse_url + ): + yield clickhouse_executor + + return clickhouse_proc_fixture \ No newline at end of file diff --git a/pytest-clickhouse/pytest_clickhouse/janitor.py b/pytest-clickhouse/pytest_clickhouse/janitor.py new file mode 100644 index 000000000..78180d973 --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/janitor.py @@ -0,0 +1,44 @@ +from contextlib import contextmanager + +from clickhouse_driver import Client as Clickhouse + +class ClickhouseJanitor: + """ + Manage clickhouse database state + """ + + + def __init__(self, dbname: str, clickhouse_url: str): + self.dbname = dbname + self.clickhouse_url = clickhouse_url + self.click = None + + + def init(self): + """ + Initialize client and test database in clickhouse + """ + self.click: Clickhouse = Clickhouse.from_url(self.clickhouse_url) + query = f""" + CREATE DATABASE IF NOT EXISTS {self.dbname} + COMMENT 'test database' + """ + self.click.execute(query, {}) + + + def drop(self): + """ + Drop test database + """ + query = f""" + DROP DATABASE IF EXISTS {self.dbname} + """ + self.click.execute(query, {}) + + + def __enter__(self): + self.init() + + + def __exit__(self): + self.drop() diff --git a/pytest-clickhouse/pytest_clickhouse/plugin.py b/pytest-clickhouse/pytest_clickhouse/plugin.py new file mode 100644 index 000000000..d2448919a --- /dev/null +++ b/pytest-clickhouse/pytest_clickhouse/plugin.py @@ -0,0 +1,58 @@ +from _pytest.config.argparsing import Parser + +from pytest_clickhouse import factories + +_help_image = "Docker image to use to run Clickhouse server" +_help_host = "Host at which Clickhouse will accept connections" +_help_port = "Port at which Clickhouse will accept connections" +_help_dbname = "Default database name" +_help_load = "Dotted-style or entrypoint-style path to callable or path to SQL File" + + +def pytest_addoption(parser: Parser) -> None: + """ + Configure options for pytest-clickhouse + """ + + parser.addini( + name="clickhouse_image", help=_help_image, default="clickhouse/clickhouse-server:24.3-alpine" + ) + + parser.addini(name="clickhouse_host", help=_help_host, default="localhost") + + parser.addini(name="clickhouse_port", help=_help_port, default=None) + + parser.addini(name="clickhouse_dbname", help=_help_dbname, default="tests") + + parser.addoption( + "--clickhouse-image", + action="store", + dest="clickhouse_image", + help=_help_image + ) + + parser.addoption( + "--clickhouse-host", + action="store", + dest="clickhouse_host", + help=_help_host + ) + + parser.addoption( + "--clickhouse-port", + action="store", + dest="clickhouse_port", + help=_help_port + ) + + parser.addoption( + "--clickhouse-dbname", + action="store", + dest="clickhouse_dbname", + help=_help_dbname + ) + + +clickhouse_proc = factories.clickhouse_proc() +clickhouse_noproc = factories.clickhouse_noproc() +clickhouse = factories.clickhouse("clickhouse_proc") \ No newline at end of file diff --git a/pytest-clickhouse/tests/__init__.py b/pytest-clickhouse/tests/__init__.py new file mode 100644 index 000000000..5ddd63fc1 --- /dev/null +++ b/pytest-clickhouse/tests/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2024-present DecFox +# +# SPDX-License-Identifier: MIT diff --git a/pytest-clickhouse/tests/test_executor.py b/pytest-clickhouse/tests/test_executor.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytest-clickhouse/tests/test_janitor.py b/pytest-clickhouse/tests/test_janitor.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytest-clickhouse/tests/test_noopexecutor.py b/pytest-clickhouse/tests/test_noopexecutor.py new file mode 100644 index 000000000..e69de29bb