diff --git a/README.rst b/README.rst index 5fae0ae5b..4879e6a9c 100644 --- a/README.rst +++ b/README.rst @@ -19,6 +19,7 @@ Currently available features: * Neo4j container * OracleDb container * PostgreSQL Db container +* Trino container * Microsoft SQL Server container * Generic docker containers * LocalStack diff --git a/docs/database.rst b/docs/database.rst index aefc5c6ac..960961d5c 100644 --- a/docs/database.rst +++ b/docs/database.rst @@ -9,5 +9,6 @@ Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, .. autoclass:: testcontainers.oracle.OracleDbContainer .. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer .. autoclass:: testcontainers.mongodb.MongoDbContainer +.. autoclass:: testcontainers.trino.TrinoContainer .. autoclass:: testcontainers.mssql.SqlServerContainer .. autoclass:: testcontainers.neo4j.Neo4jContainer diff --git a/requirements.in b/requirements.in index bcce225ef..ceedbd76d 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,4 @@ --e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka] +-e file:.[docker-compose,mysql,oracle,postgresql,trino,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka] codecov>=2.1.0 flake8 pytest diff --git a/requirements/3.6.txt b/requirements/3.6.txt index 034c46197..c08d41ad5 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -43,13 +43,13 @@ deprecation==2.1.0 # via testcontainers distro==1.5.0 # via docker-compose -docker-compose==1.26.2 - # via testcontainers docker[ssh]==4.2.2 # via # -r requirements.in # docker-compose # testcontainers +docker-compose==1.26.2 + # via testcontainers dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -58,6 +58,8 @@ docutils==0.16 # via sphinx flake8==3.8.3 # via -r requirements.in +future==0.18.2 + # via pyhive google-api-core[grpc]==1.22.1 # via google-cloud-pubsub google-auth==1.20.1 @@ -91,7 +93,8 @@ jinja2==2.11.2 # via sphinx jsonschema==3.2.0 # via docker-compose -kafka-python==2.0.2 # via testcontainers +kafka-python==2.0.2 + # via testcontainers markupsafe==1.1.1 # via jinja2 mccabe==0.6.1 @@ -117,12 +120,12 @@ psycopg2-binary==2.8.5 # via testcontainers py==1.9.0 # via pytest -pyasn1-modules==0.2.8 - # via google-auth pyasn1==0.4.8 # via # pyasn1-modules # rsa +pyasn1-modules==0.2.8 + # via google-auth pycodestyle==2.6.0 # via flake8 pycparser==2.20 @@ -131,6 +134,8 @@ pyflakes==2.2.0 # via flake8 pygments==2.6.1 # via sphinx +pyhive[trino]==0.6.4 + # via testcontainers pymongo==3.11.0 # via testcontainers pymysql==0.10.0 @@ -143,12 +148,14 @@ pyparsing==2.4.7 # via packaging pyrsistent==0.16.0 # via jsonschema -pytest-cov==2.10.1 - # via -r requirements.in pytest==6.0.1 # via # -r requirements.in # pytest-cov +pytest-cov==2.10.1 + # via -r requirements.in +python-dateutil==2.8.2 + # via pyhive python-dotenv==0.14.0 # via docker-compose pytz==2020.1 @@ -166,6 +173,7 @@ requests==2.24.0 # docker # docker-compose # google-api-core + # pyhive # sphinx rsa==4.6 # via google-auth @@ -186,6 +194,7 @@ six==1.15.0 # protobuf # pynacl # pyrsistent + # python-dateutil # websocket-client snowballstemmer==2.0.0 # via sphinx diff --git a/requirements/3.7.txt b/requirements/3.7.txt index dea3139a8..07580c89c 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -43,13 +43,13 @@ deprecation==2.1.0 # via testcontainers distro==1.5.0 # via docker-compose -docker-compose==1.26.2 - # via testcontainers docker[ssh]==4.2.2 # via # -r requirements.in # docker-compose # testcontainers +docker-compose==1.26.2 + # via testcontainers dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -58,6 +58,8 @@ docutils==0.16 # via sphinx flake8==3.8.3 # via -r requirements.in +future==0.18.2 + # via pyhive google-api-core[grpc]==1.22.1 # via google-cloud-pubsub google-auth==1.20.1 @@ -91,7 +93,8 @@ jinja2==2.11.2 # via sphinx jsonschema==3.2.0 # via docker-compose -kafka-python==2.0.2 # via testcontainers +kafka-python==2.0.2 + # via testcontainers markupsafe==1.1.1 # via jinja2 mccabe==0.6.1 @@ -117,12 +120,12 @@ psycopg2-binary==2.8.5 # via testcontainers py==1.9.0 # via pytest -pyasn1-modules==0.2.8 - # via google-auth pyasn1==0.4.8 # via # pyasn1-modules # rsa +pyasn1-modules==0.2.8 + # via google-auth pycodestyle==2.6.0 # via flake8 pycparser==2.20 @@ -131,6 +134,8 @@ pyflakes==2.2.0 # via flake8 pygments==2.6.1 # via sphinx +pyhive[trino]==0.6.4 + # via testcontainers pymongo==3.11.0 # via testcontainers pymysql==0.10.0 @@ -143,12 +148,14 @@ pyparsing==2.4.7 # via packaging pyrsistent==0.16.0 # via jsonschema -pytest-cov==2.10.1 - # via -r requirements.in pytest==6.0.1 # via # -r requirements.in # pytest-cov +pytest-cov==2.10.1 + # via -r requirements.in +python-dateutil==2.8.2 + # via pyhive python-dotenv==0.14.0 # via docker-compose pytz==2020.1 @@ -166,6 +173,7 @@ requests==2.24.0 # docker # docker-compose # google-api-core + # pyhive # sphinx rsa==4.6 # via google-auth @@ -186,6 +194,7 @@ six==1.15.0 # protobuf # pynacl # pyrsistent + # python-dateutil # websocket-client snowballstemmer==2.0.0 # via sphinx diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 4959646af..1f9c62dd8 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -43,13 +43,13 @@ deprecation==2.1.0 # via testcontainers distro==1.5.0 # via docker-compose -docker-compose==1.26.2 - # via testcontainers docker[ssh]==4.2.2 # via # -r requirements.in # docker-compose # testcontainers +docker-compose==1.26.2 + # via testcontainers dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -58,6 +58,8 @@ docutils==0.16 # via sphinx flake8==3.8.3 # via -r requirements.in +future==0.18.2 + # via pyhive google-api-core[grpc]==1.22.1 # via google-cloud-pubsub google-auth==1.20.1 @@ -79,13 +81,20 @@ idna==2.10 # via requests imagesize==1.2.0 # via sphinx +importlib-metadata==4.6.3 + # via + # flake8 + # jsonschema + # pluggy + # pytest iniconfig==1.0.1 # via pytest jinja2==2.11.2 # via sphinx jsonschema==3.2.0 # via docker-compose -kafka-python==2.0.2 # via testcontainers +kafka-python==2.0.2 + # via testcontainers markupsafe==1.1.1 # via jinja2 mccabe==0.6.1 @@ -111,12 +120,12 @@ psycopg2-binary==2.8.5 # via testcontainers py==1.9.0 # via pytest -pyasn1-modules==0.2.8 - # via google-auth pyasn1==0.4.8 # via # pyasn1-modules # rsa +pyasn1-modules==0.2.8 + # via google-auth pycodestyle==2.6.0 # via flake8 pycparser==2.20 @@ -125,6 +134,8 @@ pyflakes==2.2.0 # via flake8 pygments==2.6.1 # via sphinx +pyhive[trino]==0.6.4 + # via testcontainers pymongo==3.11.0 # via testcontainers pymysql==0.10.0 @@ -137,12 +148,14 @@ pyparsing==2.4.7 # via packaging pyrsistent==0.16.0 # via jsonschema -pytest-cov==2.10.1 - # via -r requirements.in pytest==6.0.1 # via # -r requirements.in # pytest-cov +pytest-cov==2.10.1 + # via -r requirements.in +python-dateutil==2.8.2 + # via pyhive python-dotenv==0.14.0 # via docker-compose pytz==2020.1 @@ -160,6 +173,7 @@ requests==2.24.0 # docker # docker-compose # google-api-core + # pyhive # sphinx rsa==4.6 # via google-auth @@ -180,6 +194,7 @@ six==1.15.0 # protobuf # pynacl # pyrsistent + # python-dateutil # websocket-client snowballstemmer==2.0.0 # via sphinx @@ -203,6 +218,8 @@ texttable==1.6.2 # via docker-compose toml==0.10.1 # via pytest +typing-extensions==3.10.0.0 + # via importlib-metadata urllib3==1.25.10 # via # requests @@ -213,6 +230,8 @@ websocket-client==0.57.0 # docker-compose wrapt==1.12.1 # via testcontainers +zipp==3.5.0 + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/setup.py b/setup.py index 1f8d49779..d424d11ad 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ 'mysql': ['sqlalchemy', 'pymysql'], 'oracle': ['sqlalchemy', 'cx_Oracle'], 'postgresql': ['sqlalchemy', 'psycopg2-binary'], + 'trino': ['sqlalchemy', 'pyhive[trino]'], 'selenium': ['selenium'], 'google-cloud-pubsub': ['google-cloud-pubsub'], 'mongo': ['pymongo'], diff --git a/testcontainers/trino.py b/testcontainers/trino.py new file mode 100644 index 000000000..44b21fe65 --- /dev/null +++ b/testcontainers/trino.py @@ -0,0 +1,55 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import re +import time + +from sqlalchemy import create_engine + +from testcontainers.core.exceptions import TimeoutException +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class TrinoContainer(DbContainer): + TRINO_STARTUP_TIMEOUT_SECONDS = 120 + + def __init__(self, image="trinodb/trino:latest"): + super(TrinoContainer, self).__init__(image=image) + self.port_to_expose = 8080 + self.with_exposed_ports(self.port_to_expose) + + @wait_container_is_ready() + def _connect(self): + deadline = time.time() + TrinoContainer.TRINO_STARTUP_TIMEOUT_SECONDS + regex = re.compile(".*======== SERVER STARTED ========.*", re.MULTILINE).search + + wait_for_logs(self, regex, TrinoContainer.TRINO_STARTUP_TIMEOUT_SECONDS) + + while time.time() < deadline: + engine = create_engine(self.get_connection_url()) + engine.execute("SELECT 1") + return + + raise TimeoutException( + "Trino did not start within %.3f seconds" % TrinoContainer.TRINO_STARTUP_TIMEOUT_SECONDS + ) + + def get_connection_url(self): + return "{dialect}://{host}:{port}".format( + dialect="trino", + host=self.get_container_host_ip(), + port=self.get_exposed_port(self.port_to_expose), + ) + + def _configure(self): + pass diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index 2fdc706e8..4404e8193 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -11,6 +11,7 @@ from testcontainers.neo4j import Neo4jContainer from testcontainers.oracle import OracleDbContainer from testcontainers.postgres import PostgresContainer +from testcontainers.trino import TrinoContainer def test_docker_run_mysql(): @@ -64,6 +65,15 @@ def test_docker_run_oracle(): assert {row[0] for row in result} == versions +def test_docker_run_trino(): + trino_container = TrinoContainer("trinodb/trino:360") + with trino_container as trino: + e = sqlalchemy.create_engine(trino.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0] == '360' + + def test_docker_run_mongodb(): mongo_container = MongoDbContainer("mongo:latest") with mongo_container as mongo: