Skip to content

Commit dd1ea6e

Browse files
author
Vitalii Heria
committed
add SingleStore container
1 parent 94a63d6 commit dd1ea6e

File tree

7 files changed

+135
-0
lines changed

7 files changed

+135
-0
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ jobs:
4444
- rabbitmq
4545
- redis
4646
- selenium
47+
- singlestore
4748
runs-on: ${{ matrix.runtime.machine }}
4849
steps:
4950
- uses: actions/checkout@v3

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
3434
rabbitmq/README
3535
redis/README
3636
selenium/README
37+
singlestore/README
3738

3839
Getting Started
3940
---------------

requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
-e file:rabbitmq
2222
-e file:redis
2323
-e file:selenium
24+
-e file:singlestore
2425
cryptography<37
2526
flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages.
2627
pg8000

singlestore/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. autoclass:: testcontainers.singlestore.SingleStoreContainer

singlestore/setup.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from setuptools import setup, find_namespace_packages
2+
3+
description = "SingleStore component of testcontainers-python."
4+
5+
setup(
6+
name="testcontainers-singlestore",
7+
version="0.0.1rc1",
8+
packages=find_namespace_packages(),
9+
description=description,
10+
long_description=description,
11+
long_description_content_type="text/x-rst",
12+
url="https://github.com/testcontainers/testcontainers-python",
13+
install_requires=[
14+
"testcontainers-core",
15+
"sqlalchemy",
16+
"pymysql",
17+
],
18+
python_requires=">=3.7",
19+
)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from os import environ
2+
from tempfile import NamedTemporaryFile
3+
from testcontainers.core.generic import DbContainer
4+
5+
SINGLESTORE_INIT_SCRIPT_FILE = '/init.sql'
6+
7+
8+
class SingleStoreContainer(DbContainer):
9+
"""
10+
SingleStore database container.
11+
12+
Example:
13+
14+
.. doctest::
15+
16+
>>> import sqlalchemy
17+
>>> from testcontainers.singlestore import SingleStoreContainer
18+
19+
>>> with SingleStoreContainer() as singlestore:
20+
... engine = sqlalchemy.create_engine(singlestore.get_connection_url())
21+
... with engine.begin() as connection:
22+
... result = connection.execute(sqlalchemy.text(f'use {singlestore.get_database_name()}; select 1;'))
23+
"""
24+
25+
def __init__(
26+
self,
27+
image: str = "ghcr.io/singlestore-labs/singlestoredb-dev:latest",
28+
port_to_expose: int = 3306,
29+
password: str = 'password',
30+
license_key: str = None,
31+
dbname: str = 'test_db',
32+
singlestore_version: str = None,
33+
dialect: str = 'mysql+pymysql',
34+
**kwargs
35+
):
36+
super(SingleStoreContainer, self).__init__(image, **kwargs)
37+
38+
self.port_to_expose = port_to_expose
39+
self.with_exposed_ports(self.port_to_expose)
40+
41+
self.SINGLESTORE_USER = 'root'
42+
self.SINGLESTORE_ROOT_PASSWORD = password or environ.get(
43+
'SINGLESTORE_ROOT_PASSWORD',
44+
'password'
45+
)
46+
self.SINGLESTORE_LICENSE = license_key or environ.get('SINGLESTORE_LICENSE')
47+
self.SINGLESTORE_DATABASE = dbname or environ.get('SINGLESTORE_DATABASE', 'test_db')
48+
self.SINGLESTORE_VERSION = singlestore_version or environ.get('SINGLESTORE_VERSION')
49+
50+
self.dialect = dialect
51+
52+
self.init_script = NamedTemporaryFile(prefix='singlestore-init-script')
53+
54+
def _configure(self):
55+
self.with_env("ROOT_PASSWORD", self.SINGLESTORE_ROOT_PASSWORD)
56+
self.with_env("SINGLESTORE_LICENSE", self.SINGLESTORE_LICENSE)
57+
self.with_env("LICENSE_KEY", self.SINGLESTORE_LICENSE)
58+
self.with_env("START_AFTER_INIT", 'Y')
59+
60+
if self.SINGLESTORE_VERSION:
61+
self.with_env("SINGLESTORE_VERSION", self.SINGLESTORE_VERSION)
62+
63+
self.maybe_emulate_amd64()
64+
65+
self.map_init_script()
66+
67+
def get_connection_url(self) -> str:
68+
return super()._create_connection_url(
69+
dialect=self.dialect,
70+
username=self.SQLSERVER_USER,
71+
password=self.SQLSERVER_PASSWORD,
72+
db_name=self.SQLSERVER_DBNAME,
73+
port=self.port_to_expose
74+
)
75+
76+
def map_init_script(self):
77+
self.init_script.write('CREATE DATABASE IF NOT EXISTS {};'.format(self.SINGLESTORE_DATABASE).encode('utf-8'))
78+
self.init_script.flush()
79+
self.with_volume_mapping(self.init_script.name, SINGLESTORE_INIT_SCRIPT_FILE)
80+
81+
def stop(self, force=True, delete_volume=True):
82+
self.init_script.close()
83+
super().stop(force, delete_volume)
84+
85+
def get_user(self):
86+
return self.SINGLESTORE_USER
87+
88+
def get_password(self):
89+
return self.SINGLESTORE_ROOT_PASSWORD
90+
91+
def get_database_name(self):
92+
return self.SINGLESTORE_DATABASE
93+
94+
def get_port(self):
95+
return self.port_to_expose

singlestore/tests/test_singlestore.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sqlalchemy
2+
3+
from singlestore.testcontainers.singlestore import SingleStoreContainer
4+
5+
6+
def test_docker_run_singlestore():
7+
dialect = 'mysql+pymysql'
8+
with SingleStoreContainer(
9+
dialect=dialect,
10+
license_key=('BGE5YzE5NTdmN2I1NDQ4MjhhNTQ0MTM4YWQ4Y2Q5ZWNiAAAAAAA'
11+
'AAAAEAAAAAAAAAAwwNQIYKbH2LOLeT189H+nBdRagLdLboVuJTs'
12+
'gJAhkAtSepSZkq0Zn4FDVtvZyASWzovR2OeeyiAA==')
13+
) as singlestore:
14+
engine = sqlalchemy.create_engine(singlestore.get_connection_url())
15+
with engine.begin() as connection:
16+
result = connection.execute(sqlalchemy.text(f'use {singlestore.get_database_name()}; select 1;'))
17+
assert result == 1

0 commit comments

Comments
 (0)