From f5113fb1bb77e4aa03ade1293c086897d587b4c4 Mon Sep 17 00:00:00 2001 From: shikhar08 <43385496+shikhar08@users.noreply.github.com> Date: Tue, 8 Dec 2020 15:02:12 +0530 Subject: [PATCH 001/425] fixes #122 (#123) * fixes #122 * fixes #122 --- testcontainers/selenium.py | 3 +-- tests/test_selenium.py | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/test_selenium.py diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index a2cd3a420..a4b3144ea 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -49,8 +49,7 @@ class BrowserWebDriverContainer(DockerContainer): """ def __init__(self, capabilities, image=None): self.capabilities = capabilities - if not image: - self.image = get_image_name(capabilities) + self.image = image or get_image_name(capabilities) self.port_to_expose = 4444 self.vnc_port_to_expose = 5900 super(BrowserWebDriverContainer, self).__init__(image=self.image) diff --git a/tests/test_selenium.py b/tests/test_selenium.py new file mode 100644 index 000000000..9958ecff4 --- /dev/null +++ b/tests/test_selenium.py @@ -0,0 +1,9 @@ + +def test_selenium_custom_image(): + from testcontainers.selenium import BrowserWebDriverContainer + from selenium.webdriver import DesiredCapabilities + + image = "selenium/standalone-chrome:latest" + chrome = BrowserWebDriverContainer(DesiredCapabilities.CHROME, image=image) + assert "image" in dir(chrome), "`image` attribute was not instantialized." + assert chrome.image == image, "`image` attribute was not set to the user provided value" From 7068a45c95a3bd779b4c2f96e0d3b67426e81ad4 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 11 Dec 2020 15:01:07 +0000 Subject: [PATCH 002/425] Bump from 3.1.0 to 3.2.0. (#125) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 209408d49..2e4c66576 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setuptools.setup( name='testcontainers', packages=setuptools.find_packages(exclude=['tests']), - version='3.1.0', + version='3.2.0', description='Library provides lightweight, throwaway instances of common databases, Selenium ' 'web browsers, or anything else that can run in a Docker container', author='Sergey Pirogov', From 0ee4a9408092459ce23be48c35dca515869f1810 Mon Sep 17 00:00:00 2001 From: ArthurYueh Date: Sat, 6 Mar 2021 02:59:28 +0800 Subject: [PATCH 003/425] [RedisContainer] Support decode_responses setting (#128) * [RedisContainer] Support decode_responses setting Support decode_responses setting * modify as review suggests * modify as suggestions from review --- testcontainers/redis.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/testcontainers/redis.py b/testcontainers/redis.py index 63341f418..331fc5464 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -10,6 +10,7 @@ # 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 redis as redis from testcontainers.core.container import DockerContainer @@ -28,8 +29,24 @@ def _connect(self): if not client.ping(): raise Exception - def get_client(self): - return redis.Redis(host=self.get_container_host_ip(), port=self.get_exposed_port(6379)) + def get_client(self, **kwargs): + """get redis client + + Parameters + ---------- + kwargs: dict + Keyword arguments passed to `redis.Redis`. + + Returns + ------- + client: redis.Redis + Redis client to connect to the container. + """ + return redis.Redis( + host=self.get_container_host_ip(), + port=self.get_exposed_port(6379), + **kwargs, + ) def start(self): super().start() From bf9cf206a40ba2e699b6c3f430a0a32e99db3772 Mon Sep 17 00:00:00 2001 From: Maksym Date: Sat, 6 Mar 2021 14:24:28 +0200 Subject: [PATCH 004/425] Add GitHub actions (#120) * add github actions support * add release workflow * remove deploy phase from .travis.yml * configure Python version for setup-python action * handle pull request events in Github actions * run github actions in ubuntu-18.04 --- .github/workflows/main.yml | 45 ++++++++++++++++++++++++++++++ .github/workflows/pypi-release.yml | 41 +++++++++++++++++++++++++++ .travis.yml | 10 ------- 3 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/pypi-release.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..df43fc491 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,45 @@ +name: testcontainers-python +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Cache Python dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles(format('requirements/{0}.txt', matrix.python-version)) }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install system requirements + run: | + sudo apt-get install -y --no-install-recommends unixodbc-dev # required for pyodbc + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list + sudo apt-get -qq update + sudo ACCEPT_EULA=Y apt-get -y install msodbcsql17 + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install wheel + pip install -r requirements/${{ matrix.python-version }}.txt + - name: Run checks + run: | + flake8 + sphinx-build -nW docs docs/_build/html + py.test -sv --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/ + codecov diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml new file mode 100644 index 000000000..c9e09100d --- /dev/null +++ b/.github/workflows/pypi-release.yml @@ -0,0 +1,41 @@ +name: Upload Python packages to PyPi +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-18.04 + env: + python-version: 3.8 + steps: + - uses: actions/checkout@v2 + + - name: Setup python ${{ env.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.python-version }} + + - name: Cache Python dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles(format('requirements/{0}.txt', env.python-version)) }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + pip install -r requirements/${{ env.python-version }}.txt + + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py bdist_wheel + twine upload dist/* diff --git a/.travis.yml b/.travis.yml index 6f1f26f6f..b4e779dfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,13 +37,3 @@ notifications: email: recipients: - sergio_89@ukr.net - -deploy: - provider: pypi - user: tillahoffmann - password: - secure: "MoX7m8vD5z9PMzxyf/AW0/GJrjw75K0O5fWyzYDVK0e9fE4en4DVZQXsQ+bI5aVoOfEpAJ/wGgR4t/tsdAXolZxVyK4qM3JKfv75fHcsT1Le++S6daxruTkJY/DbkY6gj9aSMqGX9M5qumeAF4nA7VXxgQ28B0Fc65UASr4tsQ6ekqumCRpqeFMFi7IHdEm2le8J9LSxlNXOGl1QgNkC/ABPAoZiOGn/hdcugaX4BGaorwDk4if8bfonr42pizNfIkMJgCQdU0n+1KqQdm30zD1JHmVOZXryi+QZUiTLHfvpjhIlHUGr7skU0sohUwen0VEbmgezvsF303bMkfQS3zS/GlwVODv6xGFGr7Vp6sYPc3452eB9wWi08lk1evPiyiFzTb4GJR37u9bwoj22/rtePfhXvP4hZs3KXHLDn6zE2LJT+OXSRWb+UD8TqS9kHIGiR7cqRXeTnca9UyGDgAnO/Q6bYvrKcoqb94ElW4VtMvLfwqGAVWYpdD4YdLZp5FoqA+U/1MuYVV81+z8ocem5f3jnuoYfbkKFbTE0wbYozjDIeirc1Bh6ekuODakF4oKcuWdOgnO5ZEobFO45BIM5DUKkOqsfCielWdLx+A8H7v6Y04bIVwCS3NRYEsuXZoBtda3lQVYVSNGeMJUtd0TCPmzolUTTVX3K2YAe6pw=" - # Required if building on more than one python version because each build will try to deploy - skip_existing: true - on: - tags: true From 7ddfa2f82924509fb056a7b16109e897dab8669f Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 Mar 2021 14:17:05 +0100 Subject: [PATCH 005/425] Remove travis and update release code. (#131) * Remove travis and update release code. * Support old syntax in generate_version.py. --- .github/workflows/pypi-release.yml | 1 + .travis.yml | 39 ------------------------------ MANIFEST.in | 1 + generate_version.py | 12 +++++++++ setup.py | 9 ++++++- 5 files changed, 22 insertions(+), 40 deletions(-) delete mode 100644 .travis.yml create mode 100644 MANIFEST.in create mode 100644 generate_version.py diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index c9e09100d..5339e0260 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -37,5 +37,6 @@ jobs: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | + python generate_version.py python setup.py bdist_wheel twine upload dist/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b4e779dfa..000000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -sudo: required -dist: xenial - -services: - - docker - - xvfb - -addons: - chrome: "stable" - apt: - packages: - - unixodbc-dev # required for pyodbc - -language: python -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" - -install: - - pip install -r requirements/$TRAVIS_PYTHON_VERSION.txt - -before_script: # add the mssql driver for xenial - - curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - - curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list - - sudo apt-get -qq update - - sudo ACCEPT_EULA=Y apt-get -y install msodbcsql17 - -script: - - flake8 - - sphinx-build -nW docs docs/_build/html - - py.test -sv --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/ - - codecov - -notifications: - email: - recipients: - - sergio_89@ukr.net diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..ceeea233f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include VERSION diff --git a/generate_version.py b/generate_version.py new file mode 100644 index 000000000..b8e02c082 --- /dev/null +++ b/generate_version.py @@ -0,0 +1,12 @@ +import os + +# Automatically determine the version to push to pypi +github_ref = os.environ.get('GITHUB_REF', '') +prefix = 'refs/tags/v' +if github_ref.startswith(prefix): + version = github_ref[len(prefix):] + with open('VERSION', 'w') as fp: + fp.write(version) + print('Wrote version %s to VERSION file.' % version) +else: + raise ValueError('Could not identify version in %s.' % github_ref) diff --git a/setup.py b/setup.py index 2e4c66576..071049d5f 100644 --- a/setup.py +++ b/setup.py @@ -16,10 +16,17 @@ with open('README.rst') as fp: long_description = fp.read() +# Load the version number +try: + with open('VERSION') as fp: + version = fp.read().strip() +except FileNotFoundError: + version = 'dev' + setuptools.setup( name='testcontainers', packages=setuptools.find_packages(exclude=['tests']), - version='3.2.0', + version=version, description='Library provides lightweight, throwaway instances of common databases, Selenium ' 'web browsers, or anything else that can run in a Docker container', author='Sergey Pirogov', From 95ac492c2271203f1f002b19ea199e23c0ef724b Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 Mar 2021 14:49:03 +0100 Subject: [PATCH 006/425] Drop support for retired 3.5 and update dependencies. (#132) --- .github/workflows/main.yml | 2 +- Makefile | 2 +- README.rst | 2 +- requirements/3.5.txt | 91 ------------ requirements/3.6.txt | 297 +++++++++++++++++++++++++++---------- requirements/3.7.txt | 297 +++++++++++++++++++++++++++---------- requirements/3.8.txt | 287 +++++++++++++++++++++++++---------- setup.py | 1 - 8 files changed, 643 insertions(+), 336 deletions(-) delete mode 100644 requirements/3.5.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index df43fc491..34ee0be44 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: build: strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8] + python-version: [3.6, 3.7, 3.8] runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 diff --git a/Makefile b/Makefile index 622902071..93ad8bedd 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PYTHON_VERSIONS = 3.5 3.6 3.7 3.8 +PYTHON_VERSIONS = 3.6 3.7 3.8 REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) TESTS = $(addprefix tests/,${PYTHON_VERSIONS}) IMAGES = $(addprefix image/,${PYTHON_VERSIONS}) diff --git a/README.rst b/README.rst index 436e2d6e3..5fae0ae5b 100644 --- a/README.rst +++ b/README.rst @@ -65,7 +65,7 @@ When trying to launch a testcontainer from within a Docker container two things Setting up a development environment ------------------------------------ -We recommend you use a `virtual environment `_ for development. Note that a python version :code:`>=3.5` is required. After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. +We recommend you use a `virtual environment `_ for development. Note that a python version :code:`>=3.6` is required. After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. .. code-block:: bash diff --git a/requirements/3.5.txt b/requirements/3.5.txt deleted file mode 100644 index 009a01ad4..000000000 --- a/requirements/3.5.txt +++ /dev/null @@ -1,91 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --output-file=requirements/3.5.txt requirements.in -# --e file:. # via -r requirements.in -alabaster==0.7.12 # via sphinx -attrs==19.3.0 # via jsonschema, pytest -babel==2.8.0 # via sphinx -bcrypt==3.1.7 # via paramiko -cached-property==1.5.1 # via docker-compose -cachetools==4.1.1 # via google-auth -certifi==2020.6.20 # via requests -cffi==1.14.2 # via bcrypt, cryptography, pynacl -chardet==3.0.4 # via requests -codecov==2.1.8 # via -r requirements.in -coverage==5.2.1 # via codecov, pytest-cov -cryptography==3.0 # via paramiko -cx-oracle==8.0.0 # via testcontainers -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 -dockerpty==0.4.1 # via docker-compose -docopt==0.6.2 # via docker-compose -docutils==0.16 # via sphinx -flake8==3.8.3 # via -r requirements.in -google-api-core[grpc]==1.22.1 # via google-cloud-pubsub -google-auth==1.20.1 # via google-api-core -google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 # via google-api-core, grpc-google-iam-v1 -grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 # via google-api-core, googleapis-common-protos, grpc-google-iam-v1 -idna==2.10 # via requests -imagesize==1.2.0 # via sphinx -importlib-metadata==1.7.0 # via flake8, jsonschema, pluggy, pytest -iniconfig==1.0.1 # via pytest -jinja2==2.11.2 # via sphinx -jsonschema==3.2.0 # via docker-compose -markupsafe==1.1.1 # via jinja2 -mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 # via pytest -neo4j==4.1.0 # via testcontainers -packaging==20.4 # via deprecation, pytest, sphinx -paramiko==2.7.1 # via docker -pathlib2==2.3.5 # via pytest -pluggy==0.13.1 # via pytest -protobuf==3.13.0 # via google-api-core, googleapis-common-protos -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 -pycodestyle==2.6.0 # via flake8 -pycparser==2.20 # via cffi -pyflakes==2.2.0 # via flake8 -pygments==2.6.1 # via sphinx -pymongo==3.11.0 # via testcontainers -pymysql==0.10.0 # via testcontainers -pynacl==1.4.0 # via paramiko -pyodbc==4.0.30 # via testcontainers -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 -python-dotenv==0.14.0 # via docker-compose -pytz==2020.1 # via babel, google-api-core, neo4j -pyyaml==5.3.1 # via docker-compose -redis==3.5.3 # via testcontainers -requests==2.24.0 # via codecov, docker, docker-compose, google-api-core, sphinx -rsa==4.6 # via google-auth -selenium==3.141.0 # via testcontainers -six==1.15.0 # via bcrypt, cryptography, docker, docker-compose, dockerpty, google-api-core, google-auth, grpcio, jsonschema, packaging, pathlib2, protobuf, pynacl, pyrsistent, websocket-client -snowballstemmer==2.0.0 # via sphinx -sphinx==3.2.1 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 # via sphinx -sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 # via sphinx -sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 # via sphinx -sqlalchemy==1.3.18 # via testcontainers -texttable==1.6.2 # via docker-compose -toml==0.10.1 # via pytest -urllib3==1.25.10 # via requests, selenium -websocket-client==0.57.0 # via docker, docker-compose -wrapt==1.12.1 # via testcontainers -zipp==1.2.0 # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/3.6.txt b/requirements/3.6.txt index f9e5c517c..4f494be4b 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -4,87 +4,222 @@ # # pip-compile --output-file=requirements/3.6.txt requirements.in # --e file:. # via -r requirements.in -alabaster==0.7.12 # via sphinx -attrs==19.3.0 # via jsonschema, pytest -babel==2.8.0 # via sphinx -bcrypt==3.2.0 # via paramiko -cached-property==1.5.1 # via docker-compose -cachetools==4.1.1 # via google-auth -certifi==2020.6.20 # via requests -cffi==1.14.2 # via bcrypt, cryptography, pynacl -chardet==3.0.4 # via requests -codecov==2.1.8 # via -r requirements.in -coverage==5.2.1 # via codecov, pytest-cov -cryptography==3.0 # via paramiko -cx-oracle==8.0.0 # via testcontainers -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 -dockerpty==0.4.1 # via docker-compose -docopt==0.6.2 # via docker-compose -docutils==0.16 # via sphinx -flake8==3.8.3 # via -r requirements.in -google-api-core[grpc]==1.22.1 # via google-cloud-pubsub -google-auth==1.20.1 # via google-api-core -google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 # via google-api-core, grpc-google-iam-v1 -grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 # via google-api-core, googleapis-common-protos, grpc-google-iam-v1 -idna==2.10 # via requests -imagesize==1.2.0 # via sphinx -importlib-metadata==1.7.0 # via flake8, jsonschema, pluggy, pytest -iniconfig==1.0.1 # via pytest -jinja2==2.11.2 # via sphinx -jsonschema==3.2.0 # via docker-compose -markupsafe==1.1.1 # via jinja2 -mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 # via pytest -neo4j==4.1.0 # via testcontainers -packaging==20.4 # via deprecation, pytest, sphinx -paramiko==2.7.1 # via docker -pluggy==0.13.1 # via pytest -protobuf==3.13.0 # via google-api-core, googleapis-common-protos -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 -pycodestyle==2.6.0 # via flake8 -pycparser==2.20 # via cffi -pyflakes==2.2.0 # via flake8 -pygments==2.6.1 # via sphinx -pymongo==3.11.0 # via testcontainers -pymysql==0.10.0 # via testcontainers -pynacl==1.4.0 # via paramiko -pyodbc==4.0.30 # via testcontainers -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 -python-dotenv==0.14.0 # via docker-compose -pytz==2020.1 # via babel, google-api-core, neo4j -pyyaml==5.3.1 # via docker-compose -redis==3.5.3 # via testcontainers -requests==2.24.0 # via codecov, docker, docker-compose, google-api-core, sphinx -rsa==4.6 # via google-auth -selenium==3.141.0 # via testcontainers -six==1.15.0 # via bcrypt, cryptography, docker, docker-compose, dockerpty, google-api-core, google-auth, grpcio, jsonschema, packaging, protobuf, pynacl, pyrsistent, websocket-client -snowballstemmer==2.0.0 # via sphinx -sphinx==3.2.1 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 # via sphinx -sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 # via sphinx -sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 # via sphinx -sqlalchemy==1.3.18 # via testcontainers -texttable==1.6.2 # via docker-compose -toml==0.10.1 # via pytest -urllib3==1.25.10 # via requests, selenium -websocket-client==0.57.0 # via docker, docker-compose -wrapt==1.12.1 # via testcontainers -zipp==3.1.0 # via importlib-metadata +-e file:. + # via -r requirements.in +alabaster==0.7.12 + # via sphinx +attrs==19.3.0 + # via + # jsonschema + # pytest +babel==2.8.0 + # via sphinx +bcrypt==3.2.0 + # via paramiko +cached-property==1.5.1 + # via docker-compose +cachetools==4.1.1 + # via google-auth +certifi==2020.6.20 + # via requests +cffi==1.14.2 + # via + # bcrypt + # cryptography + # pynacl +chardet==3.0.4 + # via requests +codecov==2.1.8 + # via -r requirements.in +coverage==5.2.1 + # via + # codecov + # pytest-cov +cryptography==3.0 + # via paramiko +cx-oracle==8.0.0 + # via testcontainers +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 +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.16 + # via sphinx +flake8==3.8.3 + # via -r requirements.in +google-api-core[grpc]==1.22.1 + # via google-cloud-pubsub +google-auth==1.20.1 + # via google-api-core +google-cloud-pubsub==1.7.0 + # via testcontainers +googleapis-common-protos[grpc]==1.52.0 + # via + # google-api-core + # grpc-google-iam-v1 +grpc-google-iam-v1==0.12.3 + # via google-cloud-pubsub +grpcio==1.31.0 + # via + # google-api-core + # googleapis-common-protos + # grpc-google-iam-v1 +idna==2.10 + # via requests +imagesize==1.2.0 + # via sphinx +importlib-metadata==1.7.0 + # via + # flake8 + # jsonschema + # pluggy + # pytest +iniconfig==1.0.1 + # via pytest +jinja2==2.11.2 + # via sphinx +jsonschema==3.2.0 + # via docker-compose +markupsafe==1.1.1 + # via jinja2 +mccabe==0.6.1 + # via flake8 +more-itertools==8.4.0 + # via pytest +neo4j==4.1.0 + # via testcontainers +packaging==20.4 + # via + # deprecation + # pytest + # sphinx +paramiko==2.7.1 + # via docker +pluggy==0.13.1 + # via pytest +protobuf==3.13.0 + # via + # google-api-core + # googleapis-common-protos +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 +pycodestyle==2.6.0 + # via flake8 +pycparser==2.20 + # via cffi +pyflakes==2.2.0 + # via flake8 +pygments==2.6.1 + # via sphinx +pymongo==3.11.0 + # via testcontainers +pymysql==0.10.0 + # via testcontainers +pynacl==1.4.0 + # via paramiko +pyodbc==4.0.30 + # via testcontainers +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 +python-dotenv==0.14.0 + # via docker-compose +pytz==2020.1 + # via + # babel + # google-api-core + # neo4j +pyyaml==5.3.1 + # via docker-compose +redis==3.5.3 + # via testcontainers +requests==2.24.0 + # via + # codecov + # docker + # docker-compose + # google-api-core + # sphinx +rsa==4.6 + # via google-auth +selenium==3.141.0 + # via testcontainers +six==1.15.0 + # via + # bcrypt + # cryptography + # docker + # docker-compose + # dockerpty + # google-api-core + # google-auth + # grpcio + # jsonschema + # packaging + # protobuf + # pynacl + # pyrsistent + # websocket-client +snowballstemmer==2.0.0 + # via sphinx +sphinx==3.2.1 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==1.0.3 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.4 + # via sphinx +sqlalchemy==1.3.18 + # via testcontainers +texttable==1.6.2 + # via docker-compose +toml==0.10.1 + # via pytest +urllib3==1.25.10 + # via + # requests + # selenium +websocket-client==0.57.0 + # via + # docker + # docker-compose +wrapt==1.12.1 + # via testcontainers +zipp==3.1.0 + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.7.txt b/requirements/3.7.txt index c46235555..1671545c1 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -4,87 +4,222 @@ # # pip-compile --output-file=requirements/3.7.txt requirements.in # --e file:. # via -r requirements.in -alabaster==0.7.12 # via sphinx -attrs==19.3.0 # via jsonschema, pytest -babel==2.8.0 # via sphinx -bcrypt==3.2.0 # via paramiko -cached-property==1.5.1 # via docker-compose -cachetools==4.1.1 # via google-auth -certifi==2020.6.20 # via requests -cffi==1.14.2 # via bcrypt, cryptography, pynacl -chardet==3.0.4 # via requests -codecov==2.1.8 # via -r requirements.in -coverage==5.2.1 # via codecov, pytest-cov -cryptography==3.0 # via paramiko -cx-oracle==8.0.0 # via testcontainers -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 -dockerpty==0.4.1 # via docker-compose -docopt==0.6.2 # via docker-compose -docutils==0.16 # via sphinx -flake8==3.8.3 # via -r requirements.in -google-api-core[grpc]==1.22.1 # via google-cloud-pubsub -google-auth==1.20.1 # via google-api-core -google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 # via google-api-core, grpc-google-iam-v1 -grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 # via google-api-core, googleapis-common-protos, grpc-google-iam-v1 -idna==2.10 # via requests -imagesize==1.2.0 # via sphinx -importlib-metadata==1.7.0 # via flake8, jsonschema, pluggy, pytest -iniconfig==1.0.1 # via pytest -jinja2==2.11.2 # via sphinx -jsonschema==3.2.0 # via docker-compose -markupsafe==1.1.1 # via jinja2 -mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 # via pytest -neo4j==4.1.0 # via testcontainers -packaging==20.4 # via deprecation, pytest, sphinx -paramiko==2.7.1 # via docker -pluggy==0.13.1 # via pytest -protobuf==3.13.0 # via google-api-core, googleapis-common-protos -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 -pycodestyle==2.6.0 # via flake8 -pycparser==2.20 # via cffi -pyflakes==2.2.0 # via flake8 -pygments==2.6.1 # via sphinx -pymongo==3.11.0 # via testcontainers -pymysql==0.10.0 # via testcontainers -pynacl==1.4.0 # via paramiko -pyodbc==4.0.30 # via testcontainers -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 -python-dotenv==0.14.0 # via docker-compose -pytz==2020.1 # via babel, google-api-core, neo4j -pyyaml==5.3.1 # via docker-compose -redis==3.5.3 # via testcontainers -requests==2.24.0 # via codecov, docker, docker-compose, google-api-core, sphinx -rsa==4.6 # via google-auth -selenium==3.141.0 # via testcontainers -six==1.15.0 # via bcrypt, cryptography, docker, docker-compose, dockerpty, google-api-core, google-auth, grpcio, jsonschema, packaging, protobuf, pynacl, pyrsistent, websocket-client -snowballstemmer==2.0.0 # via sphinx -sphinx==3.2.1 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 # via sphinx -sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 # via sphinx -sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 # via sphinx -sqlalchemy==1.3.18 # via testcontainers -texttable==1.6.2 # via docker-compose -toml==0.10.1 # via pytest -urllib3==1.25.10 # via requests, selenium -websocket-client==0.57.0 # via docker, docker-compose -wrapt==1.12.1 # via testcontainers -zipp==3.1.0 # via importlib-metadata +-e file:. + # via -r requirements.in +alabaster==0.7.12 + # via sphinx +attrs==19.3.0 + # via + # jsonschema + # pytest +babel==2.8.0 + # via sphinx +bcrypt==3.2.0 + # via paramiko +cached-property==1.5.1 + # via docker-compose +cachetools==4.1.1 + # via google-auth +certifi==2020.6.20 + # via requests +cffi==1.14.2 + # via + # bcrypt + # cryptography + # pynacl +chardet==3.0.4 + # via requests +codecov==2.1.8 + # via -r requirements.in +coverage==5.2.1 + # via + # codecov + # pytest-cov +cryptography==3.0 + # via paramiko +cx-oracle==8.0.0 + # via testcontainers +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 +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.16 + # via sphinx +flake8==3.8.3 + # via -r requirements.in +google-api-core[grpc]==1.22.1 + # via google-cloud-pubsub +google-auth==1.20.1 + # via google-api-core +google-cloud-pubsub==1.7.0 + # via testcontainers +googleapis-common-protos[grpc]==1.52.0 + # via + # google-api-core + # grpc-google-iam-v1 +grpc-google-iam-v1==0.12.3 + # via google-cloud-pubsub +grpcio==1.31.0 + # via + # google-api-core + # googleapis-common-protos + # grpc-google-iam-v1 +idna==2.10 + # via requests +imagesize==1.2.0 + # via sphinx +importlib-metadata==1.7.0 + # via + # flake8 + # jsonschema + # pluggy + # pytest +iniconfig==1.0.1 + # via pytest +jinja2==2.11.2 + # via sphinx +jsonschema==3.2.0 + # via docker-compose +markupsafe==1.1.1 + # via jinja2 +mccabe==0.6.1 + # via flake8 +more-itertools==8.4.0 + # via pytest +neo4j==4.1.0 + # via testcontainers +packaging==20.4 + # via + # deprecation + # pytest + # sphinx +paramiko==2.7.1 + # via docker +pluggy==0.13.1 + # via pytest +protobuf==3.13.0 + # via + # google-api-core + # googleapis-common-protos +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 +pycodestyle==2.6.0 + # via flake8 +pycparser==2.20 + # via cffi +pyflakes==2.2.0 + # via flake8 +pygments==2.6.1 + # via sphinx +pymongo==3.11.0 + # via testcontainers +pymysql==0.10.0 + # via testcontainers +pynacl==1.4.0 + # via paramiko +pyodbc==4.0.30 + # via testcontainers +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 +python-dotenv==0.14.0 + # via docker-compose +pytz==2020.1 + # via + # babel + # google-api-core + # neo4j +pyyaml==5.3.1 + # via docker-compose +redis==3.5.3 + # via testcontainers +requests==2.24.0 + # via + # codecov + # docker + # docker-compose + # google-api-core + # sphinx +rsa==4.6 + # via google-auth +selenium==3.141.0 + # via testcontainers +six==1.15.0 + # via + # bcrypt + # cryptography + # docker + # docker-compose + # dockerpty + # google-api-core + # google-auth + # grpcio + # jsonschema + # packaging + # protobuf + # pynacl + # pyrsistent + # websocket-client +snowballstemmer==2.0.0 + # via sphinx +sphinx==3.2.1 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==1.0.3 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.4 + # via sphinx +sqlalchemy==1.3.18 + # via testcontainers +texttable==1.6.2 + # via docker-compose +toml==0.10.1 + # via pytest +urllib3==1.25.10 + # via + # requests + # selenium +websocket-client==0.57.0 + # via + # docker + # docker-compose +wrapt==1.12.1 + # via testcontainers +zipp==3.1.0 + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.8.txt b/requirements/3.8.txt index b0c455e9d..08c4bb553 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -4,85 +4,214 @@ # # pip-compile --output-file=requirements/3.8.txt requirements.in # --e file:. # via -r requirements.in -alabaster==0.7.12 # via sphinx -attrs==19.3.0 # via jsonschema, pytest -babel==2.8.0 # via sphinx -bcrypt==3.2.0 # via paramiko -cached-property==1.5.1 # via docker-compose -cachetools==4.1.1 # via google-auth -certifi==2020.6.20 # via requests -cffi==1.14.2 # via bcrypt, cryptography, pynacl -chardet==3.0.4 # via requests -codecov==2.1.8 # via -r requirements.in -coverage==5.2.1 # via codecov, pytest-cov -cryptography==3.0 # via paramiko -cx-oracle==8.0.0 # via testcontainers -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 -dockerpty==0.4.1 # via docker-compose -docopt==0.6.2 # via docker-compose -docutils==0.16 # via sphinx -flake8==3.8.3 # via -r requirements.in -google-api-core[grpc]==1.22.1 # via google-cloud-pubsub -google-auth==1.20.1 # via google-api-core -google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 # via google-api-core, grpc-google-iam-v1 -grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 # via google-api-core, googleapis-common-protos, grpc-google-iam-v1 -idna==2.10 # via requests -imagesize==1.2.0 # via sphinx -iniconfig==1.0.1 # via pytest -jinja2==2.11.2 # via sphinx -jsonschema==3.2.0 # via docker-compose -markupsafe==1.1.1 # via jinja2 -mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 # via pytest -neo4j==4.1.0 # via testcontainers -packaging==20.4 # via deprecation, pytest, sphinx -paramiko==2.7.1 # via docker -pluggy==0.13.1 # via pytest -protobuf==3.13.0 # via google-api-core, googleapis-common-protos -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 -pycodestyle==2.6.0 # via flake8 -pycparser==2.20 # via cffi -pyflakes==2.2.0 # via flake8 -pygments==2.6.1 # via sphinx -pymongo==3.11.0 # via testcontainers -pymysql==0.10.0 # via testcontainers -pynacl==1.4.0 # via paramiko -pyodbc==4.0.30 # via testcontainers -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 -python-dotenv==0.14.0 # via docker-compose -pytz==2020.1 # via babel, google-api-core, neo4j -pyyaml==5.3.1 # via docker-compose -redis==3.5.3 # via testcontainers -requests==2.24.0 # via codecov, docker, docker-compose, google-api-core, sphinx -rsa==4.6 # via google-auth -selenium==3.141.0 # via testcontainers -six==1.15.0 # via bcrypt, cryptography, docker, docker-compose, dockerpty, google-api-core, google-auth, grpcio, jsonschema, packaging, protobuf, pynacl, pyrsistent, websocket-client -snowballstemmer==2.0.0 # via sphinx -sphinx==3.2.1 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 # via sphinx -sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 # via sphinx -sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 # via sphinx -sqlalchemy==1.3.18 # via testcontainers -texttable==1.6.2 # via docker-compose -toml==0.10.1 # via pytest -urllib3==1.25.10 # via requests, selenium -websocket-client==0.57.0 # via docker, docker-compose -wrapt==1.12.1 # via testcontainers +-e file:. + # via -r requirements.in +alabaster==0.7.12 + # via sphinx +attrs==19.3.0 + # via + # jsonschema + # pytest +babel==2.8.0 + # via sphinx +bcrypt==3.2.0 + # via paramiko +cached-property==1.5.1 + # via docker-compose +cachetools==4.1.1 + # via google-auth +certifi==2020.6.20 + # via requests +cffi==1.14.2 + # via + # bcrypt + # cryptography + # pynacl +chardet==3.0.4 + # via requests +codecov==2.1.8 + # via -r requirements.in +coverage==5.2.1 + # via + # codecov + # pytest-cov +cryptography==3.0 + # via paramiko +cx-oracle==8.0.0 + # via testcontainers +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 +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.16 + # via sphinx +flake8==3.8.3 + # via -r requirements.in +google-api-core[grpc]==1.22.1 + # via google-cloud-pubsub +google-auth==1.20.1 + # via google-api-core +google-cloud-pubsub==1.7.0 + # via testcontainers +googleapis-common-protos[grpc]==1.52.0 + # via + # google-api-core + # grpc-google-iam-v1 +grpc-google-iam-v1==0.12.3 + # via google-cloud-pubsub +grpcio==1.31.0 + # via + # google-api-core + # googleapis-common-protos + # grpc-google-iam-v1 +idna==2.10 + # via requests +imagesize==1.2.0 + # via sphinx +iniconfig==1.0.1 + # via pytest +jinja2==2.11.2 + # via sphinx +jsonschema==3.2.0 + # via docker-compose +markupsafe==1.1.1 + # via jinja2 +mccabe==0.6.1 + # via flake8 +more-itertools==8.4.0 + # via pytest +neo4j==4.1.0 + # via testcontainers +packaging==20.4 + # via + # deprecation + # pytest + # sphinx +paramiko==2.7.1 + # via docker +pluggy==0.13.1 + # via pytest +protobuf==3.13.0 + # via + # google-api-core + # googleapis-common-protos +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 +pycodestyle==2.6.0 + # via flake8 +pycparser==2.20 + # via cffi +pyflakes==2.2.0 + # via flake8 +pygments==2.6.1 + # via sphinx +pymongo==3.11.0 + # via testcontainers +pymysql==0.10.0 + # via testcontainers +pynacl==1.4.0 + # via paramiko +pyodbc==4.0.30 + # via testcontainers +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 +python-dotenv==0.14.0 + # via docker-compose +pytz==2020.1 + # via + # babel + # google-api-core + # neo4j +pyyaml==5.3.1 + # via docker-compose +redis==3.5.3 + # via testcontainers +requests==2.24.0 + # via + # codecov + # docker + # docker-compose + # google-api-core + # sphinx +rsa==4.6 + # via google-auth +selenium==3.141.0 + # via testcontainers +six==1.15.0 + # via + # bcrypt + # cryptography + # docker + # docker-compose + # dockerpty + # google-api-core + # google-auth + # grpcio + # jsonschema + # packaging + # protobuf + # pynacl + # pyrsistent + # websocket-client +snowballstemmer==2.0.0 + # via sphinx +sphinx==3.2.1 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==1.0.3 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.4 + # via sphinx +sqlalchemy==1.3.18 + # via testcontainers +texttable==1.6.2 + # via docker-compose +toml==0.10.1 + # via pytest +urllib3==1.25.10 + # via + # requests + # selenium +websocket-client==0.57.0 + # via + # docker + # docker-compose +wrapt==1.12.1 + # via testcontainers # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/setup.py b/setup.py index 071049d5f..cfe3dec01 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ 'Intended Audience :: Information Technology', 'Intended Audience :: Developers', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', From bc969c53680606d7fff6a2e8350f5a6f234fcd38 Mon Sep 17 00:00:00 2001 From: Ashay Thorat Date: Mon, 29 Mar 2021 14:55:41 +0530 Subject: [PATCH 007/425] adding kafka test container support (#127) * adding kafka containers support * Removing the assertions inside the for loop as they are not guarenteed to provide correct feedback --- requirements.in | 2 +- requirements/3.6.txt | 1 + requirements/3.7.txt | 1 + requirements/3.8.txt | 1 + setup.py | 3 +- testcontainers/kafka.py | 84 +++++++++++++++++++++++++++++++++++++++++ tests/test_kafka.py | 31 +++++++++++++++ 7 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 testcontainers/kafka.py create mode 100644 tests/test_kafka.py diff --git a/requirements.in b/requirements.in index 7aece96cb..bcce225ef 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] +-e file:.[docker-compose,mysql,oracle,postgresql,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 4f494be4b..034c46197 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -91,6 +91,7 @@ jinja2==2.11.2 # via sphinx jsonschema==3.2.0 # via docker-compose +kafka-python==2.0.2 # via testcontainers markupsafe==1.1.1 # via jinja2 mccabe==0.6.1 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 1671545c1..dea3139a8 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -91,6 +91,7 @@ jinja2==2.11.2 # via sphinx jsonschema==3.2.0 # via docker-compose +kafka-python==2.0.2 # via testcontainers markupsafe==1.1.1 # via jinja2 mccabe==0.6.1 diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 08c4bb553..4959646af 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -85,6 +85,7 @@ jinja2==2.11.2 # via sphinx jsonschema==3.2.0 # via docker-compose +kafka-python==2.0.2 # via testcontainers markupsafe==1.1.1 # via jinja2 mccabe==0.6.1 diff --git a/setup.py b/setup.py index cfe3dec01..1f8d49779 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,8 @@ 'mongo': ['pymongo'], 'redis': ['redis'], 'mssqlserver': ['pyodbc'], - 'neo4j': ['neo4j'] + 'neo4j': ['neo4j'], + 'kafka': ['kafka-python'] }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py new file mode 100644 index 000000000..560a80157 --- /dev/null +++ b/testcontainers/kafka.py @@ -0,0 +1,84 @@ +import tarfile +import time +from io import BytesIO +from textwrap import dedent + +from kafka import KafkaConsumer +from kafka.errors import KafkaError + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class KafkaContainer(DockerContainer): + KAFKA_PORT = 9093 + TC_START_SCRIPT = '/tc-start.sh' + + def __init__(self, image="confluentinc/cp-kafka:5.4.3", port_to_expose=KAFKA_PORT): + super(KafkaContainer, self).__init__(image) + self.port_to_expose = port_to_expose + self.with_exposed_ports(self.port_to_expose) + listeners = 'PLAINTEXT://0.0.0.0:{},BROKER://0.0.0.0:9092'.format(port_to_expose) + self.with_env('KAFKA_LISTENERS', listeners) + self.with_env('KAFKA_LISTENER_SECURITY_PROTOCOL_MAP', + 'BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT') + self.with_env('KAFKA_INTER_BROKER_LISTENER_NAME', 'BROKER') + + self.with_env('KAFKA_BROKER_ID', '1') + self.with_env('KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR', '1') + self.with_env('KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS', '1') + self.with_env('KAFKA_LOG_FLUSH_INTERVAL_MESSAGES', '10000000') + self.with_env('KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS', '0') + + def get_bootstrap_server(self): + host = self.get_container_host_ip() + port = self.get_exposed_port(self.port_to_expose) + return '{}:{}'.format(host, port) + + @wait_container_is_ready() + def _connect(self): + bootstrap_server = self.get_bootstrap_server() + consumer = KafkaConsumer(group_id='test', bootstrap_servers=[bootstrap_server]) + if not consumer.topics(): + raise KafkaError("Unable to connect with kafka container!") + + def tc_start(self): + port = self.get_exposed_port(self.port_to_expose) + listeners = 'PLAINTEXT://localhost:{},BROKER://$(hostname -i):9092'.format(port) + data = ( + dedent( + """ + #!/bin/bash + echo 'clientPort=2181' > zookeeper.properties + echo 'dataDir=/var/lib/zookeeper/data' >> zookeeper.properties + echo 'dataLogDir=/var/lib/zookeeper/log' >> zookeeper.properties + zookeeper-server-start zookeeper.properties & + export KAFKA_ZOOKEEPER_CONNECT='localhost:2181' + export KAFKA_ADVERTISED_LISTENERS={} + . /etc/confluent/docker/bash-config + /etc/confluent/docker/configure + /etc/confluent/docker/launch + """.format(listeners) + ) + .strip() + .encode('utf-8') + ) + self.create_file(data, KafkaContainer.TC_START_SCRIPT) + + def start(self): + script = KafkaContainer.TC_START_SCRIPT + command = 'sh -c "while [ ! -f {} ]; do sleep 0.1; done; sh {}"'.format(script, script) + self.with_command(command) + super().start() + self.tc_start() + self._connect() + return self + + def create_file(self, content: bytes, path: str): + with BytesIO() as archive, tarfile.TarFile(fileobj=archive, mode="w") as tar: + tarinfo = tarfile.TarInfo(name=path) + tarinfo.size = len(content) + tarinfo.mtime = time.time() + tar.addfile(tarinfo, BytesIO(content)) + archive.seek(0) + self.get_wrapped_container().put_archive("/", archive) diff --git a/tests/test_kafka.py b/tests/test_kafka.py new file mode 100644 index 000000000..3f6a80dfc --- /dev/null +++ b/tests/test_kafka.py @@ -0,0 +1,31 @@ +from kafka import KafkaConsumer, KafkaProducer, TopicPartition + +from testcontainers.kafka import KafkaContainer + + +def test_kafka_producer_consumer(): + with KafkaContainer() as container: + produce_and_consume_kafka_message(container) + + +def test_kafka_producer_consumer_custom_port(): + with KafkaContainer(port_to_expose=9888) as container: + assert container.port_to_expose == 9888 + produce_and_consume_kafka_message(container) + + +def produce_and_consume_kafka_message(container): + topic = 'test-topic' + bootstrap_server = container.get_bootstrap_server() + + producer = KafkaProducer(bootstrap_servers=[bootstrap_server]) + producer.send(topic, b"verification message") + producer.flush() + producer.close() + + consumer = KafkaConsumer(bootstrap_servers=[bootstrap_server]) + tp = TopicPartition(topic, 0) + consumer.assign([tp]) + consumer.seek_to_beginning() + assert consumer.end_offsets([tp])[tp] == 1, \ + "Expected exactly one test message to be present on test topic !" From fa36a25b3728b555740d123139ed7bc54b70fc3f Mon Sep 17 00:00:00 2001 From: Petrosyuk <24464919+Petrosyuk@users.noreply.github.com> Date: Thu, 15 Apr 2021 13:42:07 -0400 Subject: [PATCH 008/425] Support passing custom env-file to DockerCompose (#135) * adding support for passing custom env-file (#134) * adding support for passing env-file to DockerCompose (#134) * fixed flake8 * trigger Gihub Actions again Co-authored-by: Anton Petrosyuk --- testcontainers/compose.py | 11 ++++++++--- tests/.env.test | 2 ++ tests/docker-compose-3.yml | 7 +++++++ tests/test_docker_compose.py | 11 ++++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/.env.test create mode 100644 tests/docker-compose-3.yml diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 9e595a605..205144107 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -5,9 +5,8 @@ Allows to spin up services configured via :code:`docker-compose.yml`. """ -import subprocess - import requests +import subprocess from testcontainers.core.waiting_utils import wait_container_is_ready from testcontainers.core.exceptions import NoSuchPortExposed @@ -55,16 +54,19 @@ class DockerCompose(object): expose: - "5555" """ + def __init__( self, filepath, compose_file_name="docker-compose.yml", - pull=False): + pull=False, + env_file=None): self.filepath = filepath self.compose_file_names = compose_file_name if isinstance( compose_file_name, (list, tuple) ) else [compose_file_name] self.pull = pull + self.env_file = env_file def __enter__(self): self.start() @@ -77,12 +79,15 @@ def docker_compose_command(self): docker_compose_cmd = ['docker-compose'] for file in self.compose_file_names: docker_compose_cmd += ['-f', file] + if self.env_file: + docker_compose_cmd += ['--env-file', self.env_file] return docker_compose_cmd def start(self): if self.pull: pull_cmd = self.docker_compose_command() + ['pull'] subprocess.call(pull_cmd, cwd=self.filepath) + up_cmd = self.docker_compose_command() + ['up', '-d'] subprocess.call(up_cmd, cwd=self.filepath) diff --git a/tests/.env.test b/tests/.env.test new file mode 100644 index 000000000..23e151152 --- /dev/null +++ b/tests/.env.test @@ -0,0 +1,2 @@ +TAG_MYSQL_ALLOW_EMPTY_PASSWORD="true" +TAG_TEST_ASSERT_KEY="test_is_passed" \ No newline at end of file diff --git a/tests/docker-compose-3.yml b/tests/docker-compose-3.yml new file mode 100644 index 000000000..35448cb7e --- /dev/null +++ b/tests/docker-compose-3.yml @@ -0,0 +1,7 @@ +mysql: + image: mysql + ports: + - "3306:3306" + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: ${TAG_MYSQL_ALLOW_EMPTY_PASSWORD} + TEST_ASSERT_KEY: ${TAG_TEST_ASSERT_KEY} \ No newline at end of file diff --git a/tests/test_docker_compose.py b/tests/test_docker_compose.py index d13cbc329..747be5e16 100644 --- a/tests/test_docker_compose.py +++ b/tests/test_docker_compose.py @@ -1,4 +1,5 @@ import pytest +import subprocess from testcontainers.compose import DockerCompose from testcontainers.core.docker_client import DockerClient @@ -6,7 +7,7 @@ def test_can_spawn_service_via_compose(): - with DockerCompose("tests") as compose: + with DockerCompose('tests') as compose: host = compose.get_service_host("hub", 4444) port = compose.get_service_port("hub", 4444) assert host == "0.0.0.0" @@ -53,3 +54,11 @@ def test_can_get_logs(): compose.wait_for("http://%s:4444/wd/hub" % docker.host()) stdout, stderr = compose.get_logs() assert stdout, 'There should be something on stdout' + + +def test_can_pass_env_params_by_env_file(): + with DockerCompose('tests', compose_file_name='docker-compose-3.yml', + env_file='.env.test') as _: + check_env_is_set_cmd = 'docker exec tests_mysql_1 printenv | grep TEST_ASSERT_KEY'.split() + out = subprocess.run(check_env_is_set_cmd, stdout=subprocess.PIPE) + assert out.stdout.decode('utf-8').splitlines()[0], 'test_is_passed' From 1a143b0bef3732c8d13f462672da4045356c0861 Mon Sep 17 00:00:00 2001 From: SergeyPirogov Date: Thu, 3 Jun 2021 11:02:52 +0300 Subject: [PATCH 009/425] add host to fix db connection on windows --- testcontainers/core/generic.py | 5 +++-- testcontainers/postgres.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 1cce71afd..c2832c963 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -29,10 +29,11 @@ def _connect(self): def get_connection_url(self): raise NotImplementedError - def _create_connection_url(self, dialect, username, password, port, db_name=None): + def _create_connection_url(self, dialect, username, password, host=None, port=None, db_name=None): if self._container is None: raise RuntimeError("container has not been started") - host = self.get_container_host_ip() + if not host: + host = self.get_container_host_ip() port = self.get_exposed_port(port) url = "{dialect}://{username}:{password}@{host}:{port}".format( dialect=dialect, username=username, password=password, host=host, port=port diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index be9b514ab..502b70c12 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -46,9 +46,10 @@ def _configure(self): self.with_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) self.with_env("POSTGRES_DB", self.POSTGRES_DB) - def get_connection_url(self): + def get_connection_url(self, host="localhost"): return super()._create_connection_url(dialect="postgresql+psycopg2", username=self.POSTGRES_USER, password=self.POSTGRES_PASSWORD, db_name=self.POSTGRES_DB, + host=host, port=self.port_to_expose) From 50f8d8ca1b031b1a2cf5c594c857fc16cf7d226b Mon Sep 17 00:00:00 2001 From: SergeyPirogov Date: Thu, 3 Jun 2021 11:07:40 +0300 Subject: [PATCH 010/425] fix flake --- testcontainers/core/generic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index c2832c963..a486801f5 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -29,7 +29,8 @@ def _connect(self): def get_connection_url(self): raise NotImplementedError - def _create_connection_url(self, dialect, username, password, host=None, port=None, db_name=None): + def _create_connection_url(self, dialect, username, password, + host=None, port=None, db_name=None): if self._container is None: raise RuntimeError("container has not been started") if not host: From fcbad411fdf4e427772144b63d5bd4cfb6646442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4t=C3=A4l=C3=A4?= <63060811+sebastian-hatala-leanix@users.noreply.github.com> Date: Sun, 15 Aug 2021 19:49:30 +0200 Subject: [PATCH 011/425] unset default for host in pg conn url (#145) the host will correctly be assigned in `_create_connection_url` using `get_container_host_ip` --- testcontainers/postgres.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index 502b70c12..c60c60b39 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -46,7 +46,7 @@ def _configure(self): self.with_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) self.with_env("POSTGRES_DB", self.POSTGRES_DB) - def get_connection_url(self, host="localhost"): + def get_connection_url(self, host=None): return super()._create_connection_url(dialect="postgresql+psycopg2", username=self.POSTGRES_USER, password=self.POSTGRES_PASSWORD, From d48294c8cfc7509c50242730d4d310a14d7ebc7c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Dec 2021 17:31:46 -0500 Subject: [PATCH 012/425] Add diagnostics for tests. --- .github/workflows/main.yml | 10 ++++++++++ Dockerfile | 2 +- Dockerfile.diagnostics | 7 +++++++ diagnostics.py | 23 +++++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.diagnostics create mode 100644 diagnostics.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34ee0be44..956a3f188 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,6 +37,16 @@ jobs: python -m pip install --upgrade pip pip install wheel pip install -r requirements/${{ matrix.python-version }}.txt + - name: Run docker diagnostics + run: | + echo "Build minimal container for docker-in-docker diagnostics" + docker build -f Dockerfile.diagnostics -t testcontainers-python . + echo "Bare metal diagnostics" + python diagnostics.py + echo "Container diagnostics with bridge network" + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=bridge testcontainers-python python diagnostics.py + echo "Container diagnostics with host network" + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - name: Run checks run: | flake8 diff --git a/Dockerfile b/Dockerfile index 73d815683..ef3cb7652 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,6 @@ FROM python:${version} WORKDIR /workspace ARG version=3.8 COPY requirements/${version}.txt requirements.txt -COPY setup.py README.md ./ +COPY setup.py README.rst ./ RUN pip install -r requirements.txt COPY . . diff --git a/Dockerfile.diagnostics b/Dockerfile.diagnostics new file mode 100644 index 000000000..83bc6621c --- /dev/null +++ b/Dockerfile.diagnostics @@ -0,0 +1,7 @@ +ARG version=3.8 +FROM python:${version} + +WORKDIR /workspace +COPY setup.py README.rst ./ +RUN pip install -e . +COPY . . diff --git a/diagnostics.py b/diagnostics.py new file mode 100644 index 000000000..79d306fba --- /dev/null +++ b/diagnostics.py @@ -0,0 +1,23 @@ +import json +from testcontainers.core import utils +from testcontainers.core.container import DockerContainer + + +result = { + 'is_linux': utils.is_linux(), + 'is_mac': utils.is_mac(), + 'is_windows': utils.is_windows(), + 'inside_container': utils.inside_container(), + 'default_gateway_ip': utils.default_gateway_ip(), +} + +with DockerContainer('alpine:latest') as container: + client = container.get_docker_client() + result.update({ + 'container_host_ip': container.get_container_host_ip(), + 'docker_client_gateway_ip': client.gateway_ip(container._container.id), + 'docker_client_bridge_ip': client.bridge_ip(container._container.id), + 'docker_client_host': client.host(), + }) + +print(json.dumps(result, indent=2)) From 6fb1a686578aa3380e8b08f72b16707e443331c4 Mon Sep 17 00:00:00 2001 From: Kersten Breuer Date: Wed, 19 Jan 2022 18:35:39 +0100 Subject: [PATCH 013/425] added testcontainer for RabbitMQ (#162) * added testcontainer for RabbitMQ Added a testcontainer for the message broker RabbitMQ. It includes ready-to-use config params for the `pika` client library. This library was added to the dependencies. * Fix linting errors in RabbitMQ * fix spelling in readme Co-authored-by: Naomi Elstein --- .gitignore | 4 ++ README.rst | 3 +- requirements.in | 2 +- requirements/3.6.txt | 19 ++++---- requirements/3.7.txt | 19 ++++---- requirements/3.8.txt | 19 ++++---- setup.py | 3 +- testcontainers/rabbitmq.py | 88 ++++++++++++++++++++++++++++++++++++++ tests/test_rabbitmq.py | 54 +++++++++++++++++++++++ 9 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 testcontainers/rabbitmq.py create mode 100644 tests/test_rabbitmq.py diff --git a/.gitignore b/.gitignore index 89c1c82d5..f0760bd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,7 @@ docs/_build/ .idea/ .venv/ .testrepository/ + +# vscode: +.devcontainer/ +.vscode/ diff --git a/README.rst b/README.rst index 5fae0ae5b..8df059e96 100644 --- a/README.rst +++ b/README.rst @@ -22,6 +22,7 @@ Currently available features: * Microsoft SQL Server container * Generic docker containers * LocalStack +* RabbitMQ Installation ------------ @@ -75,4 +76,4 @@ We recommend you use a `virtual environment =2.1.0 flake8 pytest diff --git a/requirements/3.6.txt b/requirements/3.6.txt index 034c46197..37a8f65ef 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.6 # To update, run: # # pip-compile --output-file=requirements/3.6.txt requirements.in @@ -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 @@ -91,7 +91,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 @@ -107,6 +108,8 @@ packaging==20.4 # sphinx paramiko==2.7.1 # via docker +pika==1.2.0 + # via testcontainers pluggy==0.13.1 # via pytest protobuf==3.13.0 @@ -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 @@ -143,12 +146,12 @@ 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-dotenv==0.14.0 # via docker-compose pytz==2020.1 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index dea3139a8..59383778c 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.7 # To update, run: # # pip-compile --output-file=requirements/3.7.txt requirements.in @@ -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 @@ -91,7 +91,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 @@ -107,6 +108,8 @@ packaging==20.4 # sphinx paramiko==2.7.1 # via docker +pika==1.2.0 + # via testcontainers pluggy==0.13.1 # via pytest protobuf==3.13.0 @@ -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 @@ -143,12 +146,12 @@ 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-dotenv==0.14.0 # via docker-compose pytz==2020.1 diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 4959646af..10dfaa4d5 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # pip-compile --output-file=requirements/3.8.txt requirements.in @@ -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 @@ -85,7 +85,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 @@ -101,6 +102,8 @@ packaging==20.4 # sphinx paramiko==2.7.1 # via docker +pika==1.2.0 + # via testcontainers pluggy==0.13.1 # via pytest protobuf==3.13.0 @@ -111,12 +114,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 @@ -137,12 +140,12 @@ 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-dotenv==0.14.0 # via docker-compose pytz==2020.1 diff --git a/setup.py b/setup.py index 1f8d49779..a0d840d49 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,8 @@ 'redis': ['redis'], 'mssqlserver': ['pyodbc'], 'neo4j': ['neo4j'], - 'kafka': ['kafka-python'] + 'kafka': ['kafka-python'], + 'rabbitmq': ['pika'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py new file mode 100644 index 000000000..2518204a9 --- /dev/null +++ b/testcontainers/rabbitmq.py @@ -0,0 +1,88 @@ +import os +from typing import Optional + +import pika +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class RabbitMqContainer(DockerContainer): + """ + Test container for RabbitMQ. + + Example + ------- + The example spins up a RabbitMQ broker and uses the `pika` client library + (https://pypi.org/project/pika/) establish a connection to the broker. + :: + from testcontainer.rabbitmq import RabbitMqContainer + import pika + + with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq: + + connection = pika.BlockingConnection(rabbitmq.get_connection_params()) + channel = connection.channel() + """ + + RABBITMQ_NODE_PORT = os.environ.get("RABBITMQ_NODE_PORT", 5672) + RABBITMQ_DEFAULT_USER = os.environ.get("RABBITMQ_DEFAULT_USER", "guest") + RABBITMQ_DEFAULT_PASS = os.environ.get("RABBITMQ_DEFAULT_PASS", "guest") + + def __init__( + self, + image: str = "rabbitmq:latest", + port: Optional[int] = None, + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: + """Initialize the RabbitMQ test container. + + Args: + image (str, optional): + The docker image from docker hub. Defaults to "rabbitmq:latest". + port (int, optional): + The port to reach the AMQP API. Defaults to 5672. + username (str, optional): + Overwrite the default username which is "guest". + password (str, optional): + Overwrite the default username which is "guest". + """ + super(RabbitMqContainer, self).__init__(image=image) + self.RABBITMQ_NODE_PORT = port or int(self.RABBITMQ_NODE_PORT) + self.RABBITMQ_DEFAULT_USER = username or self.RABBITMQ_DEFAULT_USER + self.RABBITMQ_DEFAULT_PASS = password or self.RABBITMQ_DEFAULT_PASS + + self.with_exposed_ports(self.RABBITMQ_NODE_PORT) + self.with_env("RABBITMQ_NODE_PORT", self.RABBITMQ_NODE_PORT) + self.with_env("RABBITMQ_DEFAULT_USER", self.RABBITMQ_DEFAULT_USER) + self.with_env("RABBITMQ_DEFAULT_PASS", self.RABBITMQ_DEFAULT_PASS) + + @wait_container_is_ready() + def readiness_probe(self) -> bool: + """Test if the RabbitMQ broker is ready.""" + connection = pika.BlockingConnection(self.get_connection_params()) + if connection.is_open: + connection.close() + return self + raise RuntimeError("Could not open connection to RabbitMQ broker.") + + def get_connection_params(self) -> pika.ConnectionParameters: + """ + Get connection params as a pika.ConnectionParameters object. + For more details see: + https://pika.readthedocs.io/en/latest/modules/parameters.html + """ + credentials = pika.PlainCredentials(username=self.RABBITMQ_DEFAULT_USER, + password=self.RABBITMQ_DEFAULT_PASS) + + return pika.ConnectionParameters( + host=self.get_container_host_ip(), + port=self.get_exposed_port(self.RABBITMQ_NODE_PORT), + credentials=credentials, + ) + + def start(self): + """Start the test container.""" + super().start() + self.readiness_probe() + return self diff --git a/tests/test_rabbitmq.py b/tests/test_rabbitmq.py new file mode 100644 index 000000000..08427a861 --- /dev/null +++ b/tests/test_rabbitmq.py @@ -0,0 +1,54 @@ +from typing import Optional +import json + +import pika +import pytest +from testcontainers.rabbitmq import RabbitMqContainer + +QUEUE = "test-q" +EXCHANGE = "test-exchange" +ROUTING_KEY = "test-route-key" +MESSAGE = {"hello": "world"} + + +@pytest.mark.parametrize( + "port,username,password", + [ + (None, None, None), # use the defaults + (5673, None, None), # test with custom port + (None, "my_test_user", "my_secret_password"), # test with custom credentials + ] +) +def test_docker_run_rabbitmq( + port: Optional[int], + username: Optional[str], + password: Optional[str] +): + """Run rabbitmq test container and use it to deliver a simple message.""" + kwargs = {} + if port is not None: + kwargs["port"] = port + if username is not None: + kwargs["username"] = username + if password is not None: + kwargs["password"] = password + + rabbitmq_container = RabbitMqContainer("rabbitmq:latest", **kwargs) + with rabbitmq_container as rabbitmq: + # connect to rabbitmq: + connection_params = rabbitmq.get_connection_params() + connection = pika.BlockingConnection(connection_params) + + # create exchange and queue: + channel = connection.channel() + channel.exchange_declare(exchange=EXCHANGE, exchange_type="topic") + channel.queue_declare(QUEUE, arguments={}) + channel.queue_bind(QUEUE, EXCHANGE, ROUTING_KEY) + + # pulish message: + encoded_message = json.dumps(MESSAGE) + channel.basic_publish(EXCHANGE, ROUTING_KEY, body=encoded_message) + + _, _, body = channel.basic_get(queue=QUEUE) + received_message = json.loads(body.decode()) + assert received_message == MESSAGE From cfd6b12511d12d4762ac7f1102075b72b453975a Mon Sep 17 00:00:00 2001 From: Robin Tweedie Date: Tue, 11 Jan 2022 14:38:02 +0000 Subject: [PATCH 014/425] use container_host_ip in KAFKA_ADVERTISED_LISTENERS --- testcontainers/kafka.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py index 560a80157..23bf69e1c 100644 --- a/testcontainers/kafka.py +++ b/testcontainers/kafka.py @@ -43,8 +43,9 @@ def _connect(self): raise KafkaError("Unable to connect with kafka container!") def tc_start(self): + host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) - listeners = 'PLAINTEXT://localhost:{},BROKER://$(hostname -i):9092'.format(port) + listeners = 'PLAINTEXT://{}:{},BROKER://$(hostname -i):9092'.format(host, port) data = ( dedent( """ From 6f17878734116131e924bd674180381c92346e91 Mon Sep 17 00:00:00 2001 From: yakimka Date: Wed, 12 Jan 2022 15:01:32 +0200 Subject: [PATCH 015/425] Drop support for python 3.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a0d840d49..4e38fee16 100644 --- a/setup.py +++ b/setup.py @@ -68,5 +68,5 @@ }, long_description_content_type="text/x-rst", long_description=long_description, - python_requires='>=3.5', + python_requires='>=3.6', ) From d737e97fc6a5bf529e34f003238495dd4e016378 Mon Sep 17 00:00:00 2001 From: yakimka Date: Sat, 26 Mar 2022 22:16:56 +0200 Subject: [PATCH 016/425] Add dirs exclude for flake8 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 3b42fe85a..c82dc448f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,3 +13,4 @@ description-file = README.md [flake8] max-line-length = 100 +exclude = .git,__pycache__,build,dist,venv,.venv From d06d1da86fca98fab48a393590303d16b4a13a55 Mon Sep 17 00:00:00 2001 From: "Taylor D. Edmiston" Date: Tue, 30 Nov 2021 15:47:18 -0500 Subject: [PATCH 017/425] Fix link syntax --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8df059e96..855d66249 100644 --- a/README.rst +++ b/README.rst @@ -59,7 +59,7 @@ Usage within Docker (i.e. in a CI) When trying to launch a testcontainer from within a Docker container two things have to be provided: -1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the [official docker images](https://hub.docker.com/_/docker)) or install the client from within the `Dockerfile` specification. +1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. From 6b5461955e196ee4a12b708fb6f9bef750d468ad Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 13 Aug 2021 16:29:40 +0900 Subject: [PATCH 018/425] Add missing _configure to OracleDbContainer Additionally, fix Oracle example. --- testcontainers/oracle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/testcontainers/oracle.py b/testcontainers/oracle.py index ea4d0b3cc..883c36af7 100644 --- a/testcontainers/oracle.py +++ b/testcontainers/oracle.py @@ -9,7 +9,7 @@ class OracleDbContainer(DbContainer): ------- :: - with OracleDbContainer(): + with OracleDbContainer() as oracle: e = sqlalchemy.create_engine(oracle.get_connection_url()) result = e.execute("select 1 from dual") """ @@ -24,3 +24,6 @@ def get_connection_url(self): dialect="oracle", username="system", password="oracle", port=self.container_port, db_name="xe" ) + + def _configure(self): + pass From fc5f2df4ba8157d21ecbf3a9a90cd45a61049f90 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sun, 27 Mar 2022 14:32:52 -0400 Subject: [PATCH 019/425] General maintenance and tidying to ensure tests pass. (#136) * Add M1 support and update dependencies. * Restrict to pubsub client < 2 because of breaking changes. * Add platform option. * Improve error messages. * Clean up database tests. * Add missing skipif reason. * Various changes to make tests pass. * Fix linting errors. * Fail fast given long test cycle. * Only use pymssql for arm. * Only use azure sql edge image for arm. * Use pymssql instead of pyodbc. * Add ConnectionError to list of transient errors. * Add BrokenPipeError to transient errors. * Mark kafka tests as failing. * Simplify Kafka configuration. * Use pymssql in container. * Resolve dependency conflict with flake8. * Partially revert `kafka` changes. * Fix `rabbitmq` readiness probe. * Add ValueError to transient kafka errors. --- .github/workflows/main.yml | 9 +- Dockerfile | 6 + Makefile | 9 +- requirements.in | 3 +- requirements/3.6.txt | 172 +++++++++-------- requirements/3.7.txt | 215 +++++++++++++--------- requirements/3.8.txt | 207 +++++++++++++-------- requirements/3.9.txt | 266 +++++++++++++++++++++++++++ setup.cfg | 13 +- setup.py | 4 +- testcontainers/compose.py | 2 +- testcontainers/core/container.py | 7 +- testcontainers/core/docker_client.py | 16 +- testcontainers/core/generic.py | 3 +- testcontainers/core/utils.py | 5 + testcontainers/core/waiting_utils.py | 22 ++- testcontainers/elasticsearch.py | 2 +- testcontainers/kafka.py | 4 +- testcontainers/mssql.py | 28 +-- testcontainers/mysql.py | 18 +- testcontainers/rabbitmq.py | 2 +- testcontainers/redis.py | 4 +- testcontainers/selenium.py | 4 +- tests/test_db_containers.py | 61 ++---- tests/test_elasticsearch.py | 5 +- tests/test_kafka.py | 1 - tests/test_localstack.py | 3 +- tests/test_new_docker_api.py | 12 +- tests/test_webdriver_container.py | 9 +- 29 files changed, 733 insertions(+), 379 deletions(-) create mode 100644 requirements/3.9.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 956a3f188..aff59631b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,13 +25,6 @@ jobs: restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- - - name: Install system requirements - run: | - sudo apt-get install -y --no-install-recommends unixodbc-dev # required for pyodbc - curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list - sudo apt-get -qq update - sudo ACCEPT_EULA=Y apt-get -y install msodbcsql17 - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -51,5 +44,5 @@ jobs: run: | flake8 sphinx-build -nW docs docs/_build/html - py.test -sv --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/ + py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/ codecov diff --git a/Dockerfile b/Dockerfile index ef3cb7652..f839d68af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,12 @@ ARG version=3.8 FROM python:${version} +WORKDIR /workspace +RUN pip install --upgrade pip \ + && apt-get update \ + && apt-get install -y \ + freetds-dev \ + && rm -rf /var/lib/apt/lists/* WORKDIR /workspace ARG version=3.8 COPY requirements/${version}.txt requirements.txt diff --git a/Makefile b/Makefile index 93ad8bedd..032dc90c0 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -PYTHON_VERSIONS = 3.6 3.7 3.8 +PYTHON_VERSIONS = 3.6 3.7 3.8 3.9 REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) TESTS = $(addprefix tests/,${PYTHON_VERSIONS}) IMAGES = $(addprefix image/,${PYTHON_VERSIONS}) RUN = docker run --rm -it + .PHONY : docs # Default target @@ -17,7 +18,7 @@ requirements : ${REQUIREMENTS} ${REQUIREMENTS} : requirements/%.txt : requirements.in setup.py mkdir -p $(dir $@) ${RUN} -w /workspace -v `pwd`:/workspace python:$* bash -c \ - "pip install pip-tools && pip-compile -v -o $@ $<" + "pip install pip-tools && pip-compile -v --upgrade -o $@ $<" # Targets to build docker images @@ -33,8 +34,8 @@ ${IMAGES} : image/% : requirements/%.txt tests : ${TESTS} ${TESTS} : tests/% : image/% - ${RUN} -v /var/run/docker.sock:/var/run/docker.sock testcontainers-python:$* bash -c \ - "flake8 && pytest -v ${ARGS}" + ${RUN} -v /var/run/docker.sock:/var/run/docker.sock testcontainers-python:$* \ + bash -c "flake8 && pytest -v ${ARGS}" # Target to build the documentation diff --git a/requirements.in b/requirements.in index c84b9a3e8..ec56a5d66 100644 --- a/requirements.in +++ b/requirements.in @@ -1,7 +1,6 @@ -e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq] codecov>=2.1.0 -flake8 +flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pytest pytest-cov sphinx -docker<4.3.0 diff --git a/requirements/3.6.txt b/requirements/3.6.txt index 37a8f65ef..ad694224a 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -8,117 +8,130 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx -attrs==19.3.0 +async-timeout==4.0.2 + # via redis +attrs==21.4.0 # via # jsonschema # pytest -babel==2.8.0 +babel==2.9.1 # via sphinx bcrypt==3.2.0 # via paramiko -cached-property==1.5.1 +cached-property==1.5.2 # via docker-compose -cachetools==4.1.1 +cachetools==4.2.4 # via google-auth -certifi==2020.6.20 +certifi==2021.10.8 # via requests -cffi==1.14.2 +cffi==1.15.0 # via # bcrypt # cryptography # pynacl -chardet==3.0.4 +charset-normalizer==2.0.12 # via requests -codecov==2.1.8 +codecov==2.1.12 # via -r requirements.in -coverage==5.2.1 +coverage[toml]==6.2 # via # codecov # pytest-cov -cryptography==3.0 +cryptography==36.0.2 # via paramiko -cx-oracle==8.0.0 +cx-oracle==8.3.0 # via testcontainers +deprecated==1.2.13 + # via redis deprecation==2.1.0 # via testcontainers -distro==1.5.0 +distro==1.7.0 # via docker-compose -docker[ssh]==4.2.2 +docker[ssh]==5.0.3 # via - # -r requirements.in # docker-compose # testcontainers -docker-compose==1.26.2 +docker-compose==1.29.2 # via testcontainers dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.16 +docutils==0.17.1 # via sphinx -flake8==3.8.3 +entrypoints==0.3 + # via flake8 +flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==1.22.1 +google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==1.20.1 +google-auth==2.6.2 # via google-api-core google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 +googleapis-common-protos[grpc]==1.56.0 # via # google-api-core # grpc-google-iam-v1 + # grpcio-status +greenlet==1.1.2 + # via sqlalchemy grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 +grpcio==1.45.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 -idna==2.10 + # grpcio-status +grpcio-status==1.45.0 + # via google-api-core +idna==3.3 # via requests -imagesize==1.2.0 +imagesize==1.3.0 # via sphinx -importlib-metadata==1.7.0 +importlib-metadata==4.8.3 # via - # flake8 # jsonschema # pluggy # pytest -iniconfig==1.0.1 + # redis + # sphinx + # sqlalchemy +iniconfig==1.1.1 # via pytest -jinja2==2.11.2 +jinja2==3.0.3 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers -markupsafe==1.1.1 +markupsafe==2.0.1 # via jinja2 mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 - # via pytest -neo4j==4.1.0 +neo4j==4.4.2 # via testcontainers -packaging==20.4 +packaging==21.3 # via # deprecation # pytest + # redis # sphinx -paramiko==2.7.1 +paramiko==2.10.3 # via docker pika==1.2.0 # via testcontainers -pluggy==0.13.1 +pluggy==1.0.0 # via pytest -protobuf==3.13.0 +protobuf==3.19.4 # via # google-api-core # googleapis-common-protos -psycopg2-binary==2.8.5 + # grpcio-status +psycopg2-binary==2.9.3 # via testcontainers -py==1.9.0 +py==1.11.0 # via pytest pyasn1==0.4.8 # via @@ -126,103 +139,104 @@ pyasn1==0.4.8 # rsa pyasn1-modules==0.2.8 # via google-auth -pycodestyle==2.6.0 +pycodestyle==2.5.0 # via flake8 -pycparser==2.20 +pycparser==2.21 # via cffi -pyflakes==2.2.0 +pyflakes==2.1.1 # via flake8 -pygments==2.6.1 +pygments==2.11.2 # via sphinx -pymongo==3.11.0 +pymongo==4.0.2 # via testcontainers -pymysql==0.10.0 +pymssql==2.2.4 # via testcontainers -pynacl==1.4.0 - # via paramiko -pyodbc==4.0.30 +pymysql==1.0.2 # via testcontainers -pyparsing==2.4.7 +pynacl==1.5.0 + # via paramiko +pyparsing==3.0.7 # via packaging -pyrsistent==0.16.0 +pyrsistent==0.18.0 # via jsonschema -pytest==6.0.1 +pytest==7.0.1 # via # -r requirements.in # pytest-cov -pytest-cov==2.10.1 +pytest-cov==3.0.0 # via -r requirements.in -python-dotenv==0.14.0 +python-dotenv==0.20.0 # via docker-compose -pytz==2020.1 +pytz==2022.1 # via # babel - # google-api-core # neo4j -pyyaml==5.3.1 +pyyaml==5.4.1 # via docker-compose -redis==3.5.3 +redis==4.2.0 # via testcontainers -requests==2.24.0 +requests==2.27.1 # via # codecov # docker # docker-compose # google-api-core # sphinx -rsa==4.6 +rsa==4.8 # via google-auth selenium==3.141.0 # via testcontainers -six==1.15.0 +six==1.16.0 # via # bcrypt - # cryptography - # docker - # docker-compose # dockerpty - # google-api-core # google-auth # grpcio # jsonschema - # packaging - # protobuf - # pynacl - # pyrsistent + # paramiko # websocket-client -snowballstemmer==2.0.0 +snowballstemmer==2.2.0 # via sphinx -sphinx==3.2.1 +sphinx==4.4.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.3.18 +sqlalchemy==1.4.32 # via testcontainers -texttable==1.6.2 +texttable==1.6.4 # via docker-compose -toml==0.10.1 - # via pytest -urllib3==1.25.10 +tomli==1.2.3 + # via + # coverage + # pytest +typing-extensions==4.1.1 + # via + # async-timeout + # importlib-metadata + # redis +urllib3==1.26.9 # via # requests # selenium -websocket-client==0.57.0 +websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.12.1 - # via testcontainers -zipp==3.1.0 +wrapt==1.14.0 + # via + # deprecated + # testcontainers +zipp==3.6.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 59383778c..9a557afed 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -8,117 +8,148 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx -attrs==19.3.0 +async-generator==1.10 + # via + # trio + # trio-websocket +async-timeout==4.0.2 + # via redis +attrs==21.4.0 # via # jsonschema + # outcome # pytest -babel==2.8.0 + # trio +babel==2.9.1 # via sphinx bcrypt==3.2.0 # via paramiko -cached-property==1.5.1 +cached-property==1.5.2 # via docker-compose -cachetools==4.1.1 +cachetools==5.0.0 # via google-auth -certifi==2020.6.20 - # via requests -cffi==1.14.2 +certifi==2021.10.8 + # via + # requests + # urllib3 +cffi==1.15.0 # via # bcrypt # cryptography # pynacl -chardet==3.0.4 +charset-normalizer==2.0.12 # via requests -codecov==2.1.8 +codecov==2.1.12 # via -r requirements.in -coverage==5.2.1 +coverage[toml]==6.3.2 # via # codecov # pytest-cov -cryptography==3.0 - # via paramiko -cx-oracle==8.0.0 +cryptography==36.0.2 + # via + # paramiko + # pyopenssl + # urllib3 +cx-oracle==8.3.0 # via testcontainers +deprecated==1.2.13 + # via redis deprecation==2.1.0 # via testcontainers -distro==1.5.0 +distro==1.7.0 # via docker-compose -docker[ssh]==4.2.2 +docker[ssh]==5.0.3 # via - # -r requirements.in # docker-compose # testcontainers -docker-compose==1.26.2 +docker-compose==1.29.2 # via testcontainers dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.16 +docutils==0.17.1 # via sphinx -flake8==3.8.3 +entrypoints==0.3 + # via flake8 +flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==1.22.1 +google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==1.20.1 +google-auth==2.6.2 # via google-api-core google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 +googleapis-common-protos[grpc]==1.56.0 # via # google-api-core # grpc-google-iam-v1 + # grpcio-status +greenlet==1.1.2 + # via sqlalchemy grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 +grpcio==1.45.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 -idna==2.10 - # via requests -imagesize==1.2.0 + # grpcio-status +grpcio-status==1.45.0 + # via google-api-core +h11==0.13.0 + # via wsproto +idna==3.3 + # via + # requests + # trio + # urllib3 +imagesize==1.3.0 # via sphinx -importlib-metadata==1.7.0 +importlib-metadata==4.11.3 # via - # flake8 # jsonschema # pluggy # pytest -iniconfig==1.0.1 + # redis + # sphinx + # sqlalchemy +iniconfig==1.1.1 # via pytest -jinja2==2.11.2 +jinja2==3.1.1 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers -markupsafe==1.1.1 +markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 - # via pytest -neo4j==4.1.0 +neo4j==4.4.2 # via testcontainers -packaging==20.4 +outcome==1.1.0 + # via trio +packaging==21.3 # via # deprecation # pytest + # redis # sphinx -paramiko==2.7.1 +paramiko==2.10.3 # via docker pika==1.2.0 # via testcontainers -pluggy==0.13.1 +pluggy==1.0.0 # via pytest -protobuf==3.13.0 +protobuf==3.19.4 # via # google-api-core # googleapis-common-protos -psycopg2-binary==2.8.5 + # grpcio-status +psycopg2-binary==2.9.3 # via testcontainers -py==1.9.0 +py==1.11.0 # via pytest pyasn1==0.4.8 # via @@ -126,103 +157,121 @@ pyasn1==0.4.8 # rsa pyasn1-modules==0.2.8 # via google-auth -pycodestyle==2.6.0 +pycodestyle==2.5.0 # via flake8 -pycparser==2.20 +pycparser==2.21 # via cffi -pyflakes==2.2.0 +pyflakes==2.1.1 # via flake8 -pygments==2.6.1 +pygments==2.11.2 # via sphinx -pymongo==3.11.0 +pymongo==4.0.2 # via testcontainers -pymysql==0.10.0 +pymssql==2.2.4 # via testcontainers -pynacl==1.4.0 - # via paramiko -pyodbc==4.0.30 +pymysql==1.0.2 # via testcontainers -pyparsing==2.4.7 +pynacl==1.5.0 + # via paramiko +pyopenssl==22.0.0 + # via urllib3 +pyparsing==3.0.7 # via packaging -pyrsistent==0.16.0 +pyrsistent==0.18.1 # via jsonschema -pytest==6.0.1 +pysocks==1.7.1 + # via urllib3 +pytest==7.1.1 # via # -r requirements.in # pytest-cov -pytest-cov==2.10.1 +pytest-cov==3.0.0 # via -r requirements.in -python-dotenv==0.14.0 +python-dotenv==0.20.0 # via docker-compose -pytz==2020.1 +pytz==2022.1 # via # babel - # google-api-core # neo4j -pyyaml==5.3.1 +pyyaml==5.4.1 # via docker-compose -redis==3.5.3 +redis==4.2.0 # via testcontainers -requests==2.24.0 +requests==2.27.1 # via # codecov # docker # docker-compose # google-api-core # sphinx -rsa==4.6 +rsa==4.8 # via google-auth -selenium==3.141.0 +selenium==4.1.3 # via testcontainers -six==1.15.0 +six==1.16.0 # via # bcrypt - # cryptography - # docker - # docker-compose # dockerpty - # google-api-core # google-auth # grpcio # jsonschema - # packaging - # protobuf - # pynacl - # pyrsistent + # paramiko # websocket-client -snowballstemmer==2.0.0 +sniffio==1.2.0 + # via trio +snowballstemmer==2.2.0 # via sphinx -sphinx==3.2.1 +sortedcontainers==2.4.0 + # via trio +sphinx==4.4.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.3.18 +sqlalchemy==1.4.32 # via testcontainers -texttable==1.6.2 +texttable==1.6.4 # via docker-compose -toml==0.10.1 - # via pytest -urllib3==1.25.10 +tomli==2.0.1 + # via + # coverage + # pytest +trio==0.20.0 + # via + # selenium + # trio-websocket +trio-websocket==0.9.2 + # via selenium +typing-extensions==4.1.1 + # via + # async-timeout + # h11 + # importlib-metadata + # redis +urllib3[secure,socks]==1.26.9 # via # requests # selenium -websocket-client==0.57.0 +websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.12.1 - # via testcontainers -zipp==3.1.0 +wrapt==1.14.0 + # via + # deprecated + # testcontainers +wsproto==1.1.0 + # via trio-websocket +zipp==3.7.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 10dfaa4d5..be3d9548c 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -8,111 +8,140 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx -attrs==19.3.0 +async-generator==1.10 + # via + # trio + # trio-websocket +async-timeout==4.0.2 + # via redis +attrs==21.4.0 # via # jsonschema + # outcome # pytest -babel==2.8.0 + # trio +babel==2.9.1 # via sphinx bcrypt==3.2.0 # via paramiko -cached-property==1.5.1 - # via docker-compose -cachetools==4.1.1 +cachetools==5.0.0 # via google-auth -certifi==2020.6.20 - # via requests -cffi==1.14.2 +certifi==2021.10.8 + # via + # requests + # urllib3 +cffi==1.15.0 # via # bcrypt # cryptography # pynacl -chardet==3.0.4 +charset-normalizer==2.0.12 # via requests -codecov==2.1.8 +codecov==2.1.12 # via -r requirements.in -coverage==5.2.1 +coverage[toml]==6.3.2 # via # codecov # pytest-cov -cryptography==3.0 - # via paramiko -cx-oracle==8.0.0 +cryptography==36.0.2 + # via + # paramiko + # pyopenssl + # urllib3 +cx-oracle==8.3.0 # via testcontainers +deprecated==1.2.13 + # via redis deprecation==2.1.0 # via testcontainers -distro==1.5.0 +distro==1.7.0 # via docker-compose -docker[ssh]==4.2.2 +docker[ssh]==5.0.3 # via - # -r requirements.in # docker-compose # testcontainers -docker-compose==1.26.2 +docker-compose==1.29.2 # via testcontainers dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.16 +docutils==0.17.1 # via sphinx -flake8==3.8.3 +entrypoints==0.3 + # via flake8 +flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==1.22.1 +google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==1.20.1 +google-auth==2.6.2 # via google-api-core google-cloud-pubsub==1.7.0 # via testcontainers -googleapis-common-protos[grpc]==1.52.0 +googleapis-common-protos[grpc]==1.56.0 # via # google-api-core # grpc-google-iam-v1 + # grpcio-status +greenlet==1.1.2 + # via sqlalchemy grpc-google-iam-v1==0.12.3 # via google-cloud-pubsub -grpcio==1.31.0 +grpcio==1.45.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 -idna==2.10 - # via requests -imagesize==1.2.0 + # grpcio-status +grpcio-status==1.45.0 + # via google-api-core +h11==0.13.0 + # via wsproto +idna==3.3 + # via + # requests + # trio + # urllib3 +imagesize==1.3.0 + # via sphinx +importlib-metadata==4.11.3 # via sphinx -iniconfig==1.0.1 +iniconfig==1.1.1 # via pytest -jinja2==2.11.2 +jinja2==3.1.1 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers -markupsafe==1.1.1 +markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -more-itertools==8.4.0 - # via pytest -neo4j==4.1.0 +neo4j==4.4.2 # via testcontainers -packaging==20.4 +outcome==1.1.0 + # via trio +packaging==21.3 # via # deprecation # pytest + # redis # sphinx -paramiko==2.7.1 +paramiko==2.10.3 # via docker pika==1.2.0 # via testcontainers -pluggy==0.13.1 +pluggy==1.0.0 # via pytest -protobuf==3.13.0 +protobuf==3.19.4 # via # google-api-core # googleapis-common-protos -psycopg2-binary==2.8.5 + # grpcio-status +psycopg2-binary==2.9.3 # via testcontainers -py==1.9.0 +py==1.11.0 # via pytest pyasn1==0.4.8 # via @@ -120,102 +149,118 @@ pyasn1==0.4.8 # rsa pyasn1-modules==0.2.8 # via google-auth -pycodestyle==2.6.0 +pycodestyle==2.5.0 # via flake8 -pycparser==2.20 +pycparser==2.21 # via cffi -pyflakes==2.2.0 +pyflakes==2.1.1 # via flake8 -pygments==2.6.1 +pygments==2.11.2 # via sphinx -pymongo==3.11.0 +pymongo==4.0.2 # via testcontainers -pymysql==0.10.0 +pymssql==2.2.4 # via testcontainers -pynacl==1.4.0 - # via paramiko -pyodbc==4.0.30 +pymysql==1.0.2 # via testcontainers -pyparsing==2.4.7 +pynacl==1.5.0 + # via paramiko +pyopenssl==22.0.0 + # via urllib3 +pyparsing==3.0.7 # via packaging -pyrsistent==0.16.0 +pyrsistent==0.18.1 # via jsonschema -pytest==6.0.1 +pysocks==1.7.1 + # via urllib3 +pytest==7.1.1 # via # -r requirements.in # pytest-cov -pytest-cov==2.10.1 +pytest-cov==3.0.0 # via -r requirements.in -python-dotenv==0.14.0 +python-dotenv==0.20.0 # via docker-compose -pytz==2020.1 +pytz==2022.1 # via # babel - # google-api-core # neo4j -pyyaml==5.3.1 +pyyaml==5.4.1 # via docker-compose -redis==3.5.3 +redis==4.2.0 # via testcontainers -requests==2.24.0 +requests==2.27.1 # via # codecov # docker # docker-compose # google-api-core # sphinx -rsa==4.6 +rsa==4.8 # via google-auth -selenium==3.141.0 +selenium==4.1.3 # via testcontainers -six==1.15.0 +six==1.16.0 # via # bcrypt - # cryptography - # docker - # docker-compose # dockerpty - # google-api-core # google-auth # grpcio # jsonschema - # packaging - # protobuf - # pynacl - # pyrsistent + # paramiko # websocket-client -snowballstemmer==2.0.0 +sniffio==1.2.0 + # via trio +snowballstemmer==2.2.0 # via sphinx -sphinx==3.2.1 +sortedcontainers==2.4.0 + # via trio +sphinx==4.4.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.3.18 +sqlalchemy==1.4.32 # via testcontainers -texttable==1.6.2 +texttable==1.6.4 # via docker-compose -toml==0.10.1 - # via pytest -urllib3==1.25.10 +tomli==2.0.1 + # via + # coverage + # pytest +trio==0.20.0 + # via + # selenium + # trio-websocket +trio-websocket==0.9.2 + # via selenium +typing-extensions==4.1.1 + # via redis +urllib3[secure,socks]==1.26.9 # via # requests # selenium -websocket-client==0.57.0 +websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.12.1 - # via testcontainers +wrapt==1.14.0 + # via + # deprecated + # testcontainers +wsproto==1.1.0 + # via trio-websocket +zipp==3.7.0 + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.9.txt b/requirements/3.9.txt new file mode 100644 index 000000000..8cb78d91b --- /dev/null +++ b/requirements/3.9.txt @@ -0,0 +1,266 @@ +# +# This file is autogenerated by pip-compile with python 3.9 +# To update, run: +# +# pip-compile --output-file=requirements/3.9.txt requirements.in +# +-e file:. + # via -r requirements.in +alabaster==0.7.12 + # via sphinx +async-generator==1.10 + # via + # trio + # trio-websocket +async-timeout==4.0.2 + # via redis +attrs==21.4.0 + # via + # jsonschema + # outcome + # pytest + # trio +babel==2.9.1 + # via sphinx +bcrypt==3.2.0 + # via paramiko +cachetools==5.0.0 + # via google-auth +certifi==2021.10.8 + # via + # requests + # urllib3 +cffi==1.15.0 + # via + # bcrypt + # cryptography + # pynacl +charset-normalizer==2.0.12 + # via requests +codecov==2.1.12 + # via -r requirements.in +coverage[toml]==6.3.2 + # via + # codecov + # pytest-cov +cryptography==36.0.2 + # via + # paramiko + # pyopenssl + # urllib3 +cx-oracle==8.3.0 + # via testcontainers +deprecated==1.2.13 + # via redis +deprecation==2.1.0 + # via testcontainers +distro==1.7.0 + # via docker-compose +docker[ssh]==5.0.3 + # via + # docker-compose + # testcontainers +docker-compose==1.29.2 + # via testcontainers +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.17.1 + # via sphinx +entrypoints==0.3 + # via flake8 +flake8==3.7.9 + # via -r requirements.in +google-api-core[grpc]==2.7.1 + # via google-cloud-pubsub +google-auth==2.6.2 + # via google-api-core +google-cloud-pubsub==1.7.0 + # via testcontainers +googleapis-common-protos[grpc]==1.56.0 + # via + # google-api-core + # grpc-google-iam-v1 + # grpcio-status +greenlet==1.1.2 + # via sqlalchemy +grpc-google-iam-v1==0.12.3 + # via google-cloud-pubsub +grpcio==1.45.0 + # via + # google-api-core + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +grpcio-status==1.45.0 + # via google-api-core +h11==0.13.0 + # via wsproto +idna==3.3 + # via + # requests + # trio + # urllib3 +imagesize==1.3.0 + # via sphinx +importlib-metadata==4.11.3 + # via sphinx +iniconfig==1.1.1 + # via pytest +jinja2==3.1.1 + # via sphinx +jsonschema==3.2.0 + # via docker-compose +kafka-python==2.0.2 + # via testcontainers +markupsafe==2.1.1 + # via jinja2 +mccabe==0.6.1 + # via flake8 +neo4j==4.4.2 + # via testcontainers +outcome==1.1.0 + # via trio +packaging==21.3 + # via + # deprecation + # pytest + # redis + # sphinx +paramiko==2.10.3 + # via docker +pika==1.2.0 + # via testcontainers +pluggy==1.0.0 + # via pytest +protobuf==3.19.4 + # via + # google-api-core + # googleapis-common-protos + # grpcio-status +psycopg2-binary==2.9.3 + # via testcontainers +py==1.11.0 + # via pytest +pyasn1==0.4.8 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.2.8 + # via google-auth +pycodestyle==2.5.0 + # via flake8 +pycparser==2.21 + # via cffi +pyflakes==2.1.1 + # via flake8 +pygments==2.11.2 + # via sphinx +pymongo==4.0.2 + # via testcontainers +pymssql==2.2.4 + # via testcontainers +pymysql==1.0.2 + # via testcontainers +pynacl==1.5.0 + # via paramiko +pyopenssl==22.0.0 + # via urllib3 +pyparsing==3.0.7 + # via packaging +pyrsistent==0.18.1 + # via jsonschema +pysocks==1.7.1 + # via urllib3 +pytest==7.1.1 + # via + # -r requirements.in + # pytest-cov +pytest-cov==3.0.0 + # via -r requirements.in +python-dotenv==0.20.0 + # via docker-compose +pytz==2022.1 + # via + # babel + # neo4j +pyyaml==5.4.1 + # via docker-compose +redis==4.2.0 + # via testcontainers +requests==2.27.1 + # via + # codecov + # docker + # docker-compose + # google-api-core + # sphinx +rsa==4.8 + # via google-auth +selenium==4.1.3 + # via testcontainers +six==1.16.0 + # via + # bcrypt + # dockerpty + # google-auth + # grpcio + # jsonschema + # paramiko + # websocket-client +sniffio==1.2.0 + # via trio +snowballstemmer==2.2.0 + # via sphinx +sortedcontainers==2.4.0 + # via trio +sphinx==4.4.0 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sqlalchemy==1.4.32 + # via testcontainers +texttable==1.6.4 + # via docker-compose +tomli==2.0.1 + # via + # coverage + # pytest +trio==0.20.0 + # via + # selenium + # trio-websocket +trio-websocket==0.9.2 + # via selenium +typing-extensions==4.1.1 + # via redis +urllib3[secure,socks]==1.26.9 + # via + # requests + # selenium +websocket-client==0.59.0 + # via + # docker + # docker-compose +wrapt==1.14.0 + # via + # deprecated + # testcontainers +wsproto==1.1.0 + # via trio-websocket +zipp==3.7.0 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/setup.cfg b/setup.cfg index c82dc448f..d5bfb97b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,13 @@ -[bumpversion] -current_version = 2.6.0 -tag = True -commit = True - -[bumpversion:file:setup.py] - [bdist_wheel] universal = 1 [metadata] -description-file = README.md +description-file = README.rst [flake8] max-line-length = 100 exclude = .git,__pycache__,build,dist,venv,.venv + +[tools:pytest] +log_cli_level = INFO +log_cli = true diff --git a/setup.py b/setup.py index 4e38fee16..827b4d350 100644 --- a/setup.py +++ b/setup.py @@ -58,10 +58,10 @@ 'oracle': ['sqlalchemy', 'cx_Oracle'], 'postgresql': ['sqlalchemy', 'psycopg2-binary'], 'selenium': ['selenium'], - 'google-cloud-pubsub': ['google-cloud-pubsub'], + 'google-cloud-pubsub': ['google-cloud-pubsub < 2'], 'mongo': ['pymongo'], 'redis': ['redis'], - 'mssqlserver': ['pyodbc'], + 'mssqlserver': ['pymssql'], 'neo4j': ['neo4j'], 'kafka': ['kafka-python'], 'rabbitmq': ['pika'], diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 205144107..89759a693 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -120,7 +120,7 @@ def _get_service_info(self, service, port): .format(port, service)) return result - @wait_container_is_ready() + @wait_container_is_ready(requests.exceptions.ConnectionError) def wait_for(self, url): requests.get(url) return self diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 0ca126ca6..3f4c3c046 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -3,7 +3,7 @@ from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import ContainerStartException -from testcontainers.core.utils import setup_logger, inside_container +from testcontainers.core.utils import setup_logger, inside_container, is_arm logger = setup_logger(__name__) @@ -42,6 +42,11 @@ def with_kwargs(self, **kwargs) -> 'DockerContainer': self._kwargs = kwargs return self + def maybe_emulate_amd64(self) -> 'DockerContainer': + if is_arm(): + return self.with_kwargs(platform='linux/amd64') + return self + def start(self): logger.info("Pulling image %s", self.image) docker_client = self.get_docker_client() diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index 7acc1f3e0..e65d0f63c 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -41,14 +41,24 @@ def run(self, image: str, **kwargs) def port(self, container_id, port): - return self.client.api.port(container_id, port)[0]["HostPort"] + port_mappings = self.client.api.port(container_id, port) + if not port_mappings: + raise RuntimeError(f'port mapping for container {container_id} and port {port} is not ' + 'available') + return port_mappings[0]["HostPort"] + + def get_container(self, container_id): + containers = self.client.api.containers(filters={'id': container_id}) + if not containers: + raise RuntimeError(f'could not get container with id {container_id}') + return containers[0] def bridge_ip(self, container_id): - container = self.client.api.containers(filters={'id': container_id})[0] + container = self.get_container(container_id) return container['NetworkSettings']['Networks']['bridge']['IPAddress'] def gateway_ip(self, container_id): - container = self.client.api.containers(filters={'id': container_id})[0] + container = self.get_container(container_id) return container['NetworkSettings']['Networks']['bridge']['Gateway'] def host(self): diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index a486801f5..3fd59ae34 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -14,13 +14,14 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready from deprecation import deprecated +from sqlalchemy.exc import OperationalError class DbContainer(DockerContainer): def __init__(self, image, **kwargs): super(DbContainer, self).__init__(image, **kwargs) - @wait_container_is_ready() + @wait_container_is_ready(OperationalError) def _connect(self): import sqlalchemy engine = sqlalchemy.create_engine(self.get_connection_url()) diff --git a/testcontainers/core/utils.py b/testcontainers/core/utils.py index fe14d724d..47fd1de27 100644 --- a/testcontainers/core/utils.py +++ b/testcontainers/core/utils.py @@ -1,4 +1,5 @@ import os +import platform import sys import subprocess import logging @@ -39,6 +40,10 @@ def is_windows(): return WIN == os_name() +def is_arm(): + return platform.machine() in ('arm64', 'aarch64') + + def inside_container(): """ Returns true if we are running inside a container. diff --git a/testcontainers/core/waiting_utils.py b/testcontainers/core/waiting_utils.py index 9b224d3b4..e9953ab35 100644 --- a/testcontainers/core/waiting_utils.py +++ b/testcontainers/core/waiting_utils.py @@ -14,6 +14,7 @@ import re import time +import traceback import wrapt @@ -24,7 +25,11 @@ logger = setup_logger(__name__) -def wait_container_is_ready(): +# Get a tuple of transient exceptions for which we'll retry. Other exceptions will be raised. +TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionResetError, BrokenPipeError) + + +def wait_container_is_ready(*transient_exceptions): """ Wait until container is ready. Function that spawn container should be decorated by this method @@ -33,22 +38,23 @@ def wait_container_is_ready(): :return: """ + transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) + @wrapt.decorator def wrapper(wrapped, instance, args, kwargs): exception = None logger.info("Waiting to be ready...") - for _ in range(0, config.MAX_TRIES): + for _ in range(config.MAX_TRIES): try: return wrapped(*args, **kwargs) - except Exception as e: + except transient_exceptions as e: + logger.info('container is not yet ready: %s', traceback.format_exc()) time.sleep(config.SLEEP_TIME) exception = e raise TimeoutException( - """Wait time exceeded {0} sec. - Method {1}, args {2} , kwargs {3}. - Exception {4}""".format(config.MAX_TRIES, - wrapped.__name__, - args, kwargs, exception)) + f'Wait time ({config.MAX_TRIES * config.SLEEP_TIME}s) exceeded for {wrapped.__name__}' + f'(args: {args}, kwargs {kwargs}). Exception: {exception}' + ) return wrapper diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index 692b90d2b..df9203b8f 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -27,7 +27,7 @@ class ElasticSearchContainer(DockerContainer): with ElasticSearchContainer() as es: connection_url = es.get_url() """ - def __init__(self, image="elasticsearch:7.5.0", port_to_expose=9200): + def __init__(self, image="elasticsearch", port_to_expose=9200): super(ElasticSearchContainer, self).__init__(image) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py index 23bf69e1c..c85c28eb3 100644 --- a/testcontainers/kafka.py +++ b/testcontainers/kafka.py @@ -4,7 +4,7 @@ from textwrap import dedent from kafka import KafkaConsumer -from kafka.errors import KafkaError +from kafka.errors import KafkaError, UnrecognizedBrokerVersion, NoBrokersAvailable from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -35,7 +35,7 @@ def get_bootstrap_server(self): port = self.get_exposed_port(self.port_to_expose) return '{}:{}'.format(host, port) - @wait_container_is_ready() + @wait_container_is_ready(UnrecognizedBrokerVersion, NoBrokersAvailable, KafkaError, ValueError) def _connect(self): bootstrap_server = self.get_bootstrap_server() consumer = KafkaConsumer(group_id='test', bootstrap_servers=[bootstrap_server]) diff --git a/testcontainers/mssql.py b/testcontainers/mssql.py index de96c4061..7f2e4a10f 100644 --- a/testcontainers/mssql.py +++ b/testcontainers/mssql.py @@ -20,35 +20,25 @@ class SqlServerContainer(DbContainer): Requires `ODBC Driver 17 for SQL Server `_. """ - SQLSERVER_PASSWORD = environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") - def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA", password=None, - port=1433, dbname="tempdb", driver="ODBC Driver 17 for SQL Server"): + port=1433, dbname="tempdb", dialect='mssql+pymssql'): super(SqlServerContainer, self).__init__(image) - self.SQLSERVER_PASSWORD = password or self.SQLSERVER_PASSWORD + self.SQLSERVER_PASSWORD = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") self.port_to_expose = port self.SQLSERVER_USER = user self.SQLSERVER_DBNAME = dbname - self.SQLSERVER_DRIVER = driver - - self.with_exposed_ports(self.port_to_expose) - self.ACCEPT_EULA = 'Y' - self.MSSQL_PID = 'Developer' + self.dialect = dialect def _configure(self): + self.with_exposed_ports(self.port_to_expose) self.with_env("SA_PASSWORD", self.SQLSERVER_PASSWORD) self.with_env("SQLSERVER_USER", self.SQLSERVER_USER) self.with_env("SQLSERVER_DBNAME", self.SQLSERVER_DBNAME) - self.with_env("ACCEPT_EULA", self.ACCEPT_EULA) - self.with_env("MSSQL_PID", self.MSSQL_PID) - self.with_env("SQLSERVER_DRIVER", self.SQLSERVER_DRIVER) + self.with_env("ACCEPT_EULA", 'Y') def get_connection_url(self): - standard_url = super()._create_connection_url(dialect="mssql+pyodbc", - username=self.SQLSERVER_USER, - password=self.SQLSERVER_PASSWORD, - db_name=self.SQLSERVER_DBNAME, - port=self.port_to_expose) - - return standard_url + "?driver=" + self.SQLSERVER_DRIVER + return super()._create_connection_url( + dialect=self.dialect, username=self.SQLSERVER_USER, password=self.SQLSERVER_PASSWORD, + db_name=self.SQLSERVER_DBNAME, port=self.port_to_expose + ) diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 6f1a507c2..8570589f8 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -33,23 +33,15 @@ class MySqlContainer(DbContainer): result = e.execute("select version()") version, = result.fetchone() """ - MYSQL_USER = environ.get("MYSQL_USER", "test") - MYSQL_ROOT_PASSWORD = environ.get("MYSQL_ROOT_PASSWORD", "test") - MYSQL_PASSWORD = environ.get("MYSQL_PASSWORD", "test") - MYSQL_DATABASE = environ.get("MYSQL_DATABASE", "test") - def __init__(self, image="mysql:latest", **kwargs): super(MySqlContainer, self).__init__(image) self.port_to_expose = 3306 self.with_exposed_ports(self.port_to_expose) - if 'MYSQL_USER' in kwargs: - self.MYSQL_USER = kwargs['MYSQL_USER'] - if 'MYSQL_ROOT_PASSWORD' in kwargs: - self.MYSQL_ROOT_PASSWORD = kwargs['MYSQL_ROOT_PASSWORD'] - if 'MYSQL_PASSWORD' in kwargs: - self.MYSQL_PASSWORD = kwargs['MYSQL_PASSWORD'] - if 'MYSQL_DATABASE' in kwargs: - self.MYSQL_DATABASE = kwargs['MYSQL_DATABASE'] + self.MYSQL_USER = kwargs.get('MYSQL_USER', environ.get('MYSQL_USER', 'test')) + self.MYSQL_ROOT_PASSWORD = kwargs.get('MYSQL_ROOT_PASSWORD', + environ.get('MYSQL_ROOT_PASSWORD', 'test')) + self.MYSQL_PASSWORD = kwargs.get('MYSQL_PASSWORD', environ.get('MYSQL_PASSWORD', 'test')) + self.MYSQL_DATABASE = kwargs.get('MYSQL_DATABASE', environ.get('MYSQL_DATABASE', 'test')) if self.MYSQL_USER == 'root': self.MYSQL_ROOT_PASSWORD = self.MYSQL_PASSWORD diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py index 2518204a9..9c93858e1 100644 --- a/testcontainers/rabbitmq.py +++ b/testcontainers/rabbitmq.py @@ -57,7 +57,7 @@ def __init__( self.with_env("RABBITMQ_DEFAULT_USER", self.RABBITMQ_DEFAULT_USER) self.with_env("RABBITMQ_DEFAULT_PASS", self.RABBITMQ_DEFAULT_PASS) - @wait_container_is_ready() + @wait_container_is_ready(pika.exceptions.IncompatibleProtocolError) def readiness_probe(self) -> bool: """Test if the RabbitMQ broker is ready.""" connection = pika.BlockingConnection(self.get_connection_params()) diff --git a/testcontainers/redis.py b/testcontainers/redis.py index 331fc5464..a8007d599 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -import redis as redis +import redis from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -23,7 +23,7 @@ def __init__(self, image="redis:latest", port_to_expose=6379): self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) - @wait_container_is_ready() + @wait_container_is_ready(redis.exceptions.ConnectionError) def _connect(self): client = self.get_client() if not client.ping(): diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index a4b3144ea..e4343e90b 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -19,6 +19,8 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready +import urllib3 + IMAGES = { "firefox": "selenium/standalone-firefox-debug:latest", @@ -59,7 +61,7 @@ def _configure(self): self.with_env("no_proxy", "localhost") self.with_env("HUB_ENV_no_proxy", "localhost") - @wait_container_is_ready() + @wait_container_is_ready(urllib3.exceptions.HTTPError) def _connect(self): from selenium import webdriver return webdriver.Remote( diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index 2fdc706e8..24beb571c 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -1,8 +1,9 @@ -import pytest import sqlalchemy from pymongo import MongoClient from pymongo.errors import OperationFailure +import pytest +from testcontainers.core.utils import is_arm from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for from testcontainers.mongodb import MongoDbContainer @@ -13,16 +14,17 @@ from testcontainers.postgres import PostgresContainer +@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') def test_docker_run_mysql(): config = MySqlContainer('mysql:5.7.17') with config as mysql: e = sqlalchemy.create_engine(mysql.get_connection_url()) result = e.execute("select version()") for row in result: - assert row[0] == '5.7.17' + assert row[0].startswith('5.7.17') -def test_docker_run_postgress(): +def test_docker_run_postgres(): postgres_container = PostgresContainer("postgres:9.5") with postgres_container as postgres: e = sqlalchemy.create_engine(postgres.get_connection_url()) @@ -31,29 +33,28 @@ def test_docker_run_postgress(): print("server version:", row[0]) +@pytest.mark.skip(reason='test does not verify additional code over `test_docker_run_postgres`') def test_docker_run_greenplum(): - postgres_container = PostgresContainer("datagrip/greenplum:6.8", - user="guest", password="guest", dbname="guest") - with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) + container = PostgresContainer("datagrip/greenplum:6.8", user="guest", password="guest", + dbname="guest") + with container: + e = sqlalchemy.create_engine(container.get_connection_url()) result = e.execute("select version()") for row in result: print("server version:", row[0]) def test_docker_run_mariadb(): - mariadb_container = MySqlContainer("mariadb:10.2.9") - with mariadb_container as mariadb: + with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: e = sqlalchemy.create_engine(mariadb.get_connection_url()) result = e.execute("select version()") for row in result: - assert row[0] == '10.2.9-MariaDB-10.2.9+maria~jessie' + assert row[0].startswith('10.6.5') @pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") def test_docker_run_oracle(): - oracledb_container = OracleDbContainer() - with oracledb_container as oracledb: + with OracleDbContainer() as oracledb: e = sqlalchemy.create_engine(oracledb.get_connection_url()) result = e.execute("select * from V$VERSION") versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', @@ -65,8 +66,7 @@ def test_docker_run_oracle(): def test_docker_run_mongodb(): - mongo_container = MongoDbContainer("mongo:latest") - with mongo_container as mongo: + with MongoDbContainer("mongo:latest") as mongo: db = mongo.get_connection_client().test doc = { "address": { @@ -94,26 +94,8 @@ def test_docker_run_mongodb_connect_without_credentials(): db.restaurants.insert_one({}) -def test_docker_run_neo4j_v35(): - neo4j_container = Neo4jContainer("neo4j:3.5") - with neo4j_container as neo4j: - with neo4j.get_driver() as driver: - with driver.session() as session: - result = session.run( - """ - CALL dbms.components() - YIELD name, versions, edition - UNWIND versions as version - RETURN name, version, edition - """) - record = result.single() - print("server version:", record["name"], record["version"], record["edition"]) - assert record["version"].startswith("3.5") - - def test_docker_run_neo4j_latest(): - neo4j_container = Neo4jContainer() - with neo4j_container as neo4j: + with Neo4jContainer() as neo4j: with neo4j.get_driver() as driver: with driver.session() as session: result = session.run( @@ -129,10 +111,7 @@ def test_docker_run_neo4j_latest(): def test_docker_generic_db(): - mongo_container = DockerContainer("mongo:latest") - mongo_container.with_bind_ports(27017, 27017) - - with mongo_container: + with DockerContainer("mongo:latest").with_bind_ports(27017, 27017) as mongo_container: def connect(): return MongoClient("mongodb://{}:{}".format(mongo_container.get_container_host_ip(), mongo_container.get_exposed_port(27017))) @@ -159,15 +138,15 @@ def connect(): def test_docker_run_mssql(): - config = SqlServerContainer() - with config as mssql: + image = 'mcr.microsoft.com/azure-sql-edge' + dialect = 'mssql+pymssql' + with SqlServerContainer(image, dialect=dialect) as mssql: e = sqlalchemy.create_engine(mssql.get_connection_url()) result = e.execute('select @@servicename') for row in result: assert row[0] == 'MSSQLSERVER' - config = SqlServerContainer(password="1Secure*Password2") - with config as mssql: + with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: e = sqlalchemy.create_engine(mssql.get_connection_url()) result = e.execute('select @@servicename') for row in result: diff --git a/tests/test_elasticsearch.py b/tests/test_elasticsearch.py index d4734ad6a..f9f769cc5 100644 --- a/tests/test_elasticsearch.py +++ b/tests/test_elasticsearch.py @@ -5,6 +5,7 @@ def test_docker_run_elasticsearch(): - with ElasticSearchContainer() as es: + version = '7.16.1' + with ElasticSearchContainer(f'elasticsearch:{version}') as es: resp = urllib.request.urlopen(es.get_url()) - assert json.loads(resp.read().decode())['version']['number'] == '7.5.0' + assert json.loads(resp.read().decode())['version']['number'] == version diff --git a/tests/test_kafka.py b/tests/test_kafka.py index 3f6a80dfc..4efda4377 100644 --- a/tests/test_kafka.py +++ b/tests/test_kafka.py @@ -1,5 +1,4 @@ from kafka import KafkaConsumer, KafkaProducer, TopicPartition - from testcontainers.kafka import KafkaContainer diff --git a/tests/test_localstack.py b/tests/test_localstack.py index d9ae3b29c..8650a5a3c 100644 --- a/tests/test_localstack.py +++ b/tests/test_localstack.py @@ -5,8 +5,7 @@ def test_docker_run_localstack(): - config = LocalStackContainer() - with config as localstack: + with LocalStackContainer() as localstack: resp = urllib.request.urlopen('{}/health'.format(localstack.get_url())) services = json.loads(resp.read().decode())['services'] diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index c1d4677ac..644840b0d 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -3,9 +3,7 @@ from pathlib import Path from testcontainers import mysql - from testcontainers.core.container import DockerContainer -from importlib import reload def setup_module(m): @@ -24,12 +22,10 @@ def test_docker_custom_image(): def test_docker_env_variables(): - reload(mysql) - - db = mysql.MySqlContainer() - db.with_bind_ports(3306, 32785) - with db: - url = db.get_connection_url() + container = mysql.MySqlContainer("mariadb:10.6.5")\ + .with_bind_ports(3306, 32785).maybe_emulate_amd64() + with container: + url = container.get_connection_url() pattern = r'mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db' assert re.match(pattern, url) diff --git a/tests/test_webdriver_container.py b/tests/test_webdriver_container.py index a23c7ce44..e7282aa1b 100644 --- a/tests/test_webdriver_container.py +++ b/tests/test_webdriver_container.py @@ -1,16 +1,15 @@ -from time import sleep - import pytest from selenium.webdriver import DesiredCapabilities from testcontainers.selenium import BrowserWebDriverContainer +from testcontainers.core.utils import is_arm @pytest.mark.parametrize("caps", [DesiredCapabilities.CHROME, DesiredCapabilities.FIREFOX]) def test_webdriver_container_container(caps): - chrome = BrowserWebDriverContainer(caps) + if is_arm(): + pytest.skip('https://github.com/SeleniumHQ/docker-selenium/issues/1076') - with chrome: + with BrowserWebDriverContainer(caps).maybe_emulate_amd64() as chrome: webdriver = chrome.get_driver() webdriver.get("http://google.com") webdriver.find_element_by_name("q").send_keys("Hello") - sleep(1) From 3b8a1b5495236b75a7ed748827b40887700b256d Mon Sep 17 00:00:00 2001 From: jess Date: Sun, 27 Mar 2022 17:49:39 -0300 Subject: [PATCH 020/425] [Fix/144] wait_for_logs working for DockerCompose (#149) * Fix/issue 144 (#1) * added logs for container * updated docker compose for tests * added test for container logs * Add newline at the end of test_core.py * Fix Linter typo and replaced legacy README file extension in dockerfile --- testcontainers/core/container.py | 5 +++++ testcontainers/core/waiting_utils.py | 4 +++- tests/docker-compose-4.yml | 2 ++ tests/test_core.py | 7 +++++++ tests/test_docker_compose.py | 6 ++++++ 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/docker-compose-4.yml diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 3f4c3c046..455cb6e9b 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -132,6 +132,11 @@ def get_wrapped_container(self) -> Container: def get_docker_client(self) -> DockerClient: return self._docker + def get_logs(self): + if not self._container: + raise ContainerStartException("Container should be started before") + return self._container.logs(stderr=False), self._container.logs(stdout=False) + def exec(self, command): if not self._container: raise ContainerStartException("Container should be started before") diff --git a/testcontainers/core/waiting_utils.py b/testcontainers/core/waiting_utils.py index e9953ab35..70294bdf7 100644 --- a/testcontainers/core/waiting_utils.py +++ b/testcontainers/core/waiting_utils.py @@ -90,7 +90,9 @@ def wait_for_logs(container, predicate, timeout=None, interval=1): start = time.time() while True: duration = time.time() - start - if predicate(container._container.logs().decode()): + stdout = container.get_logs()[0].decode() + stderr = container.get_logs()[1].decode() + if predicate(stdout) or predicate(stderr): return duration if timeout and duration > timeout: raise TimeoutError("container did not emit logs satisfying predicate in %.3f seconds" diff --git a/tests/docker-compose-4.yml b/tests/docker-compose-4.yml new file mode 100644 index 000000000..9966c6549 --- /dev/null +++ b/tests/docker-compose-4.yml @@ -0,0 +1,2 @@ +hello-world: + image: "hello-world" diff --git a/tests/test_core.py b/tests/test_core.py index 0b5aced4b..5a6663502 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -13,3 +13,10 @@ def test_raise_timeout(): def test_wait_for_hello(): with DockerContainer("hello-world") as container: wait_for_logs(container, "Hello from Docker!") + + +def test_can_get_logs(): + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + stdout, stderr = container.get_logs() + assert stdout, 'There should be something on stdout' diff --git a/tests/test_docker_compose.py b/tests/test_docker_compose.py index 747be5e16..28650d58d 100644 --- a/tests/test_docker_compose.py +++ b/tests/test_docker_compose.py @@ -4,6 +4,7 @@ from testcontainers.compose import DockerCompose from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import NoSuchPortExposed +from testcontainers.core.waiting_utils import wait_for_logs def test_can_spawn_service_via_compose(): @@ -34,6 +35,11 @@ def test_compose_wait_for_container_ready(): compose.wait_for("http://%s:4444/wd/hub" % docker.host()) +def test_compose_can_wait_for_logs(): + with DockerCompose(filepath="tests", compose_file_name="docker-compose-4.yml") as compose: + wait_for_logs(compose, "Hello from Docker!") + + def test_can_parse_multiple_compose_files(): with DockerCompose(filepath="tests", compose_file_name=["docker-compose.yml", "docker-compose-2.yml"]) as compose: From 9100aaf8701098db11792fad51116a9c8bf0cfd5 Mon Sep 17 00:00:00 2001 From: yakimka Date: Thu, 31 Mar 2022 06:40:38 +0300 Subject: [PATCH 021/425] Fix flacky tests and improvements (#191) * Update build badge in README.rst * Delete extra `WORKDIR` from Dockerfile * Fix error when running `Neo4jContainer` failed by timeout * Fix connect function in `RedisContainer` * Refactor `Neo4jContainer._connect` method --- Dockerfile | 1 - README.rst | 4 ++-- testcontainers/neo4j.py | 28 ++++++++++------------------ testcontainers/redis.py | 2 +- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index f839d68af..fb4ad5d24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ RUN pip install --upgrade pip \ && apt-get install -y \ freetds-dev \ && rm -rf /var/lib/apt/lists/* -WORKDIR /workspace ARG version=3.8 COPY requirements/${version}.txt requirements.txt COPY setup.py README.rst ./ diff --git a/README.rst b/README.rst index 855d66249..09295f92d 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ testcontainers-python ===================== -.. image:: https://travis-ci.org/testcontainers/testcontainers-python.svg?branch=master - :target: https://travis-ci.org/testcontainers/testcontainers-python +.. image:: https://github.com/testcontainers/testcontainers-python/workflows/testcontainers-python/badge.svg + :target: https://github.com/testcontainers/testcontainers-python/actions/workflows/main.yml .. image:: https://img.shields.io/pypi/v/testcontainers.svg?style=flat-square :target: https://pypi.python.org/pypi/testcontainers .. image:: https://readthedocs.org/projects/testcontainers-python/badge/?version=latest diff --git a/testcontainers/neo4j.py b/testcontainers/neo4j.py index f2e6ce72a..b1887de25 100644 --- a/testcontainers/neo4j.py +++ b/testcontainers/neo4j.py @@ -13,11 +13,8 @@ import os -import re -import time from neo4j import GraphDatabase -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 @@ -70,24 +67,19 @@ def get_connection_url(self): @wait_container_is_ready() def _connect(self): - deadline = time.time() + Neo4jContainer.NEO4J_STARTUP_TIMEOUT_SECONDS - regex = re.compile("Remote interface available at", re.MULTILINE).search - # First we wait for Neo4j to say it's listening - wait_for_logs(self, regex, Neo4jContainer.NEO4J_STARTUP_TIMEOUT_SECONDS) + wait_for_logs( + self, + "Remote interface available at", + Neo4jContainer.NEO4J_STARTUP_TIMEOUT_SECONDS, + ) # Then we actually check that the container really is listening - while time.time() < deadline: - with self.get_driver() as driver: - # Drivers may or may not be lazy - # force them to do a round trip to confirm neo4j is working - with driver.session() as session: - session.run("RETURN 1").single() - return - - raise TimeoutException( - "Neo4j did not start within %.3f seconds" % Neo4jContainer.NEO4J_STARTUP_TIMEOUT_SECONDS - ) + with self.get_driver() as driver: + # Drivers may or may not be lazy + # force them to do a round trip to confirm neo4j is working + with driver.session() as session: + session.run("RETURN 1").single() def get_driver(self, **kwargs): return GraphDatabase.driver( diff --git a/testcontainers/redis.py b/testcontainers/redis.py index a8007d599..3b1fc5c26 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -27,7 +27,7 @@ def __init__(self, image="redis:latest", port_to_expose=6379): def _connect(self): client = self.get_client() if not client.ping(): - raise Exception + raise redis.exceptions.ConnectionError("Could not connect to Redis") def get_client(self, **kwargs): """get redis client From 1f0ca14431553d25ec3a8326ee75e9b5e3850d67 Mon Sep 17 00:00:00 2001 From: Alex Loosley Date: Sun, 3 Apr 2022 01:46:05 +0200 Subject: [PATCH 022/425] Build option for DockerCompose (#168) * build option for compose * if condition for adding build command only * test build causes --build arg to be added to docker-compose up call * satisfy flake8 * readability change * separate subprocess method, and fast (but weaker) test of compose with --build * hardcode name of method in object mock to satisfy review request * remove attribute test * Use more refactorable method name, remove now unused compose conext variable, make test py 3.6 compatible. * add back compose.build check Co-authored-by: Alex Loosley Co-authored-by: Till Hoffmann --- testcontainers/compose.py | 16 +++++++++++++--- tests/test_docker_compose.py | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 89759a693..84f20baa5 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -60,12 +60,14 @@ def __init__( filepath, compose_file_name="docker-compose.yml", pull=False, + build=False, env_file=None): self.filepath = filepath self.compose_file_names = compose_file_name if isinstance( compose_file_name, (list, tuple) ) else [compose_file_name] self.pull = pull + self.build = build self.env_file = env_file def __enter__(self): @@ -86,14 +88,17 @@ def docker_compose_command(self): def start(self): if self.pull: pull_cmd = self.docker_compose_command() + ['pull'] - subprocess.call(pull_cmd, cwd=self.filepath) + self._call_command(cmd=pull_cmd) up_cmd = self.docker_compose_command() + ['up', '-d'] - subprocess.call(up_cmd, cwd=self.filepath) + if self.build: + up_cmd.append('--build') + + self._call_command(cmd=up_cmd) def stop(self): down_cmd = self.docker_compose_command() + ['down', '-v'] - subprocess.call(down_cmd, cwd=self.filepath) + self._call_command(cmd=down_cmd) def get_logs(self): logs_cmd = self.docker_compose_command() + ["logs"] @@ -120,6 +125,11 @@ def _get_service_info(self, service, port): .format(port, service)) return result + def _call_command(self, cmd, filepath=None): + if filepath is None: + filepath = self.filepath + subprocess.call(cmd, cwd=filepath) + @wait_container_is_ready(requests.exceptions.ConnectionError) def wait_for(self, url): requests.get(url) diff --git a/tests/test_docker_compose.py b/tests/test_docker_compose.py index 28650d58d..01a36f679 100644 --- a/tests/test_docker_compose.py +++ b/tests/test_docker_compose.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + import pytest import subprocess @@ -23,6 +25,18 @@ def test_can_pull_images_before_spawning_service_via_compose(): assert port == "4444" +def test_can_build_images_before_spawning_service_via_compose(): + with patch.object(DockerCompose, "_call_command") as call_mock: + with DockerCompose("tests", build=True) as compose: + ... + + assert compose.build + docker_compose_cmd = call_mock.call_args_list[0][1]["cmd"] + assert "docker-compose" in docker_compose_cmd + assert "up" in docker_compose_cmd + assert "--build" in docker_compose_cmd + + def test_can_throw_exception_if_no_port_exposed(): with DockerCompose("tests") as compose: with pytest.raises(NoSuchPortExposed): From 066b178db74765889ce1f048e1baaa17940dd331 Mon Sep 17 00:00:00 2001 From: Yossi Shirizli Date: Sun, 3 Apr 2022 18:47:56 +0300 Subject: [PATCH 023/425] Redis container password support (#195) * redis container password support * fixed test * fixed test * fixed test_docker_run_redis_with_password test --- testcontainers/redis.py | 9 ++++++--- tests/test_redis.py | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/testcontainers/redis.py b/testcontainers/redis.py index 3b1fc5c26..a9d852b76 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -12,16 +12,18 @@ # under the License. import redis - from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready class RedisContainer(DockerContainer): - def __init__(self, image="redis:latest", port_to_expose=6379): + def __init__(self, image="redis:latest", port_to_expose=6379, password=None): super(RedisContainer, self).__init__(image) self.port_to_expose = port_to_expose + self.password = password self.with_exposed_ports(self.port_to_expose) + if self.password: + self.with_command(f"redis-server --requirepass {self.password}") @wait_container_is_ready(redis.exceptions.ConnectionError) def _connect(self): @@ -44,7 +46,8 @@ def get_client(self, **kwargs): """ return redis.Redis( host=self.get_container_host_ip(), - port=self.get_exposed_port(6379), + port=self.get_exposed_port(self.port_to_expose), + password=self.password, **kwargs, ) diff --git a/tests/test_redis.py b/tests/test_redis.py index adcc2b666..9bf946442 100644 --- a/tests/test_redis.py +++ b/tests/test_redis.py @@ -15,6 +15,14 @@ def test_docker_run_redis(): assert b'new_msg', msg['data'] +def test_docker_run_redis_with_password(): + config = RedisContainer(password="mypass") + with config as redis: + client = redis.get_client(decode_responses=True) + client.set("hello", "world") + assert client.get("hello") == "world" + + def wait_for_message(pubsub, timeout=1, ignore_subscribe_messages=True): now = time.time() timeout = now + timeout From 2aefee2fe10eae2c05d5adb69892352c71d69de0 Mon Sep 17 00:00:00 2001 From: yakimka Date: Sun, 3 Apr 2022 19:34:56 +0300 Subject: [PATCH 024/425] Added ClickHouse support (#173) * Added ClickHouse support * Improvements for `_configure` method --- README.rst | 1 + docs/database.rst | 3 +- requirements.in | 2 +- requirements/3.6.txt | 21 +++++++++- requirements/3.7.txt | 15 +++++++- requirements/3.8.txt | 15 +++++++- requirements/3.9.txt | 11 +++++- setup.py | 1 + testcontainers/clickhouse.py | 75 ++++++++++++++++++++++++++++++++++++ tests/test_db_containers.py | 11 ++++++ 10 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 testcontainers/clickhouse.py diff --git a/README.rst b/README.rst index 09295f92d..9490bb92f 100644 --- a/README.rst +++ b/README.rst @@ -19,6 +19,7 @@ Currently available features: * Neo4j container * OracleDb container * PostgreSQL Db container +* ClickHouse container * Microsoft SQL Server container * Generic docker containers * LocalStack diff --git a/docs/database.rst b/docs/database.rst index aefc5c6ac..be9843706 100644 --- a/docs/database.rst +++ b/docs/database.rst @@ -1,7 +1,7 @@ Database containers =================== -Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, MongoDb or Neo4j. +Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, MongoDb, ClickHouse or Neo4j. .. autoclass:: testcontainers.mysql.MySqlContainer .. autoclass:: testcontainers.mysql.MariaDbContainer @@ -10,4 +10,5 @@ Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, .. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer .. autoclass:: testcontainers.mongodb.MongoDbContainer .. autoclass:: testcontainers.mssql.SqlServerContainer +.. autoclass:: testcontainers.clickhouse.ClickHouseContainer .. autoclass:: testcontainers.neo4j.Neo4jContainer diff --git a/requirements.in b/requirements.in index ec56a5d66..f6ce29f8f 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,rabbitmq] +-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse] codecov>=2.1.0 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pytest diff --git a/requirements/3.6.txt b/requirements/3.6.txt index ad694224a..01951cfc6 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -16,6 +16,10 @@ attrs==21.4.0 # pytest babel==2.9.1 # via sphinx +backports.zoneinfo==0.2.1 + # via + # pytz-deprecation-shim + # tzlocal bcrypt==3.2.0 # via paramiko cached-property==1.5.2 @@ -31,6 +35,8 @@ cffi==1.15.0 # pynacl charset-normalizer==2.0.12 # via requests +clickhouse-driver==0.2.3 + # via testcontainers codecov==2.1.12 # via -r requirements.in coverage[toml]==6.2 @@ -98,6 +104,8 @@ importlib-metadata==4.8.3 # redis # sphinx # sqlalchemy +importlib-resources==5.4.0 + # via backports.zoneinfo iniconfig==1.1.1 # via pytest jinja2==3.0.3 @@ -170,7 +178,10 @@ python-dotenv==0.20.0 pytz==2022.1 # via # babel + # clickhouse-driver # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal pyyaml==5.4.1 # via docker-compose redis==4.2.0 @@ -197,7 +208,7 @@ six==1.16.0 # websocket-client snowballstemmer==2.2.0 # via sphinx -sphinx==4.4.0 +sphinx==4.5.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -224,6 +235,10 @@ typing-extensions==4.1.1 # async-timeout # importlib-metadata # redis +tzdata==2022.1 + # via pytz-deprecation-shim +tzlocal==4.1 + # via clickhouse-driver urllib3==1.26.9 # via # requests @@ -237,7 +252,9 @@ wrapt==1.14.0 # deprecated # testcontainers zipp==3.6.0 - # via importlib-metadata + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 9a557afed..7488d8b51 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -22,6 +22,10 @@ attrs==21.4.0 # trio babel==2.9.1 # via sphinx +backports-zoneinfo==0.2.1 + # via + # pytz-deprecation-shim + # tzlocal bcrypt==3.2.0 # via paramiko cached-property==1.5.2 @@ -39,6 +43,8 @@ cffi==1.15.0 # pynacl charset-normalizer==2.0.12 # via requests +clickhouse-driver==0.2.3 + # via testcontainers codecov==2.1.12 # via -r requirements.in coverage[toml]==6.3.2 @@ -192,7 +198,10 @@ python-dotenv==0.20.0 pytz==2022.1 # via # babel + # clickhouse-driver # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal pyyaml==5.4.1 # via docker-compose redis==4.2.0 @@ -223,7 +232,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.4.0 +sphinx==4.5.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -257,6 +266,10 @@ typing-extensions==4.1.1 # h11 # importlib-metadata # redis +tzdata==2022.1 + # via pytz-deprecation-shim +tzlocal==4.1 + # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via # requests diff --git a/requirements/3.8.txt b/requirements/3.8.txt index be3d9548c..c1ceb375a 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -22,6 +22,10 @@ attrs==21.4.0 # trio babel==2.9.1 # via sphinx +backports-zoneinfo==0.2.1 + # via + # pytz-deprecation-shim + # tzlocal bcrypt==3.2.0 # via paramiko cachetools==5.0.0 @@ -37,6 +41,8 @@ cffi==1.15.0 # pynacl charset-normalizer==2.0.12 # via requests +clickhouse-driver==0.2.3 + # via testcontainers codecov==2.1.12 # via -r requirements.in coverage[toml]==6.3.2 @@ -184,7 +190,10 @@ python-dotenv==0.20.0 pytz==2022.1 # via # babel + # clickhouse-driver # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal pyyaml==5.4.1 # via docker-compose redis==4.2.0 @@ -215,7 +224,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.4.0 +sphinx==4.5.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -245,6 +254,10 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.1.1 # via redis +tzdata==2022.1 + # via pytz-deprecation-shim +tzlocal==4.1 + # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via # requests diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 8cb78d91b..5bb22e04c 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -37,6 +37,8 @@ cffi==1.15.0 # pynacl charset-normalizer==2.0.12 # via requests +clickhouse-driver==0.2.3 + # via testcontainers codecov==2.1.12 # via -r requirements.in coverage[toml]==6.3.2 @@ -184,7 +186,10 @@ python-dotenv==0.20.0 pytz==2022.1 # via # babel + # clickhouse-driver # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal pyyaml==5.4.1 # via docker-compose redis==4.2.0 @@ -215,7 +220,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.4.0 +sphinx==4.5.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -245,6 +250,10 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.1.1 # via redis +tzdata==2022.1 + # via pytz-deprecation-shim +tzlocal==4.1 + # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via # requests diff --git a/setup.py b/setup.py index 827b4d350..bb72ee38d 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ 'neo4j': ['neo4j'], 'kafka': ['kafka-python'], 'rabbitmq': ['pika'], + 'clickhouse': ['clickhouse-driver'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/clickhouse.py b/testcontainers/clickhouse.py new file mode 100644 index 000000000..585c35e49 --- /dev/null +++ b/testcontainers/clickhouse.py @@ -0,0 +1,75 @@ +# +# 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 os + +import clickhouse_driver +from clickhouse_driver.errors import Error + +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class ClickHouseContainer(DbContainer): + """ + ClickHouse database container. + + Example + ------- + The example spins up a ClickHouse database and connects to it + using the :code:`clickhouse-driver`. + :: + + with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: + with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: + result = client.execute("SELECT version()") + """ + + CLICKHOUSE_USER = os.environ.get("CLICKHOUSE_USER", "test") + CLICKHOUSE_PASSWORD = os.environ.get("CLICKHOUSE_PASSWORD", "test") + CLICKHOUSE_DB = os.environ.get("CLICKHOUSE_DB", "test") + + def __init__( + self, + image="clickhouse/clickhouse-server:latest", + port=9000, + user=None, + password=None, + dbname=None + ): + super().__init__(image=image) + + self.CLICKHOUSE_USER = user or self.CLICKHOUSE_USER + self.CLICKHOUSE_PASSWORD = password or self.CLICKHOUSE_PASSWORD + self.CLICKHOUSE_DB = dbname or self.CLICKHOUSE_DB + self.port_to_expose = port + + @wait_container_is_ready(Error, EOFError) + def _connect(self): + with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: + client.execute("SELECT version()") + + def _configure(self): + self.with_exposed_ports(self.port_to_expose) + self.with_env("CLICKHOUSE_USER", self.CLICKHOUSE_USER) + self.with_env("CLICKHOUSE_PASSWORD", self.CLICKHOUSE_PASSWORD) + self.with_env("CLICKHOUSE_DB", self.CLICKHOUSE_DB) + + def get_connection_url(self, host=None): + return self._create_connection_url( + dialect="clickhouse", + username=self.CLICKHOUSE_USER, + password=self.CLICKHOUSE_PASSWORD, + db_name=self.CLICKHOUSE_DB, + host=host, + port=self.port_to_expose, + ) diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index 24beb571c..1fb001500 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -1,9 +1,11 @@ import sqlalchemy +import clickhouse_driver from pymongo import MongoClient from pymongo.errors import OperationFailure import pytest from testcontainers.core.utils import is_arm +from testcontainers.clickhouse import ClickHouseContainer from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for from testcontainers.mongodb import MongoDbContainer @@ -151,3 +153,12 @@ def test_docker_run_mssql(): result = e.execute('select @@servicename') for row in result: assert row[0] == 'MSSQLSERVER' + + +def test_docker_run_clickhouse(): + clickhouse_container = ClickHouseContainer() + with clickhouse_container as clickhouse: + client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) + result = client.execute("select 'working'") + + assert result == [('working',)] From e3fcead75fc273fe7f69b2d3db7bbf2bc4ba722c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sun, 3 Apr 2022 12:53:53 -0400 Subject: [PATCH 025/425] Fix dependency regression (fixes #194). (#196) --- testcontainers/core/generic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 3fd59ae34..9daec350e 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -14,14 +14,19 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready from deprecation import deprecated -from sqlalchemy.exc import OperationalError +ADDITIONAL_TRANSIENT_ERRORS = [] +try: + from sqlalchemy.exc import OperationalError + ADDITIONAL_TRANSIENT_ERRORS.append(OperationalError) +except ImportError: + pass class DbContainer(DockerContainer): def __init__(self, image, **kwargs): super(DbContainer, self).__init__(image, **kwargs) - @wait_container_is_ready(OperationalError) + @wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS) def _connect(self): import sqlalchemy engine = sqlalchemy.create_engine(self.get_connection_url()) From 3bd7ae992ade4c0228b2edbc522a510dacb25dab Mon Sep 17 00:00:00 2001 From: yakimka Date: Sun, 3 Apr 2022 19:54:10 +0300 Subject: [PATCH 026/425] Fix deprecation warning message for `ElasticsearchContainer` (#171) Co-authored-by: Till Hoffmann --- testcontainers/elasticsearch.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index df9203b8f..df7ee90c6 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -52,5 +52,7 @@ def start(self): return self -ElasticsearchContainer = deprecated(details='Use `ElasticSearchContainer` with a capital S instead ' - 'of `ElasticsearchContainer`.')(ElasticSearchContainer) +@deprecated(details='Use `ElasticSearchContainer` with a capital S instead ' + 'of `ElasticsearchContainer`.') +class ElasticsearchContainer(ElasticSearchContainer): + pass From 083487ee1fad293e2f3f2bdf7e3f41107bd06604 Mon Sep 17 00:00:00 2001 From: Kieran Lea Date: Sun, 3 Apr 2022 16:09:13 -0400 Subject: [PATCH 027/425] Add compose exec_in_container method (#151) * Add compose exec_in_container method * Update testcontainers/compose.py Co-authored-by: Till Hoffmann --- testcontainers/compose.py | 25 +++++++++++++++++++++++++ tests/test_docker_compose.py | 8 ++++++++ 2 files changed, 33 insertions(+) diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 84f20baa5..28cddcfd0 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -110,6 +110,31 @@ def get_logs(self): ) return result.stdout, result.stderr + def exec_in_container(self, service_name, command): + """ + Executes a command in the container of one of the services. + + Parameters + ---------- + service_name: str + Name of the docker compose service to run the command in + command: list[str] + The command to execute + + Returns + ------- + tuple[str, str, int] + stdout, stderr, return code + """ + exec_cmd = self.docker_compose_command() + ['exec', '-T', service_name] + command + result = subprocess.run( + exec_cmd, + cwd=self.filepath, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode + def get_service_port(self, service_name, port): return self._get_service_info(service_name, port)[1] diff --git a/tests/test_docker_compose.py b/tests/test_docker_compose.py index 01a36f679..8739f572f 100644 --- a/tests/test_docker_compose.py +++ b/tests/test_docker_compose.py @@ -82,3 +82,11 @@ def test_can_pass_env_params_by_env_file(): check_env_is_set_cmd = 'docker exec tests_mysql_1 printenv | grep TEST_ASSERT_KEY'.split() out = subprocess.run(check_env_is_set_cmd, stdout=subprocess.PIPE) assert out.stdout.decode('utf-8').splitlines()[0], 'test_is_passed' + + +def test_can_exec_commands(): + with DockerCompose("tests") as compose: + result = compose.exec_in_container('hub', ['echo', 'my_test']) + assert result[0] == 'my_test\n', "The echo should be successful" + assert result[1] == '', "stderr should be empty" + assert result[2] == 0, 'The exit code should be successful' From 4cfe4c3f54d6a4dbfbb03d62bf16556e3aacc598 Mon Sep 17 00:00:00 2001 From: Kieran Lea Date: Mon, 4 Apr 2022 12:30:16 -0400 Subject: [PATCH 028/425] Update compose docs (#198) --- .gitignore | 1 + testcontainers/compose.py | 81 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f0760bd0d..9d6acfb77 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ docs/_build/ .noseids .idea/ .venv/ +venv .testrepository/ # vscode: diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 28cddcfd0..1896b6f73 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -1,5 +1,5 @@ """ -Docker compose support +Docker Compose Support ====================== Allows to spin up services configured via :code:`docker-compose.yml`. @@ -14,7 +14,20 @@ class DockerCompose(object): """ - Docker compose containers. + Manage docker compose environments. + + Parameters + ---------- + filepath: str + The relative directory containing the docker compose configuration file + compose_file_name: str + The file name of the docker compose configuration file + pull: bool + Attempts to pull images before launching environment + build: bool + Whether to build images referenced in the configuration file + env_file: str + Path to an env file containing environment variables to pass to docker compose Example ------- @@ -54,7 +67,6 @@ class DockerCompose(object): expose: - "5555" """ - def __init__( self, filepath, @@ -78,6 +90,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.stop() def docker_compose_command(self): + """ + Returns command parts used for the docker compose commands + + Returns + ------- + list[str] + The docker compose command parts + """ docker_compose_cmd = ['docker-compose'] for file in self.compose_file_names: docker_compose_cmd += ['-f', file] @@ -86,6 +106,9 @@ def docker_compose_command(self): return docker_compose_cmd def start(self): + """ + Starts the docker compose environment. + """ if self.pull: pull_cmd = self.docker_compose_command() + ['pull'] self._call_command(cmd=pull_cmd) @@ -97,10 +120,21 @@ def start(self): self._call_command(cmd=up_cmd) def stop(self): + """ + Stops the docker compose environment. + """ down_cmd = self.docker_compose_command() + ['down', '-v'] self._call_command(cmd=down_cmd) def get_logs(self): + """ + Returns all log output from stdout and stderr + + Returns + ------- + tuple[bytes, bytes] + stdout, stderr + """ logs_cmd = self.docker_compose_command() + ["logs"] result = subprocess.run( logs_cmd, @@ -136,9 +170,39 @@ def exec_in_container(self, service_name, command): return result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode def get_service_port(self, service_name, port): + """ + Returns the mapped port for one of the services. + + Parameters + ---------- + service_name: str + Name of the docker compose service + port: int + The internal port to get the mapping for + + Returns + ------- + str: + The mapped port on the host + """ return self._get_service_info(service_name, port)[1] def get_service_host(self, service_name, port): + """ + Returns the host for one of the services. + + Parameters + ---------- + service_name: str + Name of the docker compose service + port: int + The internal port to get the host for + + Returns + ------- + str: + The hostname for the service + """ return self._get_service_info(service_name, port)[0] def _get_service_info(self, service, port): @@ -157,5 +221,16 @@ def _call_command(self, cmd, filepath=None): @wait_container_is_ready(requests.exceptions.ConnectionError) def wait_for(self, url): + """ + Waits for a response from a given URL. This is typically used to + block until a service in the environment has started and is responding. + Note that it does not assert any sort of return code, only check that + the connection was successful. + + Parameters + ---------- + url: str + URL from one of the services in the environment to use to wait on + """ requests.get(url) return self From 0223ab1ebb65cbb9cbf3aafd393c40438ba8c5ef Mon Sep 17 00:00:00 2001 From: Karolis Labrencis Date: Mon, 4 Apr 2022 22:57:15 +0300 Subject: [PATCH 029/425] Support specifying postgresql drivers (#156) * Support specifying postgresql drivers * Add "postgresql-pg8000" extra dep * Update setup.py Co-authored-by: Till Hoffmann * Update postgres.py * Update postgres.py * fix whitespace Co-authored-by: Till Hoffmann --- testcontainers/postgres.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index c60c60b39..681fd73d4 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -32,12 +32,18 @@ class PostgresContainer(DbContainer): POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "test") POSTGRES_DB = os.environ.get("POSTGRES_DB", "test") - def __init__(self, image="postgres:latest", port=5432, user=None, password=None, dbname=None): + def __init__(self, + image="postgres:latest", + port=5432, user=None, + password=None, + dbname=None, + driver="psycopg2"): super(PostgresContainer, self).__init__(image=image) self.POSTGRES_USER = user or self.POSTGRES_USER self.POSTGRES_PASSWORD = password or self.POSTGRES_PASSWORD self.POSTGRES_DB = dbname or self.POSTGRES_DB self.port_to_expose = port + self.driver = driver self.with_exposed_ports(self.port_to_expose) @@ -47,7 +53,7 @@ def _configure(self): self.with_env("POSTGRES_DB", self.POSTGRES_DB) def get_connection_url(self, host=None): - return super()._create_connection_url(dialect="postgresql+psycopg2", + return super()._create_connection_url(dialect="postgresql+{}".format(self.driver), username=self.POSTGRES_USER, password=self.POSTGRES_PASSWORD, db_name=self.POSTGRES_DB, From 130d89e28f34606e45b2d620225be3b706f65c6e Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Mon, 4 Apr 2022 18:02:19 -0400 Subject: [PATCH 030/425] Consider ConnectionError a transient exception (fixes #193). (#201) --- testcontainers/core/waiting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/core/waiting_utils.py b/testcontainers/core/waiting_utils.py index 70294bdf7..cafc1ae32 100644 --- a/testcontainers/core/waiting_utils.py +++ b/testcontainers/core/waiting_utils.py @@ -26,7 +26,7 @@ # Get a tuple of transient exceptions for which we'll retry. Other exceptions will be raised. -TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionResetError, BrokenPipeError) +TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionError) def wait_container_is_ready(*transient_exceptions): From c9828a30dbdc211921e00e4e96fa4560d3750c40 Mon Sep 17 00:00:00 2001 From: yakimka Date: Mon, 4 Apr 2022 23:22:07 +0300 Subject: [PATCH 031/425] Fix readthedocs build --- .github/workflows/main.yml | 5 ++++- .readthedocs.yml | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aff59631b..8aae73556 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,9 +40,12 @@ jobs: docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=bridge testcontainers-python python diagnostics.py echo "Container diagnostics with host network" docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py + - name: Make docs + if: matrix.python-version == '3.7' + run: | + sphinx-build -nW docs docs/_build/html - name: Run checks run: | flake8 - sphinx-build -nW docs docs/_build/html py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/ codecov diff --git a/.readthedocs.yml b/.readthedocs.yml index 90826f114..4f1886a13 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -16,5 +16,4 @@ formats: all python: version: 3.7 install: - - method: pip - path: . + - requirements: requirements/3.7.txt From 8119ccc1ceb240314bdd6c4ad896271abcbd66c9 Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Fri, 8 Apr 2022 14:06:22 +0200 Subject: [PATCH 032/425] Reduce log level to prevent log file pollution (#202) Co-authored-by: Till Hoffmann Co-authored-by: Till Hoffmann --- testcontainers/core/waiting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/core/waiting_utils.py b/testcontainers/core/waiting_utils.py index cafc1ae32..30d7c439b 100644 --- a/testcontainers/core/waiting_utils.py +++ b/testcontainers/core/waiting_utils.py @@ -48,7 +48,7 @@ def wrapper(wrapped, instance, args, kwargs): try: return wrapped(*args, **kwargs) except transient_exceptions as e: - logger.info('container is not yet ready: %s', traceback.format_exc()) + logger.debug('container is not yet ready: %s', traceback.format_exc()) time.sleep(config.SLEEP_TIME) exception = e raise TimeoutException( From a2bc91eb5c387d389227117b65e6398e21b5da23 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sun, 10 Apr 2022 13:15:25 -0400 Subject: [PATCH 033/425] Update docker version. (#203) * Set lower bound on docker version From my experience, the docker version must be >= 4.0.0 * Update `requirements/*.txt`. Co-authored-by: Naomi Elstein --- requirements/3.6.txt | 20 ++++++++++---------- requirements/3.7.txt | 24 ++++++++++++------------ requirements/3.8.txt | 26 ++++++++++++-------------- requirements/3.9.txt | 26 ++++++++++++-------------- setup.py | 2 +- 5 files changed, 47 insertions(+), 51 deletions(-) diff --git a/requirements/3.6.txt b/requirements/3.6.txt index 01951cfc6..a53e7232e 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -71,9 +71,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==2.6.2 +google-auth==2.6.3 # via google-api-core -google-cloud-pubsub==1.7.0 +google-cloud-pubsub==1.7.1 # via testcontainers googleapis-common-protos[grpc]==1.56.0 # via @@ -82,15 +82,15 @@ googleapis-common-protos[grpc]==1.56.0 # grpcio-status greenlet==1.1.2 # via sqlalchemy -grpc-google-iam-v1==0.12.3 +grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.45.0 +grpcio==1.44.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.45.0 +grpcio-status==1.44.0 # via google-api-core idna==3.3 # via requests @@ -155,7 +155,7 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.0.2 +pymongo==4.1.0 # via testcontainers pymssql==2.2.4 # via testcontainers @@ -163,7 +163,7 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyparsing==3.0.7 +pyparsing==3.0.8 # via packaging pyrsistent==0.18.0 # via jsonschema @@ -184,7 +184,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.0 +redis==4.2.2 # via testcontainers requests==2.27.1 # via @@ -222,7 +222,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.32 +sqlalchemy==1.4.35 # via testcontainers texttable==1.6.4 # via docker-compose @@ -237,7 +237,7 @@ typing-extensions==4.1.1 # redis tzdata==2022.1 # via pytz-deprecation-shim -tzlocal==4.1 +tzlocal==4.2 # via clickhouse-driver urllib3==1.26.9 # via diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 7488d8b51..d1e282bf4 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -82,9 +82,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==2.6.2 +google-auth==2.6.3 # via google-api-core -google-cloud-pubsub==1.7.0 +google-cloud-pubsub==1.7.1 # via testcontainers googleapis-common-protos[grpc]==1.56.0 # via @@ -93,15 +93,15 @@ googleapis-common-protos[grpc]==1.56.0 # grpcio-status greenlet==1.1.2 # via sqlalchemy -grpc-google-iam-v1==0.12.3 +grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.45.0 +grpcio==1.44.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.45.0 +grpcio-status==1.44.0 # via google-api-core h11==0.13.0 # via wsproto @@ -148,7 +148,7 @@ pika==1.2.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.19.4 +protobuf==3.20.0 # via # google-api-core # googleapis-common-protos @@ -171,7 +171,7 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.0.2 +pymongo==4.1.0 # via testcontainers pymssql==2.2.4 # via testcontainers @@ -181,7 +181,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.7 +pyparsing==3.0.8 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -204,7 +204,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.0 +redis==4.2.2 # via testcontainers requests==2.27.1 # via @@ -246,7 +246,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.32 +sqlalchemy==1.4.35 # via testcontainers texttable==1.6.4 # via docker-compose @@ -268,7 +268,7 @@ typing-extensions==4.1.1 # redis tzdata==2022.1 # via pytz-deprecation-shim -tzlocal==4.1 +tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via @@ -284,7 +284,7 @@ wrapt==1.14.0 # testcontainers wsproto==1.1.0 # via trio-websocket -zipp==3.7.0 +zipp==3.8.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.8.txt b/requirements/3.8.txt index c1ceb375a..9e51e43de 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -80,9 +80,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==2.6.2 +google-auth==2.6.3 # via google-api-core -google-cloud-pubsub==1.7.0 +google-cloud-pubsub==1.7.1 # via testcontainers googleapis-common-protos[grpc]==1.56.0 # via @@ -91,15 +91,15 @@ googleapis-common-protos[grpc]==1.56.0 # grpcio-status greenlet==1.1.2 # via sqlalchemy -grpc-google-iam-v1==0.12.3 +grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.45.0 +grpcio==1.44.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.45.0 +grpcio-status==1.44.0 # via google-api-core h11==0.13.0 # via wsproto @@ -140,7 +140,7 @@ pika==1.2.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.19.4 +protobuf==3.20.0 # via # google-api-core # googleapis-common-protos @@ -163,7 +163,7 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.0.2 +pymongo==4.1.0 # via testcontainers pymssql==2.2.4 # via testcontainers @@ -173,7 +173,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.7 +pyparsing==3.0.8 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -196,7 +196,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.0 +redis==4.2.2 # via testcontainers requests==2.27.1 # via @@ -238,7 +238,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.32 +sqlalchemy==1.4.35 # via testcontainers texttable==1.6.4 # via docker-compose @@ -252,11 +252,9 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.1.1 - # via redis tzdata==2022.1 # via pytz-deprecation-shim -tzlocal==4.1 +tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via @@ -272,7 +270,7 @@ wrapt==1.14.0 # testcontainers wsproto==1.1.0 # via trio-websocket -zipp==3.7.0 +zipp==3.8.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 5bb22e04c..6713257e0 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -76,9 +76,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.7.1 # via google-cloud-pubsub -google-auth==2.6.2 +google-auth==2.6.3 # via google-api-core -google-cloud-pubsub==1.7.0 +google-cloud-pubsub==1.7.1 # via testcontainers googleapis-common-protos[grpc]==1.56.0 # via @@ -87,15 +87,15 @@ googleapis-common-protos[grpc]==1.56.0 # grpcio-status greenlet==1.1.2 # via sqlalchemy -grpc-google-iam-v1==0.12.3 +grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.45.0 +grpcio==1.44.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.45.0 +grpcio-status==1.44.0 # via google-api-core h11==0.13.0 # via wsproto @@ -136,7 +136,7 @@ pika==1.2.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.19.4 +protobuf==3.20.0 # via # google-api-core # googleapis-common-protos @@ -159,7 +159,7 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.0.2 +pymongo==4.1.0 # via testcontainers pymssql==2.2.4 # via testcontainers @@ -169,7 +169,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.7 +pyparsing==3.0.8 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -192,7 +192,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.0 +redis==4.2.2 # via testcontainers requests==2.27.1 # via @@ -234,7 +234,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.32 +sqlalchemy==1.4.35 # via testcontainers texttable==1.6.4 # via docker-compose @@ -248,11 +248,9 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.1.1 - # via redis tzdata==2022.1 # via pytz-deprecation-shim -tzlocal==4.1 +tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via @@ -268,7 +266,7 @@ wrapt==1.14.0 # testcontainers wsproto==1.1.0 # via trio-websocket -zipp==3.7.0 +zipp==3.8.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/setup.py b/setup.py index bb72ee38d..c7512a0d1 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ 'Operating System :: MacOS', ], install_requires=[ - 'docker', + 'docker>=4.0.0', 'wrapt', 'deprecation', ], From a8c6c1e5d752eb327512216861dbcfde8a6cd8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Sat, 16 Apr 2022 21:31:08 +0200 Subject: [PATCH 034/425] add keycloak to features --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 9490bb92f..91ae29427 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,7 @@ Currently available features: * Generic docker containers * LocalStack * RabbitMQ +* Keycloak Installation ------------ From d97492b56478db65d93de84e506ab776dc13ad9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Sat, 16 Apr 2022 21:32:00 +0200 Subject: [PATCH 035/425] add python-keycloak requirement --- requirements.in | 2 +- requirements/3.6.txt | 21 ++++++++++++++++----- requirements/3.7.txt | 21 ++++++++++++++++----- requirements/3.8.txt | 21 ++++++++++++++++----- requirements/3.9.txt | 21 ++++++++++++++++----- setup.py | 1 + 6 files changed, 66 insertions(+), 21 deletions(-) diff --git a/requirements.in b/requirements.in index f6ce29f8f..35bd33273 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,rabbitmq,clickhouse] +-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak] codecov>=2.1.0 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pytest diff --git a/requirements/3.6.txt b/requirements/3.6.txt index a53e7232e..51f1d3aee 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -65,13 +65,15 @@ docopt==0.6.2 # via docker-compose docutils==0.17.1 # via sphinx +ecdsa==0.17.0 + # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.2 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.5 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -144,6 +146,7 @@ py==1.11.0 pyasn1==0.4.8 # via # pyasn1-modules + # python-jose # rsa pyasn1-modules==0.2.8 # via google-auth @@ -155,9 +158,9 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -175,6 +178,10 @@ pytest-cov==3.0.0 # via -r requirements.in python-dotenv==0.20.0 # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==0.27.0 + # via testcontainers pytz==2022.1 # via # babel @@ -192,15 +199,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-keycloak # sphinx rsa==4.8 - # via google-auth + # via + # google-auth + # python-jose selenium==3.141.0 # via testcontainers six==1.16.0 # via # bcrypt # dockerpty + # ecdsa # google-auth # grpcio # jsonschema diff --git a/requirements/3.7.txt b/requirements/3.7.txt index d1e282bf4..7b39485ba 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -76,13 +76,15 @@ docopt==0.6.2 # via docker-compose docutils==0.17.1 # via sphinx +ecdsa==0.17.0 + # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.2 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.5 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -160,6 +162,7 @@ py==1.11.0 pyasn1==0.4.8 # via # pyasn1-modules + # python-jose # rsa pyasn1-modules==0.2.8 # via google-auth @@ -171,9 +174,9 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -195,6 +198,10 @@ pytest-cov==3.0.0 # via -r requirements.in python-dotenv==0.20.0 # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==0.27.0 + # via testcontainers pytz==2022.1 # via # babel @@ -212,15 +219,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-keycloak # sphinx rsa==4.8 - # via google-auth + # via + # google-auth + # python-jose selenium==4.1.3 # via testcontainers six==1.16.0 # via # bcrypt # dockerpty + # ecdsa # google-auth # grpcio # jsonschema diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 9e51e43de..48720f54f 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -74,13 +74,15 @@ docopt==0.6.2 # via docker-compose docutils==0.17.1 # via sphinx +ecdsa==0.17.0 + # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.2 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.5 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -152,6 +154,7 @@ py==1.11.0 pyasn1==0.4.8 # via # pyasn1-modules + # python-jose # rsa pyasn1-modules==0.2.8 # via google-auth @@ -163,9 +166,9 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -187,6 +190,10 @@ pytest-cov==3.0.0 # via -r requirements.in python-dotenv==0.20.0 # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==0.27.0 + # via testcontainers pytz==2022.1 # via # babel @@ -204,15 +211,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-keycloak # sphinx rsa==4.8 - # via google-auth + # via + # google-auth + # python-jose selenium==4.1.3 # via testcontainers six==1.16.0 # via # bcrypt # dockerpty + # ecdsa # google-auth # grpcio # jsonschema diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 6713257e0..b2addff74 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -70,13 +70,15 @@ docopt==0.6.2 # via docker-compose docutils==0.17.1 # via sphinx +ecdsa==0.17.0 + # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.2 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.5 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -148,6 +150,7 @@ py==1.11.0 pyasn1==0.4.8 # via # pyasn1-modules + # python-jose # rsa pyasn1-modules==0.2.8 # via google-auth @@ -159,9 +162,9 @@ pyflakes==2.1.1 # via flake8 pygments==2.11.2 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -183,6 +186,10 @@ pytest-cov==3.0.0 # via -r requirements.in python-dotenv==0.20.0 # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==0.27.0 + # via testcontainers pytz==2022.1 # via # babel @@ -200,15 +207,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-keycloak # sphinx rsa==4.8 - # via google-auth + # via + # google-auth + # python-jose selenium==4.1.3 # via testcontainers six==1.16.0 # via # bcrypt # dockerpty + # ecdsa # google-auth # grpcio # jsonschema diff --git a/setup.py b/setup.py index c7512a0d1..de1293dc3 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ 'kafka': ['kafka-python'], 'rabbitmq': ['pika'], 'clickhouse': ['clickhouse-driver'], + 'keycloak': ['python-keycloak'], }, long_description_content_type="text/x-rst", long_description=long_description, From e09be6ca7e77ea2eca7ecc014be4ee021d89e989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Sat, 16 Apr 2022 21:32:54 +0200 Subject: [PATCH 036/425] add Keycloak Container --- testcontainers/keycloak.py | 74 ++++++++++++++++++++++++++++++++++++++ tests/test_keycloak.py | 8 +++++ 2 files changed, 82 insertions(+) create mode 100644 testcontainers/keycloak.py create mode 100644 tests/test_keycloak.py diff --git a/testcontainers/keycloak.py b/testcontainers/keycloak.py new file mode 100644 index 000000000..5f03e238c --- /dev/null +++ b/testcontainers/keycloak.py @@ -0,0 +1,74 @@ +# +# 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 os +import requests + +from keycloak import KeycloakAdmin + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class KeycloakContainer(DockerContainer): + """ + Keycloak container. + + Example + ------- + :: + + with KeycloakContainer() as kc: + keycloak: KeycloakAdmin = kc.get_client() + """ + KEYCLOAK_USER = os.environ.get("KEYCLOAK_USER", "test") + KEYCLOAK_PASSWORD = os.environ.get("KEYCLOAK_PASSWORD", "test") + + def __init__(self, image="jboss/keycloak:latest"): + super(KeycloakContainer, self).__init__(image=image) + self.port_to_expose = 8080 + self.with_exposed_ports(self.port_to_expose) + + def _configure(self): + self.with_env("KEYCLOAK_USER", self.KEYCLOAK_USER) + self.with_env("KEYCLOAK_PASSWORD", self.KEYCLOAK_PASSWORD) + + def get_url(self): + host = self.get_container_host_ip() + port = self.get_exposed_port(self.port_to_expose) + return "http://{}:{}".format(host, port) + + @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) + def _connect(self): + url = self.get_url() + response = requests.get("{}/auth".format(url), timeout=1) + response.raise_for_status() + + def start(self): + self._configure() + super().start() + self._connect() + return self + + def get_client(self, **kwargs): + default_kwargs = dict( + server_url="{}/auth/".format(self.get_url()), + username=self.KEYCLOAK_USER, + password=self.KEYCLOAK_PASSWORD, + realm_name="master", + verify=True, + ) + kwargs = { + **default_kwargs, + **kwargs + } + return KeycloakAdmin(**kwargs) diff --git a/tests/test_keycloak.py b/tests/test_keycloak.py new file mode 100644 index 000000000..bc5675380 --- /dev/null +++ b/tests/test_keycloak.py @@ -0,0 +1,8 @@ +import pytest + +from testcontainers.keycloak import KeycloakContainer + +@pytest.mark.parametrize(["version"], [("16.1.1", )]) +def test_docker_run_keycloak(version): + with KeycloakContainer('jboss/keycloak:{}'.format(version)) as kc: + kc.get_client().users_count() From cd4afeae068b0bbb9c575c9e437fa4f87dbe2d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Sat, 16 Apr 2022 21:42:11 +0200 Subject: [PATCH 037/425] flake8 add blank line --- tests/test_keycloak.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_keycloak.py b/tests/test_keycloak.py index bc5675380..8e764130e 100644 --- a/tests/test_keycloak.py +++ b/tests/test_keycloak.py @@ -2,6 +2,7 @@ from testcontainers.keycloak import KeycloakContainer + @pytest.mark.parametrize(["version"], [("16.1.1", )]) def test_docker_run_keycloak(version): with KeycloakContainer('jboss/keycloak:{}'.format(version)) as kc: From 4fba477d3d9b521344b82c6f97fbf2deb4bd8b7f Mon Sep 17 00:00:00 2001 From: Nicolas Damgaard Larsen Date: Wed, 20 Apr 2022 12:10:38 +0200 Subject: [PATCH 038/425] added logging of attempt no. while attempting to connect to container (#207) * added logging of attempt no. while attempting to connect to container * Combine log statement in waiting utils retry. * Split line in waiting_utils for flake8 compliance. Co-authored-by: Till Hoffmann --- testcontainers/core/waiting_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testcontainers/core/waiting_utils.py b/testcontainers/core/waiting_utils.py index 30d7c439b..c147ed959 100644 --- a/testcontainers/core/waiting_utils.py +++ b/testcontainers/core/waiting_utils.py @@ -44,11 +44,12 @@ def wait_container_is_ready(*transient_exceptions): def wrapper(wrapped, instance, args, kwargs): exception = None logger.info("Waiting to be ready...") - for _ in range(config.MAX_TRIES): + for attempt_no in range(config.MAX_TRIES): try: return wrapped(*args, **kwargs) except transient_exceptions as e: - logger.debug('container is not yet ready: %s', traceback.format_exc()) + logger.debug(f"Connection attempt '{attempt_no + 1}' of '{config.MAX_TRIES + 1}' " + f"failed: {traceback.format_exc()}") time.sleep(config.SLEEP_TIME) exception = e raise TimeoutException( From b0ffcc61fd88a900dfbf00d12c20ee7346e72b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Thu, 28 Apr 2022 11:21:28 +0200 Subject: [PATCH 039/425] add docker_client_kw --- testcontainers/core/container.py | 4 ++-- testcontainers/core/docker_client.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 455cb6e9b..4843166c3 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -9,12 +9,12 @@ class DockerContainer(object): - def __init__(self, image, **kwargs): + def __init__(self, image, docker_client_kw: dict = None, **kwargs): self.env = {} self.ports = {} self.volumes = {} self.image = image - self._docker = DockerClient() + self._docker = DockerClient(**(docker_client_kw if docker_client_kw else {})) self._container = None self._command = None self._name = None diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index e65d0f63c..b6513d8fe 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -19,8 +19,8 @@ class DockerClient(object): - def __init__(self): - self.client = docker.from_env() + def __init__(self, **kwargs): + self.client = docker.from_env(**kwargs) def run(self, image: str, command: str = None, From b4ccb972e9edcc2289cce7fb66524d3f4865b7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Thu, 28 Apr 2022 11:36:53 +0200 Subject: [PATCH 040/425] pass kwargs to parent constructor --- testcontainers/elasticsearch.py | 4 ++-- testcontainers/google/pubsub.py | 4 ++-- testcontainers/kafka.py | 4 ++-- testcontainers/localstack.py | 4 ++-- testcontainers/mongodb.py | 4 ++-- testcontainers/mssql.py | 4 ++-- testcontainers/neo4j.py | 2 +- testcontainers/nginx.py | 4 ++-- testcontainers/oracle.py | 4 ++-- testcontainers/postgres.py | 5 +++-- testcontainers/rabbitmq.py | 3 ++- testcontainers/redis.py | 4 ++-- testcontainers/selenium.py | 4 ++-- 13 files changed, 26 insertions(+), 24 deletions(-) diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index df7ee90c6..1e9f6eb1e 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -27,8 +27,8 @@ class ElasticSearchContainer(DockerContainer): with ElasticSearchContainer() as es: connection_url = es.get_url() """ - def __init__(self, image="elasticsearch", port_to_expose=9200): - super(ElasticSearchContainer, self).__init__(image) + def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs): + super(ElasticSearchContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) self.with_env('transport.host', '127.0.0.1') diff --git a/testcontainers/google/pubsub.py b/testcontainers/google/pubsub.py index 2615faf6a..0400e3668 100644 --- a/testcontainers/google/pubsub.py +++ b/testcontainers/google/pubsub.py @@ -34,8 +34,8 @@ def test_docker_run_pubsub(): topic = publisher.create_topic(topic_path) """ def __init__(self, image="google/cloud-sdk:latest", - project="test-project", port=8432): - super(PubSubContainer, self).__init__(image=image) + project="test-project", port=8432, **kwargs): + super(PubSubContainer, self).__init__(image=image, **kwargs) self.project = project self.port = port self.with_exposed_ports(self.port) diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py index c85c28eb3..be5cadc1a 100644 --- a/testcontainers/kafka.py +++ b/testcontainers/kafka.py @@ -14,8 +14,8 @@ class KafkaContainer(DockerContainer): KAFKA_PORT = 9093 TC_START_SCRIPT = '/tc-start.sh' - def __init__(self, image="confluentinc/cp-kafka:5.4.3", port_to_expose=KAFKA_PORT): - super(KafkaContainer, self).__init__(image) + def __init__(self, image="confluentinc/cp-kafka:5.4.3", port_to_expose=KAFKA_PORT, **kwargs): + super(KafkaContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) listeners = 'PLAINTEXT://0.0.0.0:{},BROKER://0.0.0.0:9092'.format(port_to_expose) diff --git a/testcontainers/localstack.py b/testcontainers/localstack.py index af43657bf..1b942f264 100644 --- a/testcontainers/localstack.py +++ b/testcontainers/localstack.py @@ -32,8 +32,8 @@ class LocalStackContainer(DockerContainer): EDGE_PORT = 4566 IMAGE = 'localstack/localstack:0.11.4' - def __init__(self, image=IMAGE): - super(LocalStackContainer, self).__init__(image) + def __init__(self, image=IMAGE, **kwargs): + super(LocalStackContainer, self).__init__(image, **kwargs) self.with_exposed_ports(LocalStackContainer.EDGE_PORT) def with_services(self, *services): diff --git a/testcontainers/mongodb.py b/testcontainers/mongodb.py index af95b954f..aedb3eb81 100644 --- a/testcontainers/mongodb.py +++ b/testcontainers/mongodb.py @@ -50,8 +50,8 @@ class MongoDbContainer(DbContainer): MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") MONGO_DB = os.environ.get("MONGO_DB", "test") - def __init__(self, image="mongo:latest"): - super(MongoDbContainer, self).__init__(image=image) + def __init__(self, image="mongo:latest", **kwargs): + super(MongoDbContainer, self).__init__(image=image, **kwargs) self.command = "mongo" self.port_to_expose = 27017 self.with_exposed_ports(self.port_to_expose) diff --git a/testcontainers/mssql.py b/testcontainers/mssql.py index 7f2e4a10f..f41fa57b4 100644 --- a/testcontainers/mssql.py +++ b/testcontainers/mssql.py @@ -21,8 +21,8 @@ class SqlServerContainer(DbContainer): linux-mac/installing-the-microsoft-odbc-driver-for-sql-server>`_. """ def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA", password=None, - port=1433, dbname="tempdb", dialect='mssql+pymssql'): - super(SqlServerContainer, self).__init__(image) + port=1433, dbname="tempdb", dialect='mssql+pymssql', **kwargs): + super(SqlServerContainer, self).__init__(image, **kwargs) self.SQLSERVER_PASSWORD = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") self.port_to_expose = port diff --git a/testcontainers/neo4j.py b/testcontainers/neo4j.py index b1887de25..67a30b260 100644 --- a/testcontainers/neo4j.py +++ b/testcontainers/neo4j.py @@ -47,7 +47,7 @@ class Neo4jContainer(DbContainer): NEO4J_USER = "neo4j" def __init__(self, image="neo4j:latest", **kwargs): - super(Neo4jContainer, self).__init__(image) + super(Neo4jContainer, self).__init__(image, **kwargs) self.bolt_port = Neo4jContainer.DEFAULT_BOLT_PORT self.with_exposed_ports(self.bolt_port) self._driver = None diff --git a/testcontainers/nginx.py b/testcontainers/nginx.py index c0c3a4e15..39cfe82cb 100644 --- a/testcontainers/nginx.py +++ b/testcontainers/nginx.py @@ -16,7 +16,7 @@ class NginxContainer(DockerContainer): @deprecated(details="Use `DockerContainer` with 'nginx:latest' image and expose port 80.") - def __init__(self, image="nginx:latest", port_to_expose=80): - super(NginxContainer, self).__init__(image) + def __init__(self, image="nginx:latest", port_to_expose=80, **kwargs): + super(NginxContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) diff --git a/testcontainers/oracle.py b/testcontainers/oracle.py index 883c36af7..b8a426445 100644 --- a/testcontainers/oracle.py +++ b/testcontainers/oracle.py @@ -13,8 +13,8 @@ class OracleDbContainer(DbContainer): e = sqlalchemy.create_engine(oracle.get_connection_url()) result = e.execute("select 1 from dual") """ - def __init__(self, image="wnameless/oracle-xe-11g-r2:latest"): - super(OracleDbContainer, self).__init__(image=image) + def __init__(self, image="wnameless/oracle-xe-11g-r2:latest", **kwargs): + super(OracleDbContainer, self).__init__(image=image, **kwargs) self.container_port = 1521 self.with_exposed_ports(self.container_port) self.with_env("ORACLE_ALLOW_REMOTE", "true") diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index 681fd73d4..0081c4a9a 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -37,8 +37,9 @@ def __init__(self, port=5432, user=None, password=None, dbname=None, - driver="psycopg2"): - super(PostgresContainer, self).__init__(image=image) + driver="psycopg2", + **kwargs): + super(PostgresContainer, self).__init__(image=image, **kwargs) self.POSTGRES_USER = user or self.POSTGRES_USER self.POSTGRES_PASSWORD = password or self.POSTGRES_PASSWORD self.POSTGRES_DB = dbname or self.POSTGRES_DB diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py index 9c93858e1..db012fc13 100644 --- a/testcontainers/rabbitmq.py +++ b/testcontainers/rabbitmq.py @@ -34,6 +34,7 @@ def __init__( port: Optional[int] = None, username: Optional[str] = None, password: Optional[str] = None, + **kwargs, ) -> None: """Initialize the RabbitMQ test container. @@ -47,7 +48,7 @@ def __init__( password (str, optional): Overwrite the default username which is "guest". """ - super(RabbitMqContainer, self).__init__(image=image) + super(RabbitMqContainer, self).__init__(image=image, **kwargs) self.RABBITMQ_NODE_PORT = port or int(self.RABBITMQ_NODE_PORT) self.RABBITMQ_DEFAULT_USER = username or self.RABBITMQ_DEFAULT_USER self.RABBITMQ_DEFAULT_PASS = password or self.RABBITMQ_DEFAULT_PASS diff --git a/testcontainers/redis.py b/testcontainers/redis.py index a9d852b76..749dc70e9 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -17,8 +17,8 @@ class RedisContainer(DockerContainer): - def __init__(self, image="redis:latest", port_to_expose=6379, password=None): - super(RedisContainer, self).__init__(image) + def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs): + super(RedisContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.password = password self.with_exposed_ports(self.port_to_expose) diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index e4343e90b..f9b96753e 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -49,12 +49,12 @@ class BrowserWebDriverContainer(DockerContainer): You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ - def __init__(self, capabilities, image=None): + def __init__(self, capabilities, image=None, **kwargs): self.capabilities = capabilities self.image = image or get_image_name(capabilities) self.port_to_expose = 4444 self.vnc_port_to_expose = 5900 - super(BrowserWebDriverContainer, self).__init__(image=self.image) + super(BrowserWebDriverContainer, self).__init__(image=self.image, **kwargs) self.with_exposed_ports(self.port_to_expose, self.vnc_port_to_expose) def _configure(self): From 98398ff330332a55d0c4902caaa69d0a43f0ae75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98rjan=20Solli?= Date: Thu, 5 May 2022 14:47:39 +0200 Subject: [PATCH 041/425] Add support for pg8000 postgres driver (#211) --- requirements.in | 2 ++ requirements/3.6.txt | 39 +++++++++++++++++------------- requirements/3.7.txt | 44 +++++++++++++++++++--------------- requirements/3.8.txt | 42 ++++++++++++++++++-------------- requirements/3.9.txt | 42 ++++++++++++++++++-------------- testcontainers/core/generic.py | 4 ++-- tests/test_db_containers.py | 7 ++++++ 7 files changed, 107 insertions(+), 73 deletions(-) diff --git a/requirements.in b/requirements.in index f6ce29f8f..0bbcbbec1 100644 --- a/requirements.in +++ b/requirements.in @@ -1,6 +1,8 @@ -e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse] codecov>=2.1.0 +cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. +pg8000 pytest pytest-cov sphinx diff --git a/requirements/3.6.txt b/requirements/3.6.txt index a53e7232e..6e319d2f3 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -8,19 +8,21 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx +asn1crypto==1.5.1 + # via scramp async-timeout==4.0.2 # via redis attrs==21.4.0 # via # jsonschema # pytest -babel==2.9.1 +babel==2.10.1 # via sphinx backports.zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==3.2.0 +bcrypt==3.2.2 # via paramiko cached-property==1.5.2 # via docker-compose @@ -44,7 +46,9 @@ coverage[toml]==6.2 # codecov # pytest-cov cryptography==36.0.2 - # via paramiko + # via + # -r requirements.in + # paramiko cx-oracle==8.3.0 # via testcontainers deprecated==1.2.13 @@ -69,9 +73,9 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.3 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -84,13 +88,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.44.0 +grpcio==1.46.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.44.0 +grpcio-status==1.46.0 # via google-api-core idna==3.3 # via requests @@ -118,7 +122,7 @@ markupsafe==2.0.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.2 +neo4j==4.4.3 # via testcontainers packaging==21.3 # via @@ -126,9 +130,11 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.3 +paramiko==2.10.4 # via docker -pika==1.2.0 +pg8000==1.26.0 + # via -r requirements.in +pika==1.2.1 # via testcontainers pluggy==1.0.0 # via pytest @@ -153,11 +159,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.11.2 +pygments==2.12.0 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -195,11 +201,12 @@ requests==2.27.1 # sphinx rsa==4.8 # via google-auth +scramp==1.4.1 + # via pg8000 selenium==3.141.0 # via testcontainers six==1.16.0 # via - # bcrypt # dockerpty # google-auth # grpcio @@ -222,7 +229,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.35 +sqlalchemy==1.4.36 # via testcontainers texttable==1.6.4 # via docker-compose @@ -247,7 +254,7 @@ websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.14.0 +wrapt==1.14.1 # via # deprecated # testcontainers diff --git a/requirements/3.7.txt b/requirements/3.7.txt index d1e282bf4..e800e28e4 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -8,6 +8,8 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx +asn1crypto==1.5.1 + # via scramp async-generator==1.10 # via # trio @@ -20,13 +22,13 @@ attrs==21.4.0 # outcome # pytest # trio -babel==2.9.1 +babel==2.10.1 # via sphinx backports-zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==3.2.0 +bcrypt==3.2.2 # via paramiko cached-property==1.5.2 # via docker-compose @@ -53,6 +55,7 @@ coverage[toml]==6.3.2 # pytest-cov cryptography==36.0.2 # via + # -r requirements.in # paramiko # pyopenssl # urllib3 @@ -80,9 +83,9 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.3 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -95,13 +98,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.44.0 +grpcio==1.46.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.44.0 +grpcio-status==1.46.0 # via google-api-core h11==0.13.0 # via wsproto @@ -122,7 +125,7 @@ importlib-metadata==4.11.3 # sqlalchemy iniconfig==1.1.1 # via pytest -jinja2==3.1.1 +jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose @@ -132,7 +135,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.2 +neo4j==4.4.3 # via testcontainers outcome==1.1.0 # via trio @@ -142,13 +145,15 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.3 +paramiko==2.10.4 # via docker -pika==1.2.0 +pg8000==1.26.1 + # via -r requirements.in +pika==1.2.1 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.0 +protobuf==3.20.1 # via # google-api-core # googleapis-common-protos @@ -169,11 +174,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.11.2 +pygments==2.12.0 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -187,7 +192,7 @@ pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.1 +pytest==7.1.2 # via # -r requirements.in # pytest-cov @@ -215,11 +220,12 @@ requests==2.27.1 # sphinx rsa==4.8 # via google-auth +scramp==1.4.1 + # via pg8000 selenium==4.1.3 # via testcontainers six==1.16.0 # via - # bcrypt # dockerpty # google-auth # grpcio @@ -246,7 +252,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.35 +sqlalchemy==1.4.36 # via testcontainers texttable==1.6.4 # via docker-compose @@ -260,7 +266,7 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.1.1 +typing-extensions==4.2.0 # via # async-timeout # h11 @@ -278,7 +284,7 @@ websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.14.0 +wrapt==1.14.1 # via # deprecated # testcontainers diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 9e51e43de..dd961d597 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -8,6 +8,8 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx +asn1crypto==1.5.1 + # via scramp async-generator==1.10 # via # trio @@ -20,13 +22,13 @@ attrs==21.4.0 # outcome # pytest # trio -babel==2.9.1 +babel==2.10.1 # via sphinx backports-zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==3.2.0 +bcrypt==3.2.2 # via paramiko cachetools==5.0.0 # via google-auth @@ -51,6 +53,7 @@ coverage[toml]==6.3.2 # pytest-cov cryptography==36.0.2 # via + # -r requirements.in # paramiko # pyopenssl # urllib3 @@ -78,9 +81,9 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.3 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -93,13 +96,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.44.0 +grpcio==1.46.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.44.0 +grpcio-status==1.46.0 # via google-api-core h11==0.13.0 # via wsproto @@ -114,7 +117,7 @@ importlib-metadata==4.11.3 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.1.1 +jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose @@ -124,7 +127,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.2 +neo4j==4.4.3 # via testcontainers outcome==1.1.0 # via trio @@ -134,13 +137,15 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.3 +paramiko==2.10.4 # via docker -pika==1.2.0 +pg8000==1.26.1 + # via -r requirements.in +pika==1.2.1 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.0 +protobuf==3.20.1 # via # google-api-core # googleapis-common-protos @@ -161,11 +166,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.11.2 +pygments==2.12.0 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -179,7 +184,7 @@ pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.1 +pytest==7.1.2 # via # -r requirements.in # pytest-cov @@ -207,11 +212,12 @@ requests==2.27.1 # sphinx rsa==4.8 # via google-auth +scramp==1.4.1 + # via pg8000 selenium==4.1.3 # via testcontainers six==1.16.0 # via - # bcrypt # dockerpty # google-auth # grpcio @@ -238,7 +244,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.35 +sqlalchemy==1.4.36 # via testcontainers texttable==1.6.4 # via docker-compose @@ -264,7 +270,7 @@ websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.14.0 +wrapt==1.14.1 # via # deprecated # testcontainers diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 6713257e0..19daea6d2 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -8,6 +8,8 @@ # via -r requirements.in alabaster==0.7.12 # via sphinx +asn1crypto==1.5.1 + # via scramp async-generator==1.10 # via # trio @@ -20,9 +22,9 @@ attrs==21.4.0 # outcome # pytest # trio -babel==2.9.1 +babel==2.10.1 # via sphinx -bcrypt==3.2.0 +bcrypt==3.2.2 # via paramiko cachetools==5.0.0 # via google-auth @@ -47,6 +49,7 @@ coverage[toml]==6.3.2 # pytest-cov cryptography==36.0.2 # via + # -r requirements.in # paramiko # pyopenssl # urllib3 @@ -74,9 +77,9 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.1 +google-api-core[grpc]==2.7.3 # via google-cloud-pubsub -google-auth==2.6.3 +google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers @@ -89,13 +92,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.44.0 +grpcio==1.46.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.44.0 +grpcio-status==1.46.0 # via google-api-core h11==0.13.0 # via wsproto @@ -110,7 +113,7 @@ importlib-metadata==4.11.3 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.1.1 +jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose @@ -120,7 +123,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.2 +neo4j==4.4.3 # via testcontainers outcome==1.1.0 # via trio @@ -130,13 +133,15 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.3 +paramiko==2.10.4 # via docker -pika==1.2.0 +pg8000==1.26.1 + # via -r requirements.in +pika==1.2.1 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.0 +protobuf==3.20.1 # via # google-api-core # googleapis-common-protos @@ -157,11 +162,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.11.2 +pygments==2.12.0 # via sphinx -pymongo==4.1.0 +pymongo==4.1.1 # via testcontainers -pymssql==2.2.4 +pymssql==2.2.5 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -175,7 +180,7 @@ pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.1 +pytest==7.1.2 # via # -r requirements.in # pytest-cov @@ -203,11 +208,12 @@ requests==2.27.1 # sphinx rsa==4.8 # via google-auth +scramp==1.4.1 + # via pg8000 selenium==4.1.3 # via testcontainers six==1.16.0 # via - # bcrypt # dockerpty # google-auth # grpcio @@ -234,7 +240,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.35 +sqlalchemy==1.4.36 # via testcontainers texttable==1.6.4 # via docker-compose @@ -260,7 +266,7 @@ websocket-client==0.59.0 # via # docker # docker-compose -wrapt==1.14.0 +wrapt==1.14.1 # via # deprecated # testcontainers diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 9daec350e..65c02f5fa 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -16,8 +16,8 @@ from deprecation import deprecated ADDITIONAL_TRANSIENT_ERRORS = [] try: - from sqlalchemy.exc import OperationalError - ADDITIONAL_TRANSIENT_ERRORS.append(OperationalError) + from sqlalchemy.exc import DBAPIError + ADDITIONAL_TRANSIENT_ERRORS.append(DBAPIError) except ImportError: pass diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index 1fb001500..b870ecae8 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -35,6 +35,13 @@ def test_docker_run_postgres(): print("server version:", row[0]) +def test_docker_run_postgres_with_driver_pg8000(): + postgres_container = PostgresContainer("postgres:9.5", driver="pg8000") + with postgres_container as postgres: + e = sqlalchemy.create_engine(postgres.get_connection_url()) + e.execute("select 1=1") + + @pytest.mark.skip(reason='test does not verify additional code over `test_docker_run_postgres`') def test_docker_run_greenplum(): container = PostgresContainer("datagrip/greenplum:6.8", user="guest", password="guest", From 59ba1c572739ee94875e1e845cfcb33905d5be85 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 May 2022 11:31:02 -0400 Subject: [PATCH 042/425] Separate tests into different components for easier debugging. (#212) * Restructure tests. * Update python version (closes #188). * Separate tests using GitHub Action matrix. * Add information on contributing a new container. * Fix docker compose path. * Finish running test matrix even if one element fails. * Clean up print statements in tests. * Fix docker compose tests; remove mysql dependency. --- .github/workflows/main.yml | 23 +- Makefile | 2 +- README.rst | 9 + requirements/3.10.txt | 275 ++++++++++++++++++ testcontainers/compose.py | 5 +- tests/.env.test | 2 - tests/Dockerfile | 4 - tests/docker-compose-2.yml | 6 - tests/docker-compose-3.yml | 7 - tests/docker-compose-4.yml | 2 - tests/docker-compose.yml | 16 - tests/test_clickhouse.py | 11 + tests/test_core/.env.test | 1 + tests/test_core/Dockerfile | 3 + tests/test_core/__init__.py | 0 tests/test_core/docker-compose-2.yml | 6 + tests/test_core/docker-compose-3.yml | 8 + tests/test_core/docker-compose-4.yml | 3 + tests/test_core/docker-compose.yml | 17 ++ tests/{ => test_core}/test_core.py | 0 tests/test_core/test_db_containers.py | 70 +++++ tests/{ => test_core}/test_docker_compose.py | 35 +-- tests/{ => test_core}/test_new_docker_api.py | 0 tests/test_db_containers.py | 171 ----------- tests/test_mongodb.py | 62 ++++ tests/test_neo4j.py | 16 + ...{test_nginx_container.py => test_nginx.py} | 0 ...bdriver_container.py => test_webdriver.py} | 0 28 files changed, 523 insertions(+), 231 deletions(-) create mode 100644 requirements/3.10.txt delete mode 100644 tests/.env.test delete mode 100644 tests/Dockerfile delete mode 100644 tests/docker-compose-2.yml delete mode 100644 tests/docker-compose-3.yml delete mode 100644 tests/docker-compose-4.yml delete mode 100644 tests/docker-compose.yml create mode 100644 tests/test_clickhouse.py create mode 100644 tests/test_core/.env.test create mode 100644 tests/test_core/Dockerfile create mode 100644 tests/test_core/__init__.py create mode 100644 tests/test_core/docker-compose-2.yml create mode 100644 tests/test_core/docker-compose-3.yml create mode 100644 tests/test_core/docker-compose-4.yml create mode 100644 tests/test_core/docker-compose.yml rename tests/{ => test_core}/test_core.py (100%) create mode 100644 tests/test_core/test_db_containers.py rename tests/{ => test_core}/test_docker_compose.py (71%) rename tests/{ => test_core}/test_new_docker_api.py (100%) delete mode 100644 tests/test_db_containers.py create mode 100644 tests/test_mongodb.py create mode 100644 tests/test_neo4j.py rename tests/{test_nginx_container.py => test_nginx.py} (100%) rename tests/{test_webdriver_container.py => test_webdriver.py} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aff59631b..684915fdf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,27 @@ on: jobs: build: strategy: + fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8] + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + test-component: + - core + - clickhouse.py + - elasticsearch.py + - google.py + - kafka.py + - localstack.py + - mongodb.py + - neo4j.py + - nginx.py + - rabbitmq.py + - redis.py + - selenium.py + - webdriver.py runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 @@ -44,5 +63,5 @@ jobs: run: | flake8 sphinx-build -nW docs docs/_build/html - py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/ + py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/test_${{ matrix.test-component }} codecov diff --git a/Makefile b/Makefile index 032dc90c0..1309ebe2b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PYTHON_VERSIONS = 3.6 3.7 3.8 3.9 +PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) TESTS = $(addprefix tests/,${PYTHON_VERSIONS}) IMAGES = $(addprefix image/,${PYTHON_VERSIONS}) diff --git a/README.rst b/README.rst index 9490bb92f..7dda7737d 100644 --- a/README.rst +++ b/README.rst @@ -78,3 +78,12 @@ Adding requirements ^^^^^^^^^^^^^^^^^^^ We use :code:`pip-tools` to resolve and manage dependencies. If you need to add a dependency to testcontainers or one of the extras, modify the :code:`setup.py` as well as the :code:`requirements.in` accordingly and then run :code:`pip install pip-tools` followed by :code:`make requirements` to update the requirements files. + +Contributing a new container +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can contribute a new container in three steps: + +1. Create a new module at :code:`testcontainers/[my fancy container].py` that implements the new functionality. +2. Create a new test module at :code:`tests/test_[my fancy container].py` that tests the new functionality. +3. Add :code:`[my fancy container]` to the list of test components in the GitHub Action configuration at :code:`.github/workflows/main.yml`. diff --git a/requirements/3.10.txt b/requirements/3.10.txt new file mode 100644 index 000000000..a5f5c4eb7 --- /dev/null +++ b/requirements/3.10.txt @@ -0,0 +1,275 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --output-file=requirements/3.10.txt requirements.in +# +-e file:. + # via -r requirements.in +alabaster==0.7.12 + # via sphinx +asn1crypto==1.5.1 + # via scramp +async-generator==1.10 + # via + # trio + # trio-websocket +async-timeout==4.0.2 + # via redis +attrs==21.4.0 + # via + # jsonschema + # outcome + # pytest + # trio +babel==2.10.1 + # via sphinx +bcrypt==3.2.2 + # via paramiko +cachetools==5.0.0 + # via google-auth +certifi==2021.10.8 + # via + # requests + # urllib3 +cffi==1.15.0 + # via + # bcrypt + # cryptography + # pynacl +charset-normalizer==2.0.12 + # via requests +clickhouse-driver==0.2.3 + # via testcontainers +codecov==2.1.12 + # via -r requirements.in +coverage[toml]==6.3.2 + # via + # codecov + # pytest-cov +cryptography==36.0.2 + # via + # -r requirements.in + # paramiko + # pyopenssl + # urllib3 +cx-oracle==8.3.0 + # via testcontainers +deprecated==1.2.13 + # via redis +deprecation==2.1.0 + # via testcontainers +distro==1.7.0 + # via docker-compose +docker[ssh]==5.0.3 + # via + # docker-compose + # testcontainers +docker-compose==1.29.2 + # via testcontainers +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.17.1 + # via sphinx +entrypoints==0.3 + # via flake8 +flake8==3.7.9 + # via -r requirements.in +google-api-core[grpc]==2.7.3 + # via google-cloud-pubsub +google-auth==2.6.6 + # via google-api-core +google-cloud-pubsub==1.7.1 + # via testcontainers +googleapis-common-protos[grpc]==1.56.0 + # via + # google-api-core + # grpc-google-iam-v1 + # grpcio-status +greenlet==1.1.2 + # via sqlalchemy +grpc-google-iam-v1==0.12.4 + # via google-cloud-pubsub +grpcio==1.46.0 + # via + # google-api-core + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +grpcio-status==1.46.0 + # via google-api-core +h11==0.13.0 + # via wsproto +idna==3.3 + # via + # requests + # trio + # urllib3 +imagesize==1.3.0 + # via sphinx +iniconfig==1.1.1 + # via pytest +jinja2==3.1.2 + # via sphinx +jsonschema==3.2.0 + # via docker-compose +kafka-python==2.0.2 + # via testcontainers +markupsafe==2.1.1 + # via jinja2 +mccabe==0.6.1 + # via flake8 +neo4j==4.4.3 + # via testcontainers +outcome==1.1.0 + # via trio +packaging==21.3 + # via + # deprecation + # pytest + # redis + # sphinx +paramiko==2.10.4 + # via docker +pg8000==1.26.1 + # via -r requirements.in +pika==1.2.1 + # via testcontainers +pluggy==1.0.0 + # via pytest +protobuf==3.20.1 + # via + # google-api-core + # googleapis-common-protos + # grpcio-status +psycopg2-binary==2.9.3 + # via testcontainers +py==1.11.0 + # via pytest +pyasn1==0.4.8 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.2.8 + # via google-auth +pycodestyle==2.5.0 + # via flake8 +pycparser==2.21 + # via cffi +pyflakes==2.1.1 + # via flake8 +pygments==2.12.0 + # via sphinx +pymongo==4.1.1 + # via testcontainers +pymssql==2.2.5 + # via testcontainers +pymysql==1.0.2 + # via testcontainers +pynacl==1.5.0 + # via paramiko +pyopenssl==22.0.0 + # via urllib3 +pyparsing==3.0.8 + # via packaging +pyrsistent==0.18.1 + # via jsonschema +pysocks==1.7.1 + # via urllib3 +pytest==7.1.2 + # via + # -r requirements.in + # pytest-cov +pytest-cov==3.0.0 + # via -r requirements.in +python-dotenv==0.20.0 + # via docker-compose +pytz==2022.1 + # via + # babel + # clickhouse-driver + # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal +pyyaml==5.4.1 + # via docker-compose +redis==4.2.2 + # via testcontainers +requests==2.27.1 + # via + # codecov + # docker + # docker-compose + # google-api-core + # sphinx +rsa==4.8 + # via google-auth +scramp==1.4.1 + # via pg8000 +selenium==4.1.4 + # via testcontainers +six==1.16.0 + # via + # dockerpty + # google-auth + # grpcio + # jsonschema + # paramiko + # websocket-client +sniffio==1.2.0 + # via trio +snowballstemmer==2.2.0 + # via sphinx +sortedcontainers==2.4.0 + # via trio +sphinx==4.5.0 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sqlalchemy==1.4.36 + # via testcontainers +texttable==1.6.4 + # via docker-compose +tomli==2.0.1 + # via + # coverage + # pytest +trio==0.20.0 + # via + # selenium + # trio-websocket +trio-websocket==0.9.2 + # via selenium +tzdata==2022.1 + # via pytz-deprecation-shim +tzlocal==4.2 + # via clickhouse-driver +urllib3[secure,socks]==1.26.9 + # via + # requests + # selenium +websocket-client==0.59.0 + # via + # docker + # docker-compose +wrapt==1.14.1 + # via + # deprecated + # testcontainers +wsproto==1.1.0 + # via trio-websocket + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 1896b6f73..1cac63af8 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -209,9 +209,8 @@ def _get_service_info(self, service, port): port_cmd = self.docker_compose_command() + ["port", service, str(port)] output = subprocess.check_output(port_cmd, cwd=self.filepath).decode("utf-8") result = str(output).rstrip().split(":") - if len(result) == 1: - raise NoSuchPortExposed("Port {} was not exposed for service {}" - .format(port, service)) + if len(result) != 2 or not all(result): + raise NoSuchPortExposed(f"port {port} is not exposed for service {service}") return result def _call_command(self, cmd, filepath=None): diff --git a/tests/.env.test b/tests/.env.test deleted file mode 100644 index 23e151152..000000000 --- a/tests/.env.test +++ /dev/null @@ -1,2 +0,0 @@ -TAG_MYSQL_ALLOW_EMPTY_PASSWORD="true" -TAG_TEST_ASSERT_KEY="test_is_passed" \ No newline at end of file diff --git a/tests/Dockerfile b/tests/Dockerfile deleted file mode 100644 index e68d411b4..000000000 --- a/tests/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM busybox:buildroot-2014.02 -MAINTAINER first last, first.last@yourdomain.com -VOLUME /data -CMD ["/bin/sh"] \ No newline at end of file diff --git a/tests/docker-compose-2.yml b/tests/docker-compose-2.yml deleted file mode 100644 index 434b856c3..000000000 --- a/tests/docker-compose-2.yml +++ /dev/null @@ -1,6 +0,0 @@ -mysql: - image: mysql - ports: - - "3306:3306" - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: "true" diff --git a/tests/docker-compose-3.yml b/tests/docker-compose-3.yml deleted file mode 100644 index 35448cb7e..000000000 --- a/tests/docker-compose-3.yml +++ /dev/null @@ -1,7 +0,0 @@ -mysql: - image: mysql - ports: - - "3306:3306" - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: ${TAG_MYSQL_ALLOW_EMPTY_PASSWORD} - TEST_ASSERT_KEY: ${TAG_TEST_ASSERT_KEY} \ No newline at end of file diff --git a/tests/docker-compose-4.yml b/tests/docker-compose-4.yml deleted file mode 100644 index 9966c6549..000000000 --- a/tests/docker-compose-4.yml +++ /dev/null @@ -1,2 +0,0 @@ -hello-world: - image: "hello-world" diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml deleted file mode 100644 index 75418f34c..000000000 --- a/tests/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -hub: - image: selenium/hub - ports: - - "4444:4444" -firefox: - image: selenium/node-firefox - links: - - hub - expose: - - "5555" -chrome: - image: selenium/node-chrome - links: - - hub - expose: - - "5555" \ No newline at end of file diff --git a/tests/test_clickhouse.py b/tests/test_clickhouse.py new file mode 100644 index 000000000..32ac046f7 --- /dev/null +++ b/tests/test_clickhouse.py @@ -0,0 +1,11 @@ +import clickhouse_driver +from testcontainers.clickhouse import ClickHouseContainer + + +def test_docker_run_clickhouse(): + clickhouse_container = ClickHouseContainer() + with clickhouse_container as clickhouse: + client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) + result = client.execute("select 'working'") + + assert result == [('working',)] diff --git a/tests/test_core/.env.test b/tests/test_core/.env.test new file mode 100644 index 000000000..84b2baafd --- /dev/null +++ b/tests/test_core/.env.test @@ -0,0 +1 @@ +TAG_TEST_ASSERT_KEY="test_has_passed" diff --git a/tests/test_core/Dockerfile b/tests/test_core/Dockerfile new file mode 100644 index 000000000..a63149468 --- /dev/null +++ b/tests/test_core/Dockerfile @@ -0,0 +1,3 @@ +FROM busybox:buildroot-2014.02 +VOLUME /data +CMD ["/bin/sh"] diff --git a/tests/test_core/__init__.py b/tests/test_core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_core/docker-compose-2.yml b/tests/test_core/docker-compose-2.yml new file mode 100644 index 000000000..e360bb010 --- /dev/null +++ b/tests/test_core/docker-compose-2.yml @@ -0,0 +1,6 @@ +services: + alpine: + image: alpine + command: sleep 3600 + ports: + - "3306:3306" diff --git a/tests/test_core/docker-compose-3.yml b/tests/test_core/docker-compose-3.yml new file mode 100644 index 000000000..874126c7a --- /dev/null +++ b/tests/test_core/docker-compose-3.yml @@ -0,0 +1,8 @@ +services: + alpine: + image: alpine + command: sleep 3600 + ports: + - "3306:3306" + environment: + TEST_ASSERT_KEY: ${TAG_TEST_ASSERT_KEY} diff --git a/tests/test_core/docker-compose-4.yml b/tests/test_core/docker-compose-4.yml new file mode 100644 index 000000000..081e598d1 --- /dev/null +++ b/tests/test_core/docker-compose-4.yml @@ -0,0 +1,3 @@ +services: + hello-world: + image: "hello-world" diff --git a/tests/test_core/docker-compose.yml b/tests/test_core/docker-compose.yml new file mode 100644 index 000000000..6c12ae335 --- /dev/null +++ b/tests/test_core/docker-compose.yml @@ -0,0 +1,17 @@ +services: + hub: + image: selenium/hub + ports: + - "4444:4444" + firefox: + image: selenium/node-firefox + links: + - hub + expose: + - "5555" + chrome: + image: selenium/node-chrome + links: + - hub + expose: + - "5555" diff --git a/tests/test_core.py b/tests/test_core/test_core.py similarity index 100% rename from tests/test_core.py rename to tests/test_core/test_core.py diff --git a/tests/test_core/test_db_containers.py b/tests/test_core/test_db_containers.py new file mode 100644 index 000000000..0aa6640a7 --- /dev/null +++ b/tests/test_core/test_db_containers.py @@ -0,0 +1,70 @@ +import sqlalchemy +import pytest +from testcontainers.core.utils import is_arm +from testcontainers.mssql import SqlServerContainer +from testcontainers.mysql import MySqlContainer +from testcontainers.oracle import OracleDbContainer +from testcontainers.postgres import PostgresContainer + + +@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') +def test_docker_run_mysql(): + config = MySqlContainer('mysql:5.7.17') + with config as mysql: + e = sqlalchemy.create_engine(mysql.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0].startswith('5.7.17') + + +def test_docker_run_postgres(): + postgres_container = PostgresContainer("postgres:9.5") + with postgres_container as postgres: + e = sqlalchemy.create_engine(postgres.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0].lower().startswith("postgresql 9.5") + + +def test_docker_run_postgres_with_driver_pg8000(): + postgres_container = PostgresContainer("postgres:9.5", driver="pg8000") + with postgres_container as postgres: + e = sqlalchemy.create_engine(postgres.get_connection_url()) + e.execute("select 1=1") + + +def test_docker_run_mariadb(): + with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: + e = sqlalchemy.create_engine(mariadb.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0].startswith('10.6.5') + + +@pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") +def test_docker_run_oracle(): + with OracleDbContainer() as oracledb: + e = sqlalchemy.create_engine(oracledb.get_connection_url()) + result = e.execute("select * from V$VERSION") + versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', + 'PL/SQL Release 11.2.0.2.0 - Production', + 'CORE\t11.2.0.2.0\tProduction', + 'TNS for Linux: Version 11.2.0.2.0 - Production', + 'NLSRTL Version 11.2.0.2.0 - Production'} + assert {row[0] for row in result} == versions + + +def test_docker_run_mssql(): + image = 'mcr.microsoft.com/azure-sql-edge' + dialect = 'mssql+pymssql' + with SqlServerContainer(image, dialect=dialect) as mssql: + e = sqlalchemy.create_engine(mssql.get_connection_url()) + result = e.execute('select @@servicename') + for row in result: + assert row[0] == 'MSSQLSERVER' + + with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: + e = sqlalchemy.create_engine(mssql.get_connection_url()) + result = e.execute('select @@servicename') + for row in result: + assert row[0] == 'MSSQLSERVER' diff --git a/tests/test_docker_compose.py b/tests/test_core/test_docker_compose.py similarity index 71% rename from tests/test_docker_compose.py rename to tests/test_core/test_docker_compose.py index 8739f572f..ce8ec02ce 100644 --- a/tests/test_docker_compose.py +++ b/tests/test_core/test_docker_compose.py @@ -1,7 +1,6 @@ from unittest.mock import patch import pytest -import subprocess from testcontainers.compose import DockerCompose from testcontainers.core.docker_client import DockerClient @@ -9,8 +8,11 @@ from testcontainers.core.waiting_utils import wait_for_logs +ROOT = "tests/test_core" + + def test_can_spawn_service_via_compose(): - with DockerCompose('tests') as compose: + with DockerCompose(ROOT) as compose: host = compose.get_service_host("hub", 4444) port = compose.get_service_port("hub", 4444) assert host == "0.0.0.0" @@ -18,7 +20,7 @@ def test_can_spawn_service_via_compose(): def test_can_pull_images_before_spawning_service_via_compose(): - with DockerCompose("tests", pull=True) as compose: + with DockerCompose(ROOT, pull=True) as compose: host = compose.get_service_host("hub", 4444) port = compose.get_service_port("hub", 4444) assert host == "0.0.0.0" @@ -27,7 +29,7 @@ def test_can_pull_images_before_spawning_service_via_compose(): def test_can_build_images_before_spawning_service_via_compose(): with patch.object(DockerCompose, "_call_command") as call_mock: - with DockerCompose("tests", build=True) as compose: + with DockerCompose(ROOT, build=True) as compose: ... assert compose.build @@ -38,27 +40,27 @@ def test_can_build_images_before_spawning_service_via_compose(): def test_can_throw_exception_if_no_port_exposed(): - with DockerCompose("tests") as compose: + with DockerCompose(ROOT) as compose: with pytest.raises(NoSuchPortExposed): compose.get_service_host("hub", 5555) def test_compose_wait_for_container_ready(): - with DockerCompose("tests") as compose: + with DockerCompose(ROOT) as compose: docker = DockerClient() compose.wait_for("http://%s:4444/wd/hub" % docker.host()) def test_compose_can_wait_for_logs(): - with DockerCompose(filepath="tests", compose_file_name="docker-compose-4.yml") as compose: + with DockerCompose(filepath=ROOT, compose_file_name="docker-compose-4.yml") as compose: wait_for_logs(compose, "Hello from Docker!") def test_can_parse_multiple_compose_files(): - with DockerCompose(filepath="tests", + with DockerCompose(filepath=ROOT, compose_file_name=["docker-compose.yml", "docker-compose-2.yml"]) as compose: - host = compose.get_service_host("mysql", 3306) - port = compose.get_service_port("mysql", 3306) + host = compose.get_service_host("alpine", 3306) + port = compose.get_service_port("alpine", 3306) assert host == "0.0.0.0" assert port == "3306" @@ -69,7 +71,7 @@ def test_can_parse_multiple_compose_files(): def test_can_get_logs(): - with DockerCompose("tests") as compose: + with DockerCompose(ROOT) as compose: docker = DockerClient() compose.wait_for("http://%s:4444/wd/hub" % docker.host()) stdout, stderr = compose.get_logs() @@ -77,15 +79,14 @@ def test_can_get_logs(): def test_can_pass_env_params_by_env_file(): - with DockerCompose('tests', compose_file_name='docker-compose-3.yml', - env_file='.env.test') as _: - check_env_is_set_cmd = 'docker exec tests_mysql_1 printenv | grep TEST_ASSERT_KEY'.split() - out = subprocess.run(check_env_is_set_cmd, stdout=subprocess.PIPE) - assert out.stdout.decode('utf-8').splitlines()[0], 'test_is_passed' + with DockerCompose(ROOT, compose_file_name='docker-compose-3.yml', + env_file='.env.test') as compose: + stdout, *_ = compose.exec_in_container("alpine", ["printenv"]) + assert stdout.splitlines()[0], 'test_has_passed' def test_can_exec_commands(): - with DockerCompose("tests") as compose: + with DockerCompose(ROOT) as compose: result = compose.exec_in_container('hub', ['echo', 'my_test']) assert result[0] == 'my_test\n', "The echo should be successful" assert result[1] == '', "stderr should be empty" diff --git a/tests/test_new_docker_api.py b/tests/test_core/test_new_docker_api.py similarity index 100% rename from tests/test_new_docker_api.py rename to tests/test_core/test_new_docker_api.py diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py deleted file mode 100644 index b870ecae8..000000000 --- a/tests/test_db_containers.py +++ /dev/null @@ -1,171 +0,0 @@ -import sqlalchemy -import clickhouse_driver -from pymongo import MongoClient -from pymongo.errors import OperationFailure -import pytest - -from testcontainers.core.utils import is_arm -from testcontainers.clickhouse import ClickHouseContainer -from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for -from testcontainers.mongodb import MongoDbContainer -from testcontainers.mssql import SqlServerContainer -from testcontainers.mysql import MySqlContainer -from testcontainers.neo4j import Neo4jContainer -from testcontainers.oracle import OracleDbContainer -from testcontainers.postgres import PostgresContainer - - -@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') -def test_docker_run_mysql(): - config = MySqlContainer('mysql:5.7.17') - with config as mysql: - e = sqlalchemy.create_engine(mysql.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].startswith('5.7.17') - - -def test_docker_run_postgres(): - postgres_container = PostgresContainer("postgres:9.5") - with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - result = e.execute("select version()") - for row in result: - print("server version:", row[0]) - - -def test_docker_run_postgres_with_driver_pg8000(): - postgres_container = PostgresContainer("postgres:9.5", driver="pg8000") - with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - e.execute("select 1=1") - - -@pytest.mark.skip(reason='test does not verify additional code over `test_docker_run_postgres`') -def test_docker_run_greenplum(): - container = PostgresContainer("datagrip/greenplum:6.8", user="guest", password="guest", - dbname="guest") - with container: - e = sqlalchemy.create_engine(container.get_connection_url()) - result = e.execute("select version()") - for row in result: - print("server version:", row[0]) - - -def test_docker_run_mariadb(): - with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: - e = sqlalchemy.create_engine(mariadb.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].startswith('10.6.5') - - -@pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") -def test_docker_run_oracle(): - with OracleDbContainer() as oracledb: - e = sqlalchemy.create_engine(oracledb.get_connection_url()) - result = e.execute("select * from V$VERSION") - versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', - 'PL/SQL Release 11.2.0.2.0 - Production', - 'CORE\t11.2.0.2.0\tProduction', - 'TNS for Linux: Version 11.2.0.2.0 - Production', - 'NLSRTL Version 11.2.0.2.0 - Production'} - assert {row[0] for row in result} == versions - - -def test_docker_run_mongodb(): - with MongoDbContainer("mongo:latest") as mongo: - db = mongo.get_connection_client().test - doc = { - "address": { - "street": "2 Avenue", - "zipcode": "10075", - "building": "1480", - "coord": [-73.9557413, 40.7720266] - }, - "borough": "Manhattan", - "cuisine": "Italian", - "name": "Vella", - "restaurant_id": "41704620" - } - db.restaurants.insert_one(doc) - cursor = db.restaurants.find({"borough": "Manhattan"}) - assert cursor.next()['restaurant_id'] == doc['restaurant_id'] - - -def test_docker_run_mongodb_connect_without_credentials(): - with MongoDbContainer() as mongo: - connection_url = "mongodb://{}:{}".format(mongo.get_container_host_ip(), - mongo.get_exposed_port(mongo.port_to_expose)) - db = MongoClient(connection_url).test - with pytest.raises(OperationFailure): - db.restaurants.insert_one({}) - - -def test_docker_run_neo4j_latest(): - with Neo4jContainer() as neo4j: - with neo4j.get_driver() as driver: - with driver.session() as session: - result = session.run( - """ - CALL dbms.components() - YIELD name, versions, edition - UNWIND versions as version - RETURN name, version, edition - """) - record = result.single() - print("server version:", record["name"], record["version"], record["edition"]) - assert record["name"].startswith("Neo4j") - - -def test_docker_generic_db(): - with DockerContainer("mongo:latest").with_bind_ports(27017, 27017) as mongo_container: - def connect(): - return MongoClient("mongodb://{}:{}".format(mongo_container.get_container_host_ip(), - mongo_container.get_exposed_port(27017))) - - db = wait_for(connect).primer - result = db.restaurants.insert_one( - { - "address": { - "street": "2 Avenue", - "zipcode": "10075", - "building": "1480", - "coord": [-73.9557413, 40.7720266] - }, - "borough": "Manhattan", - "cuisine": "Italian", - "name": "Vella", - "restaurant_id": "41704620" - } - ) - print(result.inserted_id) - cursor = db.restaurants.find({"borough": "Manhattan"}) - for document in cursor: - print(document) - - -def test_docker_run_mssql(): - image = 'mcr.microsoft.com/azure-sql-edge' - dialect = 'mssql+pymssql' - with SqlServerContainer(image, dialect=dialect) as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute('select @@servicename') - for row in result: - assert row[0] == 'MSSQLSERVER' - - with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute('select @@servicename') - for row in result: - assert row[0] == 'MSSQLSERVER' - - -def test_docker_run_clickhouse(): - clickhouse_container = ClickHouseContainer() - with clickhouse_container as clickhouse: - client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) - result = client.execute("select 'working'") - - assert result == [('working',)] diff --git a/tests/test_mongodb.py b/tests/test_mongodb.py new file mode 100644 index 000000000..a1e03e660 --- /dev/null +++ b/tests/test_mongodb.py @@ -0,0 +1,62 @@ +from pymongo import MongoClient +from pymongo.errors import OperationFailure +import pytest +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for +from testcontainers.mongodb import MongoDbContainer + + +def test_docker_generic_db(): + with DockerContainer("mongo:latest").with_bind_ports(27017, 27017) as mongo_container: + def connect(): + return MongoClient("mongodb://{}:{}".format(mongo_container.get_container_host_ip(), + mongo_container.get_exposed_port(27017))) + + db = wait_for(connect).primer + result = db.restaurants.insert_one( + { + "address": { + "street": "2 Avenue", + "zipcode": "10075", + "building": "1480", + "coord": [-73.9557413, 40.7720266] + }, + "borough": "Manhattan", + "cuisine": "Italian", + "name": "Vella", + "restaurant_id": "41704620" + } + ) + assert result.inserted_id + cursor = db.restaurants.find({"borough": "Manhattan"}) + for document in cursor: + assert document + + +def test_docker_run_mongodb(): + with MongoDbContainer("mongo:latest") as mongo: + db = mongo.get_connection_client().test + doc = { + "address": { + "street": "2 Avenue", + "zipcode": "10075", + "building": "1480", + "coord": [-73.9557413, 40.7720266] + }, + "borough": "Manhattan", + "cuisine": "Italian", + "name": "Vella", + "restaurant_id": "41704620" + } + db.restaurants.insert_one(doc) + cursor = db.restaurants.find({"borough": "Manhattan"}) + assert cursor.next()['restaurant_id'] == doc['restaurant_id'] + + +def test_docker_run_mongodb_connect_without_credentials(): + with MongoDbContainer() as mongo: + connection_url = "mongodb://{}:{}".format(mongo.get_container_host_ip(), + mongo.get_exposed_port(mongo.port_to_expose)) + db = MongoClient(connection_url).test + with pytest.raises(OperationFailure): + db.restaurants.insert_one({}) diff --git a/tests/test_neo4j.py b/tests/test_neo4j.py new file mode 100644 index 000000000..8c90ca0e9 --- /dev/null +++ b/tests/test_neo4j.py @@ -0,0 +1,16 @@ +from testcontainers.neo4j import Neo4jContainer + + +def test_docker_run_neo4j_latest(): + with Neo4jContainer() as neo4j: + with neo4j.get_driver() as driver: + with driver.session() as session: + result = session.run( + """ + CALL dbms.components() + YIELD name, versions, edition + UNWIND versions as version + RETURN name, version, edition + """) + record = result.single() + assert record["name"].startswith("Neo4j") diff --git a/tests/test_nginx_container.py b/tests/test_nginx.py similarity index 100% rename from tests/test_nginx_container.py rename to tests/test_nginx.py diff --git a/tests/test_webdriver_container.py b/tests/test_webdriver.py similarity index 100% rename from tests/test_webdriver_container.py rename to tests/test_webdriver.py From 4e4ff3afd3f4fadf96f4c394a033ad8b6c75a6ad Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 May 2022 11:49:27 -0400 Subject: [PATCH 043/425] Add PR template for adding new containers. --- .github/PULL_REQUEST_TEMPLATE/new_container.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/new_container.md diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md new file mode 100644 index 000000000..36e52a03c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -0,0 +1,8 @@ +You have implemented a new container and would like to contribute it? Great! Here are the necessary steps: + +- [ ] You have added the new container as a module in the `testcontainers` directory (such as `testcontainers/my_fancy_container.py`). +- [ ] You have added any new python dependencies in the `extras_require` section of `setup.py`. +- [ ] You have added the `extra_requires` key to `requirements.in`. +- [ ] You have updated all python requirements by running `make requirements` from the root directory. +- [ ] You have added tests for the new container in the `tests` directory, e.g. `tests/test_my_fancy_container.py`. +- [ ] You have added the name of the container (such as `my_fancy_container`) to the `test-components` matrix in `.github/workflows/main.yml` to ensure the tests are run. From 1c7db33436e68b59a6fe2d89ea938f877b483a40 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 May 2022 11:55:46 -0400 Subject: [PATCH 044/425] Add rebase/merge step to PR template. --- .github/PULL_REQUEST_TEMPLATE/new_container.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index 36e52a03c..f89a8d6d2 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -6,3 +6,4 @@ You have implemented a new container and would like to contribute it? Great! Her - [ ] You have updated all python requirements by running `make requirements` from the root directory. - [ ] You have added tests for the new container in the `tests` directory, e.g. `tests/test_my_fancy_container.py`. - [ ] You have added the name of the container (such as `my_fancy_container`) to the `test-components` matrix in `.github/workflows/main.yml` to ensure the tests are run. +- [ ] You have rebased your development branch on `master` (or merged `master` into your development branch). From 281a8c7217e093d02a3cae7680af91e18e94ecc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Sat, 7 May 2022 21:52:07 +0200 Subject: [PATCH 045/425] add keycloak to test-component --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a7272ba9..a3baac405 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,7 @@ jobs: - redis.py - selenium.py - webdriver.py + - keycloak.py runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 From b38e02e58da2f79e06ecee68757226daa3894c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Sat, 7 May 2022 22:21:00 +0200 Subject: [PATCH 046/425] add docker client kw tests --- tests/test_docker_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_docker_client.py diff --git a/tests/test_docker_client.py b/tests/test_docker_client.py new file mode 100644 index 000000000..17e04e27c --- /dev/null +++ b/tests/test_docker_client.py @@ -0,0 +1,25 @@ +from unittest.mock import MagicMock, patch +import docker +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.container import DockerContainer +from testcontainers.core.generic import DbContainer + +def test_docker_client_from_env(): + test_kwargs = dict( + test_kw="test_value" + ) + mock_docker = MagicMock(spec=docker) + with patch("testcontainers.core.docker_client.docker", mock_docker): + DockerClient(**test_kwargs) + + mock_docker.from_env.assert_called_with(**test_kwargs) + +def test_container_docker_client_kw(): + test_kwargs = dict( + test_kw="test_value" + ) + mock_docker = MagicMock(spec=docker) + with patch("testcontainers.core.docker_client.docker", mock_docker): + DockerContainer(image="", docker_client_kw=test_kwargs) + + mock_docker.from_env.assert_called_with(**test_kwargs) From 492fc07751491676c0798778725900bdf4174f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Mon, 9 May 2022 09:06:49 +0200 Subject: [PATCH 047/425] add keycloak's 3.10 requirements --- requirements/3.10.txt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/requirements/3.10.txt b/requirements/3.10.txt index a5f5c4eb7..5ae26cd97 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -73,6 +73,8 @@ docopt==0.6.2 # via docker-compose docutils==0.17.1 # via sphinx +ecdsa==0.17.0 + # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 @@ -151,6 +153,7 @@ py==1.11.0 pyasn1==0.4.8 # via # pyasn1-modules + # python-jose # rsa pyasn1-modules==0.2.8 # via google-auth @@ -186,6 +189,10 @@ pytest-cov==3.0.0 # via -r requirements.in python-dotenv==0.20.0 # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==0.27.0 + # via testcontainers pytz==2022.1 # via # babel @@ -195,7 +202,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.2 +redis==4.3.0 # via testcontainers requests==2.27.1 # via @@ -203,16 +210,20 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-keycloak # sphinx rsa==4.8 - # via google-auth + # via + # google-auth + # python-jose scramp==1.4.1 # via pg8000 -selenium==4.1.4 +selenium==4.1.5 # via testcontainers six==1.16.0 # via # dockerpty + # ecdsa # google-auth # grpcio # jsonschema From 88a24aab1148ae5f1e5d778052b50782aa5005f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Mon, 9 May 2022 09:09:19 +0200 Subject: [PATCH 048/425] flake8 --- tests/test_docker_client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_docker_client.py b/tests/test_docker_client.py index 17e04e27c..af498ff69 100644 --- a/tests/test_docker_client.py +++ b/tests/test_docker_client.py @@ -2,7 +2,7 @@ import docker from testcontainers.core.docker_client import DockerClient from testcontainers.core.container import DockerContainer -from testcontainers.core.generic import DbContainer + def test_docker_client_from_env(): test_kwargs = dict( @@ -13,7 +13,8 @@ def test_docker_client_from_env(): DockerClient(**test_kwargs) mock_docker.from_env.assert_called_with(**test_kwargs) - + + def test_container_docker_client_kw(): test_kwargs = dict( test_kw="test_value" From 88a46d20e08eab2b3d7a8f013091eebbcf90a028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Baumg=C3=A4rtner?= Date: Mon, 9 May 2022 15:55:56 +0200 Subject: [PATCH 049/425] improve default condition Co-authored-by: Till Hoffmann --- testcontainers/core/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 4843166c3..849460df8 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -14,7 +14,7 @@ def __init__(self, image, docker_client_kw: dict = None, **kwargs): self.ports = {} self.volumes = {} self.image = image - self._docker = DockerClient(**(docker_client_kw if docker_client_kw else {})) + self._docker = DockerClient(**(docker_client_kw or {})) self._container = None self._command = None self._name = None From c0dc701d0cc61cfce8764a9958bd495f41341466 Mon Sep 17 00:00:00 2001 From: Netanel Shine Date: Tue, 14 Jun 2022 12:37:28 +0300 Subject: [PATCH 050/425] Add ArangoDB Container support (#221) * Add ArangoDB container Co-authored-by: Till Hoffmann * Update testcontainers/arangodb.py Co-authored-by: Till Hoffmann * Update testcontainers/arangodb.py Co-authored-by: Till Hoffmann * Update testcontainers/arangodb.py Co-authored-by: Till Hoffmann * align to PR comment * Align to 2nd review cycle Co-authored-by: Till Hoffmann --- .github/workflows/main.yml | 1 + README.rst | 1 + docs/conf.py | 2 +- docs/database.rst | 3 +- requirements.in | 2 +- requirements/3.10.txt | 44 ++++++++------ requirements/3.7.txt | 47 +++++++++------ requirements/3.8.txt | 46 ++++++++------ requirements/3.9.txt | 46 ++++++++------ setup.py | 1 + testcontainers/arangodb.py | 80 +++++++++++++++++++++++++ tests/test_arangodb.py | 120 +++++++++++++++++++++++++++++++++++++ 12 files changed, 319 insertions(+), 74 deletions(-) create mode 100644 testcontainers/arangodb.py create mode 100644 tests/test_arangodb.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3baac405..d749a65e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,6 +30,7 @@ jobs: - selenium.py - webdriver.py - keycloak.py + - arangodb.py runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 diff --git a/README.rst b/README.rst index ddc4272c1..ea6461f11 100644 --- a/README.rst +++ b/README.rst @@ -22,6 +22,7 @@ Currently available features: * ClickHouse container * Microsoft SQL Server container * Generic docker containers +* ArangoDB container * LocalStack * RabbitMQ * Keycloak diff --git a/docs/conf.py b/docs/conf.py index 7270d20d5..6077599ec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,7 +66,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/docs/database.rst b/docs/database.rst index be9843706..49d82eeb2 100644 --- a/docs/database.rst +++ b/docs/database.rst @@ -1,7 +1,7 @@ Database containers =================== -Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, MongoDb, ClickHouse or Neo4j. +Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, MongoDb, ClickHouse, Neo4j or ArangoDB .. autoclass:: testcontainers.mysql.MySqlContainer .. autoclass:: testcontainers.mysql.MariaDbContainer @@ -12,3 +12,4 @@ Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, .. autoclass:: testcontainers.mssql.SqlServerContainer .. autoclass:: testcontainers.clickhouse.ClickHouseContainer .. autoclass:: testcontainers.neo4j.Neo4jContainer +.. autoclass:: testcontainers.arangodb.ArangoDbContainer diff --git a/requirements.in b/requirements.in index d6f22ece5..4309c1d7f 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,rabbitmq,clickhouse,keycloak] +-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb] codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 5ae26cd97..09d9c9faa 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -26,9 +26,9 @@ babel==2.10.1 # via sphinx bcrypt==3.2.2 # via paramiko -cachetools==5.0.0 +cachetools==5.2.0 # via google-auth -certifi==2021.10.8 +certifi==2022.5.18.1 # via # requests # urllib3 @@ -43,7 +43,7 @@ clickhouse-driver==0.2.3 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.3.2 +coverage[toml]==6.4.1 # via # codecov # pytest-cov @@ -71,7 +71,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.17.1 +docutils==0.18.1 # via sphinx ecdsa==0.17.0 # via python-jose @@ -79,13 +79,13 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.3 +google-api-core[grpc]==2.8.1 # via google-cloud-pubsub google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers -googleapis-common-protos[grpc]==1.56.0 +googleapis-common-protos[grpc]==1.56.2 # via # google-api-core # grpc-google-iam-v1 @@ -94,13 +94,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.0 +grpcio==1.46.3 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.0 +grpcio-status==1.46.3 # via google-api-core h11==0.13.0 # via wsproto @@ -123,7 +123,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.3 +neo4j==4.4.4 # via testcontainers outcome==1.1.0 # via trio @@ -133,9 +133,9 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.4 +paramiko==2.11.0 # via docker -pg8000==1.26.1 +pg8000==1.29.1 # via -r requirements.in pika==1.2.1 # via testcontainers @@ -165,6 +165,8 @@ pyflakes==2.1.1 # via flake8 pygments==2.12.0 # via sphinx +pyjwt==2.4.0 + # via python-arango pymongo==4.1.1 # via testcontainers pymssql==2.2.5 @@ -175,7 +177,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.8 +pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -187,11 +189,13 @@ pytest==7.1.2 # pytest-cov pytest-cov==3.0.0 # via -r requirements.in +python-arango==7.3.4 + # via testcontainers python-dotenv==0.20.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==0.27.0 +python-keycloak==1.5.0 # via testcontainers pytz==2022.1 # via @@ -202,7 +206,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.0 +redis==4.3.3 # via testcontainers requests==2.27.1 # via @@ -210,15 +214,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-arango # python-keycloak + # requests-toolbelt # sphinx +requests-toolbelt==0.9.1 + # via python-arango rsa==4.8 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.1.5 +selenium==4.2.0 # via testcontainers six==1.16.0 # via @@ -235,7 +243,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.5.0 +sphinx==5.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -249,7 +257,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 # via testcontainers texttable==1.6.4 # via docker-compose @@ -269,6 +277,8 @@ tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via + # python-arango + # python-keycloak # requests # selenium websocket-client==0.59.0 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 455fc9e44..ebc2f128d 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -32,9 +32,9 @@ bcrypt==3.2.2 # via paramiko cached-property==1.5.2 # via docker-compose -cachetools==5.0.0 +cachetools==5.2.0 # via google-auth -certifi==2021.10.8 +certifi==2022.5.18.1 # via # requests # urllib3 @@ -49,7 +49,7 @@ clickhouse-driver==0.2.3 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.3.2 +coverage[toml]==6.4.1 # via # codecov # pytest-cov @@ -77,7 +77,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.17.1 +docutils==0.18.1 # via sphinx ecdsa==0.17.0 # via python-jose @@ -85,13 +85,13 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.3 +google-api-core[grpc]==2.8.1 # via google-cloud-pubsub google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers -googleapis-common-protos[grpc]==1.56.0 +googleapis-common-protos[grpc]==1.56.2 # via # google-api-core # grpc-google-iam-v1 @@ -100,13 +100,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.0 +grpcio==1.46.3 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.0 +grpcio-status==1.46.3 # via google-api-core h11==0.13.0 # via wsproto @@ -117,9 +117,10 @@ idna==3.3 # urllib3 imagesize==1.3.0 # via sphinx -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 # via # jsonschema + # pg8000 # pluggy # pytest # redis @@ -137,7 +138,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.3 +neo4j==4.4.4 # via testcontainers outcome==1.1.0 # via trio @@ -147,9 +148,9 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.4 +paramiko==2.11.0 # via docker -pg8000==1.26.1 +pg8000==1.29.1 # via -r requirements.in pika==1.2.1 # via testcontainers @@ -179,6 +180,8 @@ pyflakes==2.1.1 # via flake8 pygments==2.12.0 # via sphinx +pyjwt==2.4.0 + # via python-arango pymongo==4.1.1 # via testcontainers pymssql==2.2.5 @@ -189,7 +192,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.8 +pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -201,11 +204,13 @@ pytest==7.1.2 # pytest-cov pytest-cov==3.0.0 # via -r requirements.in +python-arango==7.3.4 + # via testcontainers python-dotenv==0.20.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==0.27.0 +python-keycloak==1.5.0 # via testcontainers pytz==2022.1 # via @@ -216,7 +221,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.2 +redis==4.3.3 # via testcontainers requests==2.27.1 # via @@ -224,15 +229,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-arango # python-keycloak + # requests-toolbelt # sphinx +requests-toolbelt==0.9.1 + # via python-arango rsa==4.8 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.1.3 +selenium==4.2.0 # via testcontainers six==1.16.0 # via @@ -249,7 +258,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.5.0 +sphinx==5.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -263,7 +272,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 # via testcontainers texttable==1.6.4 # via docker-compose @@ -289,6 +298,8 @@ tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via + # python-arango + # python-keycloak # requests # selenium websocket-client==0.59.0 diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 7189bc0b4..823b0a9d2 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -30,9 +30,9 @@ backports-zoneinfo==0.2.1 # tzlocal bcrypt==3.2.2 # via paramiko -cachetools==5.0.0 +cachetools==5.2.0 # via google-auth -certifi==2021.10.8 +certifi==2022.5.18.1 # via # requests # urllib3 @@ -47,7 +47,7 @@ clickhouse-driver==0.2.3 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.3.2 +coverage[toml]==6.4.1 # via # codecov # pytest-cov @@ -75,7 +75,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.17.1 +docutils==0.18.1 # via sphinx ecdsa==0.17.0 # via python-jose @@ -83,13 +83,13 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.3 +google-api-core[grpc]==2.8.1 # via google-cloud-pubsub google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers -googleapis-common-protos[grpc]==1.56.0 +googleapis-common-protos[grpc]==1.56.2 # via # google-api-core # grpc-google-iam-v1 @@ -98,13 +98,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.0 +grpcio==1.46.3 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.0 +grpcio-status==1.46.3 # via google-api-core h11==0.13.0 # via wsproto @@ -115,7 +115,7 @@ idna==3.3 # urllib3 imagesize==1.3.0 # via sphinx -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 # via sphinx iniconfig==1.1.1 # via pytest @@ -129,7 +129,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.3 +neo4j==4.4.4 # via testcontainers outcome==1.1.0 # via trio @@ -139,9 +139,9 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.4 +paramiko==2.11.0 # via docker -pg8000==1.26.1 +pg8000==1.29.1 # via -r requirements.in pika==1.2.1 # via testcontainers @@ -171,6 +171,8 @@ pyflakes==2.1.1 # via flake8 pygments==2.12.0 # via sphinx +pyjwt==2.4.0 + # via python-arango pymongo==4.1.1 # via testcontainers pymssql==2.2.5 @@ -181,7 +183,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.8 +pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -193,11 +195,13 @@ pytest==7.1.2 # pytest-cov pytest-cov==3.0.0 # via -r requirements.in +python-arango==7.3.4 + # via testcontainers python-dotenv==0.20.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==0.27.0 +python-keycloak==1.5.0 # via testcontainers pytz==2022.1 # via @@ -208,7 +212,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.2 +redis==4.3.3 # via testcontainers requests==2.27.1 # via @@ -216,15 +220,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-arango # python-keycloak + # requests-toolbelt # sphinx +requests-toolbelt==0.9.1 + # via python-arango rsa==4.8 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.1.3 +selenium==4.2.0 # via testcontainers six==1.16.0 # via @@ -241,7 +249,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.5.0 +sphinx==5.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -255,7 +263,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 # via testcontainers texttable==1.6.4 # via docker-compose @@ -275,6 +283,8 @@ tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via + # python-arango + # python-keycloak # requests # selenium websocket-client==0.59.0 diff --git a/requirements/3.9.txt b/requirements/3.9.txt index eef7dbced..8d9d6a15c 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -26,9 +26,9 @@ babel==2.10.1 # via sphinx bcrypt==3.2.2 # via paramiko -cachetools==5.0.0 +cachetools==5.2.0 # via google-auth -certifi==2021.10.8 +certifi==2022.5.18.1 # via # requests # urllib3 @@ -43,7 +43,7 @@ clickhouse-driver==0.2.3 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.3.2 +coverage[toml]==6.4.1 # via # codecov # pytest-cov @@ -71,7 +71,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.17.1 +docutils==0.18.1 # via sphinx ecdsa==0.17.0 # via python-jose @@ -79,13 +79,13 @@ entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.7.3 +google-api-core[grpc]==2.8.1 # via google-cloud-pubsub google-auth==2.6.6 # via google-api-core google-cloud-pubsub==1.7.1 # via testcontainers -googleapis-common-protos[grpc]==1.56.0 +googleapis-common-protos[grpc]==1.56.2 # via # google-api-core # grpc-google-iam-v1 @@ -94,13 +94,13 @@ greenlet==1.1.2 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.0 +grpcio==1.46.3 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.0 +grpcio-status==1.46.3 # via google-api-core h11==0.13.0 # via wsproto @@ -111,7 +111,7 @@ idna==3.3 # urllib3 imagesize==1.3.0 # via sphinx -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 # via sphinx iniconfig==1.1.1 # via pytest @@ -125,7 +125,7 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 -neo4j==4.4.3 +neo4j==4.4.4 # via testcontainers outcome==1.1.0 # via trio @@ -135,9 +135,9 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.10.4 +paramiko==2.11.0 # via docker -pg8000==1.26.1 +pg8000==1.29.1 # via -r requirements.in pika==1.2.1 # via testcontainers @@ -167,6 +167,8 @@ pyflakes==2.1.1 # via flake8 pygments==2.12.0 # via sphinx +pyjwt==2.4.0 + # via python-arango pymongo==4.1.1 # via testcontainers pymssql==2.2.5 @@ -177,7 +179,7 @@ pynacl==1.5.0 # via paramiko pyopenssl==22.0.0 # via urllib3 -pyparsing==3.0.8 +pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema @@ -189,11 +191,13 @@ pytest==7.1.2 # pytest-cov pytest-cov==3.0.0 # via -r requirements.in +python-arango==7.3.4 + # via testcontainers python-dotenv==0.20.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==0.27.0 +python-keycloak==1.5.0 # via testcontainers pytz==2022.1 # via @@ -204,7 +208,7 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.2.2 +redis==4.3.3 # via testcontainers requests==2.27.1 # via @@ -212,15 +216,19 @@ requests==2.27.1 # docker # docker-compose # google-api-core + # python-arango # python-keycloak + # requests-toolbelt # sphinx +requests-toolbelt==0.9.1 + # via python-arango rsa==4.8 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.1.3 +selenium==4.2.0 # via testcontainers six==1.16.0 # via @@ -237,7 +245,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==4.5.0 +sphinx==5.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -251,7 +259,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 # via testcontainers texttable==1.6.4 # via docker-compose @@ -271,6 +279,8 @@ tzlocal==4.2 # via clickhouse-driver urllib3[secure,socks]==1.26.9 # via + # python-arango + # python-keycloak # requests # selenium websocket-client==0.59.0 diff --git a/setup.py b/setup.py index de1293dc3..5eb485455 100644 --- a/setup.py +++ b/setup.py @@ -67,6 +67,7 @@ 'rabbitmq': ['pika'], 'clickhouse': ['clickhouse-driver'], 'keycloak': ['python-keycloak'], + 'arangodb': ['python-arango'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/arangodb.py b/testcontainers/arangodb.py new file mode 100644 index 000000000..bb9563306 --- /dev/null +++ b/testcontainers/arangodb.py @@ -0,0 +1,80 @@ +""" +ArangoDB container support. +""" +from os import environ +from testcontainers.core.config import MAX_TRIES +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +class ArangoDbContainer(DbContainer): + """ + ArangoDB container. + + Example + ------- + The example will spin up a ArangoDB container. + You may use the :code:`get_connection_url()` method which returns a arangoclient-compatible url + in format :code:`scheme://host:port`. As of now, only a single host is supported (over HTTP). + :: + + with ArangoContainer("arangodb:3.9.1") as arango: + client = ArangoClient(hosts=arango.get_connection_url()) + + # Connect + sys_db = arango_client.db(username='root', password='') + + # Create a new database named "test". + sys_db.create_database("test") + """ + def __init__(self, + image="arangodb:latest", + port_to_expose=8529, + arango_root_password='passwd', + arango_no_auth=False, + arango_random_root_password=False, + **kwargs): + """ + Args: + image (str, optional): Actual docker image/tag to pull. Defaults to "arangodb:latest". + port_to_expose (int, optional): Port the container needs to expose. Defaults to 8529. + arango_root_password (str, optional): Start ArangoDB with the + given password for root. Defaults to 'passwd'. + arango_no_auth (bool, optional): Disable authentication completely. + Defaults to False. + arango_random_root_password (bool, optional): Let ArangoDB generate a + random root password. Defaults to False. + """ + super().__init__(image=image) + self.port_to_expose = port_to_expose + self.with_exposed_ports(self.port_to_expose) + + # https://www.arangodb.com/docs/stable/deployment-single-instance-manual-start.html + self.arango_no_auth = arango_no_auth or \ + environ.get("ARANGO_NO_AUTH") + self.arango_root_password = arango_root_password or \ + environ.get('ARANGO_ROOT_PASSWORD') + self.arango_random_root_password = arango_random_root_password or \ + environ.get('ARANGO_RANDOM_ROOT_PASSWORD') + + def _configure(self): + self.with_env( + "ARANGO_NO_AUTH", self.arango_no_auth) + self.with_env( + "ARANGO_ROOT_PASSWORD", self.arango_root_password) + self.with_env( + "ARANGO_RANDOM_ROOT_PASSWORD", self.arango_random_root_password) + + def get_connection_url(self): + # for now, single host over HTTP + scheme = 'http' + port = self.get_exposed_port(self.port_to_expose) + url = f"{scheme}://{self.get_container_host_ip()}:{port}" + + return url + + def _connect(self): + wait_for_logs( + self, + predicate="is ready for business", + timeout=MAX_TRIES) diff --git a/tests/test_arangodb.py b/tests/test_arangodb.py new file mode 100644 index 000000000..574a9fd91 --- /dev/null +++ b/tests/test_arangodb.py @@ -0,0 +1,120 @@ +""" +ArangoDB Container Tests +""" +import pytest +from arango import ArangoClient +from arango.exceptions import DatabaseCreateError, ServerVersionError +from testcontainers.arangodb import ArangoDbContainer + +ARANGODB_IMAGE_NAME = 'arangodb' + + +def arango_test_ops(arango_client, expeced_version, db_user='root', db_pass=''): + """ + Basic ArangoDB operations to test DB really up and running. + """ + students_to_insert_cnt = 3 + + # Taken from https://github.com/ArangoDB-Community/python-arango/blob/main/README.md + # Connect to "_system" database as root user. + sys_db = arango_client.db("_system", username=db_user, password=db_pass) + assert sys_db.version() == expeced_version + + # Create a new database named "test". + sys_db.create_database("test") + + # Connect to "test" database as root user. + database = arango_client.db("test", username=db_user, password=db_pass) + + # Create a new collection named "students". + students = database.create_collection("students") + + # Add a hash index to the collection. + students.add_hash_index(fields=["name"], unique=True) + + # Insert new documents into the collection. (students_to_insert_cnt) + students.insert({"name": "jane", "age": 39}) + students.insert({"name": "josh", "age": 18}) + students.insert({"name": "judy", "age": 21}) + + # Execute an AQL query and iterate through the result cursor. + cursor = database.aql.execute("FOR doc IN students RETURN doc") + student_names = [document["name"] for document in cursor] + + assert len(student_names) == students_to_insert_cnt + + +def test_docker_run_arango(): + """ + Test ArangoDB container with default settings. + """ + image_version = '3.9.1' + image = f'{ARANGODB_IMAGE_NAME}:{image_version}' + arango_db_root_password = 'passwd' + + with ArangoDbContainer(image) as arango: + client = ArangoClient(hosts=arango.get_connection_url()) + + # Test invalid auth + with pytest.raises(DatabaseCreateError): + sys_db = client.db("_system", username="root", password='notTheRightPass') + sys_db.create_database("test") + + arango_test_ops( + arango_client=client, + expeced_version=image_version, + db_pass=arango_db_root_password) + + +def test_docker_run_arango_without_auth(): + """ + Test ArangoDB container with ARANGO_NO_AUTH var set. + """ + image_version = '3.9.1' + image = f'{ARANGODB_IMAGE_NAME}:{image_version}' + + with ArangoDbContainer(image, arango_no_auth=True) as arango: + client = ArangoClient(hosts=arango.get_connection_url()) + + arango_test_ops( + arango_client=client, + expeced_version=image_version, + db_pass='') + + +def test_docker_run_arango_older_version(): + """ + Test ArangoDB container with older tag/version. + the idea behind it hides in the logic of arangodb._connect() -> + Where it waits the container to sign "ready for business" - + If someone will change the logic in the future + we must verify older image tags still supported. (without that logic - we'll face race issues + where we try to create & populate DB when ArangoDB not really ready. + """ + image_version = '3.1.7' + image = f'{ARANGODB_IMAGE_NAME}:{image_version}' + + with ArangoDbContainer(image, arango_no_auth=True) as arango: + client = ArangoClient(hosts=arango.get_connection_url()) + + arango_test_ops( + arango_client=client, + expeced_version=image_version, + db_pass='') + + +def test_docker_run_arango_random_root_password(): + """ + Test ArangoDB container with ARANGO_RANDOM_ROOT_PASSWORD var set. + """ + image_version = '3.9.1' + image = f'{ARANGODB_IMAGE_NAME}:{image_version}' + arango_db_root_password = 'passwd' + + with ArangoDbContainer(image, arango_random_root_password=True) as arango: + client = ArangoClient(hosts=arango.get_connection_url()) + + # Test invalid auth (we don't know the password in random mode) + with pytest.raises(ServerVersionError): + sys_db = client.db("_system", username='root', password=arango_db_root_password) + assert sys_db.version() == image_version From 256684fc97c3c3b8508dd29511c646496cb7bf2c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 14 Jun 2022 15:16:24 -0400 Subject: [PATCH 051/425] Fix None/True/False handling in Arango. --- testcontainers/arangodb.py | 62 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/testcontainers/arangodb.py b/testcontainers/arangodb.py index bb9563306..f6232ff73 100644 --- a/testcontainers/arangodb.py +++ b/testcontainers/arangodb.py @@ -5,6 +5,7 @@ from testcontainers.core.config import MAX_TRIES from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_for_logs +import typing class ArangoDbContainer(DbContainer): @@ -22,52 +23,55 @@ class ArangoDbContainer(DbContainer): client = ArangoClient(hosts=arango.get_connection_url()) # Connect - sys_db = arango_client.db(username='root', password='') + sys_db = arango_client.db(username="root", password="") # Create a new database named "test". sys_db.create_database("test") """ def __init__(self, - image="arangodb:latest", - port_to_expose=8529, - arango_root_password='passwd', - arango_no_auth=False, - arango_random_root_password=False, + image: str = "arangodb:latest", + port_to_expose: int = 8529, + arango_root_password: str = "passwd", + arango_no_auth: typing.Optional[bool] = None, + arango_random_root_password: typing.Optional[bool] = None, **kwargs): """ Args: - image (str, optional): Actual docker image/tag to pull. Defaults to "arangodb:latest". - port_to_expose (int, optional): Port the container needs to expose. Defaults to 8529. - arango_root_password (str, optional): Start ArangoDB with the - given password for root. Defaults to 'passwd'. - arango_no_auth (bool, optional): Disable authentication completely. - Defaults to False. - arango_random_root_password (bool, optional): Let ArangoDB generate a - random root password. Defaults to False. + image: Actual docker image/tag to pull. + port_to_expose: Port the container needs to expose. + arango_root_password: Start ArangoDB with the given password for root. Defaults to the + environment variable `ARANGO_ROOT_PASSWORD` if `None`. + arango_no_auth: Disable authentication completely. Defaults to the environment variable + `ARANGO_NO_AUTH` or `False` if the environment variable is not available. + arango_random_root_password: Let ArangoDB generate a random root password. Defaults to + the environment variable `ARANGO_NO_AUTH` or `False` if the environment variable is + not available. """ - super().__init__(image=image) + super().__init__(image=image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) - # https://www.arangodb.com/docs/stable/deployment-single-instance-manual-start.html - self.arango_no_auth = arango_no_auth or \ - environ.get("ARANGO_NO_AUTH") - self.arango_root_password = arango_root_password or \ - environ.get('ARANGO_ROOT_PASSWORD') - self.arango_random_root_password = arango_random_root_password or \ - environ.get('ARANGO_RANDOM_ROOT_PASSWORD') + # See https://www.arangodb.com/docs/stable/deployment-single-instance-manual-start.html for + # details. We convert to int then to bool because Arango uses the string literal "1" to + # indicate flags. + self.arango_no_auth = bool(int(environ.get("ARANGO_NO_AUTH", 0) if arango_no_auth is None + else arango_no_auth)) + self.arango_root_password = environ.get("ARANGO_ROOT_PASSWORD") if arango_root_password is \ + None else arango_root_password + self.arango_random_root_password = bool(int( + environ.get("ARANGO_RANDOM_ROOT_PASSWORD", 0) if arango_random_root_password is None + else arango_random_root_password + )) def _configure(self): - self.with_env( - "ARANGO_NO_AUTH", self.arango_no_auth) - self.with_env( - "ARANGO_ROOT_PASSWORD", self.arango_root_password) - self.with_env( - "ARANGO_RANDOM_ROOT_PASSWORD", self.arango_random_root_password) + self.with_env("ARANGO_NO_AUTH", "1" if self.arango_no_auth else "0") + self.with_env("ARANGO_ROOT_PASSWORD", self.arango_root_password) + self.with_env("ARANGO_RANDOM_ROOT_PASSWORD", + "1" if self.arango_random_root_password else "0") def get_connection_url(self): # for now, single host over HTTP - scheme = 'http' + scheme = "http" port = self.get_exposed_port(self.port_to_expose) url = f"{scheme}://{self.get_container_host_ip()}:{port}" From 69f50453859cb1eaa5728703a2562ea9e99383c3 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 14 Jun 2022 15:19:37 -0400 Subject: [PATCH 052/425] Update environment variable handling. --- testcontainers/arangodb.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/testcontainers/arangodb.py b/testcontainers/arangodb.py index f6232ff73..763ee8c61 100644 --- a/testcontainers/arangodb.py +++ b/testcontainers/arangodb.py @@ -64,10 +64,11 @@ def __init__(self, )) def _configure(self): - self.with_env("ARANGO_NO_AUTH", "1" if self.arango_no_auth else "0") self.with_env("ARANGO_ROOT_PASSWORD", self.arango_root_password) - self.with_env("ARANGO_RANDOM_ROOT_PASSWORD", - "1" if self.arango_random_root_password else "0") + if self.arango_no_auth: + self.with_env("ARANGO_NO_AUTH", "1") + if self.arango_random_root_password: + self.with_env("ARANGO_RANDOM_ROOT_PASSWORD", "1") def get_connection_url(self): # for now, single host over HTTP From 3f720fa9350e6a599cd5fa93bc4717b7fb08f7ea Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 14 Jun 2022 15:26:49 -0400 Subject: [PATCH 053/425] Update default argument docstring for arangodb. --- testcontainers/arangodb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testcontainers/arangodb.py b/testcontainers/arangodb.py index 763ee8c61..ec4f005ea 100644 --- a/testcontainers/arangodb.py +++ b/testcontainers/arangodb.py @@ -42,10 +42,10 @@ def __init__(self, arango_root_password: Start ArangoDB with the given password for root. Defaults to the environment variable `ARANGO_ROOT_PASSWORD` if `None`. arango_no_auth: Disable authentication completely. Defaults to the environment variable - `ARANGO_NO_AUTH` or `False` if the environment variable is not available. + `ARANGO_NO_AUTH` if `None` or `False` if the environment variable is not available. arango_random_root_password: Let ArangoDB generate a random root password. Defaults to - the environment variable `ARANGO_NO_AUTH` or `False` if the environment variable is - not available. + the environment variable `ARANGO_NO_AUTH` if `None` or `False` if the environment + variable is not available. """ super().__init__(image=image, **kwargs) self.port_to_expose = port_to_expose From 1c4de25455481baa02e3042a353db45c55131858 Mon Sep 17 00:00:00 2001 From: Joe Morales Date: Mon, 25 Jul 2022 14:48:26 -0600 Subject: [PATCH 054/425] DockerClient.port() fails when called too quickly (from DockerContainer.get_exposed_port) after container start. Wrap DockerContainer.get_exposed_port in wait_container_is_ready() and change DockerClient.port to raise ConnectionError so that it can be caught and retried. --- testcontainers/core/container.py | 2 ++ testcontainers/core/docker_client.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 849460df8..f8b006857 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -1,6 +1,7 @@ from deprecation import deprecated from docker.models.containers import Container +from testcontainers.core.waiting_utils import wait_container_is_ready from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.utils import setup_logger, inside_container, is_arm @@ -101,6 +102,7 @@ def get_container_host_ip(self) -> str: return gateway_ip return host + @wait_container_is_ready() def get_exposed_port(self, port) -> str: mapped_port = self.get_docker_client().port(self._container.id, port) if inside_container(): diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index b6513d8fe..585cb8ff0 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -43,7 +43,7 @@ def run(self, image: str, def port(self, container_id, port): port_mappings = self.client.api.port(container_id, port) if not port_mappings: - raise RuntimeError(f'port mapping for container {container_id} and port {port} is not ' + raise ConnectionError(f'port mapping for container {container_id} and port {port} is not ' 'available') return port_mappings[0]["HostPort"] From 5605511f241ead953d4a07f3e96bcd9d81ce6acf Mon Sep 17 00:00:00 2001 From: Joe Morales Date: Mon, 25 Jul 2022 16:13:28 -0600 Subject: [PATCH 055/425] Fix line length and indent --- testcontainers/core/docker_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index 585cb8ff0..e10537aa5 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -43,8 +43,8 @@ def run(self, image: str, def port(self, container_id, port): port_mappings = self.client.api.port(container_id, port) if not port_mappings: - raise ConnectionError(f'port mapping for container {container_id} and port {port} is not ' - 'available') + raise ConnectionError(f'port mapping for container {container_id} and port {port} is ' + 'not available') return port_mappings[0]["HostPort"] def get_container(self, container_id): From 5ca7bbe0adf4f6f69a613ccca9ac48d7bb952595 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Thu, 11 Aug 2022 09:48:10 +0200 Subject: [PATCH 056/425] Support Elasticsearch 8.x --- testcontainers/elasticsearch.py | 47 +++++++++++++++++++++++++++++++-- tests/test_elasticsearch.py | 6 +++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index 1e9f6eb1e..b0e0672db 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -10,10 +10,50 @@ # 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 logging +import re +import urllib +from typing import Dict + from deprecation import deprecated + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready -import urllib + +_FALLBACK_VERSION = 8 +"""This version is used when no version could be detected from the image name.""" + + +def _major_version_from_image_name(image_name: str) -> int: + """Returns the major version from a container name like 'elasticsearch:8.1.0' + If the major version could not be determined, it will use the most recent + one (8 at the time of writing 2022-08-11). + """ + version_string = image_name.split(":")[-1] + regex_match = re.compile(r"(\d+)\.\d+\.\d+").match(version_string) + if not regex_match: + logging.warning("Could not determine major version from image name '%s'. Will use %s", + image_name, _FALLBACK_VERSION) + return _FALLBACK_VERSION + else: + return int(regex_match.group(1)) + + +def _environment_by_version(version: int) -> Dict[str, str]: + """Returns environment variables required for each major version to work.""" + if version == 6: + # This setting is needed to avoid the check for the kernel parameter + # vm.max_map_count in the BootstrapChecks + return {"discovery.zen.minimum_master_nodes": "1"} + elif version == 7: + return {} + elif version == 8: + # Elasticsearch uses https now by default. However, our readiness + # check uses http, which does not work. Hence we disable security + # which should not be an issue for our context + return {"xpack.security.enabled": "false"} + else: + raise ValueError(f"Unknown elasticsearch version given: {version}") class ElasticSearchContainer(DockerContainer): @@ -33,7 +73,10 @@ def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs): self.with_exposed_ports(self.port_to_expose) self.with_env('transport.host', '127.0.0.1') self.with_env('http.host', '0.0.0.0') - self.with_env('discovery.zen.minimum_master_nodes', '1') + + major_version = _major_version_from_image_name(image) + for key, value in _environment_by_version(major_version).items(): + self.with_env(key, value) @wait_container_is_ready() def _connect(self): diff --git a/tests/test_elasticsearch.py b/tests/test_elasticsearch.py index f9f769cc5..6bbc57fd0 100644 --- a/tests/test_elasticsearch.py +++ b/tests/test_elasticsearch.py @@ -1,11 +1,13 @@ import json import urllib +import pytest from testcontainers.elasticsearch import ElasticSearchContainer -def test_docker_run_elasticsearch(): - version = '7.16.1' +# The versions below were the current supported versions at time of writing (2022-08-11) +@pytest.mark.parametrize('version', ['6.8.23', '7.17.5', '8.3.3']) +def test_docker_run_elasticsearch(version): with ElasticSearchContainer(f'elasticsearch:{version}') as es: resp = urllib.request.urlopen(es.get_url()) assert json.loads(resp.read().decode())['version']['number'] == version From 85b692f03be5e8e7a454bceb9ea352bb9b16964b Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Mon, 15 Aug 2022 20:16:12 +0200 Subject: [PATCH 057/425] Fix indenting --- testcontainers/elasticsearch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index b0e0672db..6b6f24b35 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -33,7 +33,7 @@ def _major_version_from_image_name(image_name: str) -> int: regex_match = re.compile(r"(\d+)\.\d+\.\d+").match(version_string) if not regex_match: logging.warning("Could not determine major version from image name '%s'. Will use %s", - image_name, _FALLBACK_VERSION) + image_name, _FALLBACK_VERSION) return _FALLBACK_VERSION else: return int(regex_match.group(1)) From dac8319e511ac623671328eb8283cc4416e18f09 Mon Sep 17 00:00:00 2001 From: Pepijn Date: Mon, 22 Aug 2022 11:23:42 +0200 Subject: [PATCH 058/425] Add azurite module --- .github/workflows/main.yml | 1 + README.rst | 1 + requirements.in | 2 +- requirements/3.10.txt | 23 ++++++++ requirements/3.6.txt | 40 +++++++++++++- requirements/3.7.txt | 22 ++++++++ requirements/3.8.txt | 23 ++++++++ requirements/3.9.txt | 23 ++++++++ setup.py | 1 + testcontainers/azurite.py | 110 +++++++++++++++++++++++++++++++++++++ tests/test_azurite.py | 13 +++++ 11 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 testcontainers/azurite.py create mode 100644 tests/test_azurite.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d749a65e2..46c13d042 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,6 +31,7 @@ jobs: - webdriver.py - keycloak.py - arangodb.py + - azurite.py runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 diff --git a/README.rst b/README.rst index ea6461f11..2f55e0567 100644 --- a/README.rst +++ b/README.rst @@ -26,6 +26,7 @@ Currently available features: * LocalStack * RabbitMQ * Keycloak +* Azurite container Installation ------------ diff --git a/requirements.in b/requirements.in index 4309c1d7f..90406ba20 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,rabbitmq,clickhouse,keycloak,arangodb] +-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite] codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 09d9c9faa..f2ee01781 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -22,6 +22,12 @@ attrs==21.4.0 # outcome # pytest # trio +azure-core==1.25.0 + # via + # azure-storage-blob + # msrest +azure-storage-blob==12.13.1 + # via testcontainers babel==2.10.1 # via sphinx bcrypt==3.2.2 @@ -30,6 +36,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.5.18.1 # via + # msrest # requests # urllib3 cffi==1.15.0 @@ -50,6 +57,7 @@ coverage[toml]==6.4.1 cryptography==36.0.2 # via # -r requirements.in + # azure-storage-blob # paramiko # pyopenssl # urllib3 @@ -113,6 +121,8 @@ imagesize==1.3.0 # via sphinx iniconfig==1.1.1 # via pytest +isodate==0.6.1 + # via msrest jinja2==3.1.2 # via sphinx jsonschema==3.2.0 @@ -123,8 +133,12 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +msrest==0.7.1 + # via azure-storage-blob neo4j==4.4.4 # via testcontainers +oauthlib==3.2.0 + # via requests-oauthlib outcome==1.1.0 # via trio packaging==21.3 @@ -210,14 +224,19 @@ redis==4.3.3 # via testcontainers requests==2.27.1 # via + # azure-core # codecov # docker # docker-compose # google-api-core + # msrest # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx +requests-oauthlib==1.3.1 + # via msrest requests-toolbelt==0.9.1 # via python-arango rsa==4.8 @@ -230,10 +249,12 @@ selenium==4.2.0 # via testcontainers six==1.16.0 # via + # azure-core # dockerpty # ecdsa # google-auth # grpcio + # isodate # jsonschema # paramiko # websocket-client @@ -271,6 +292,8 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium +typing-extensions==4.3.0 + # via azure-core tzdata==2022.1 # via pytz-deprecation-shim tzlocal==4.2 diff --git a/requirements/3.6.txt b/requirements/3.6.txt index 0acd6776f..d7ee5c3ff 100644 --- a/requirements/3.6.txt +++ b/requirements/3.6.txt @@ -16,6 +16,12 @@ attrs==21.4.0 # via # jsonschema # pytest +azure-core==1.24.2 + # via + # azure-storage-blob + # msrest +azure-storage-blob==12.13.1 + # via testcontainers babel==2.10.1 # via sphinx backports.zoneinfo==0.2.1 @@ -29,7 +35,9 @@ cached-property==1.5.2 cachetools==4.2.4 # via google-auth certifi==2021.10.8 - # via requests + # via + # msrest + # requests cffi==1.15.0 # via # bcrypt @@ -48,9 +56,12 @@ coverage[toml]==6.2 cryptography==36.0.2 # via # -r requirements.in + # azure-storage-blob # paramiko cx-oracle==8.3.0 # via testcontainers +dataclasses==0.8 + # via python-arango deprecated==1.2.13 # via redis deprecation==2.1.0 @@ -114,6 +125,8 @@ importlib-resources==5.4.0 # via backports.zoneinfo iniconfig==1.1.1 # via pytest +isodate==0.6.1 + # via msrest jinja2==3.0.3 # via sphinx jsonschema==3.2.0 @@ -124,13 +137,18 @@ markupsafe==2.0.1 # via jinja2 mccabe==0.6.1 # via flake8 +msrest==0.7.1 + # via azure-storage-blob neo4j==4.4.3 # via testcontainers +oauthlib==3.2.0 + # via requests-oauthlib packaging==21.3 # via # deprecation # pytest # redis + # setuptools-scm # sphinx paramiko==2.10.4 # via docker @@ -164,6 +182,8 @@ pyflakes==2.1.1 # via flake8 pygments==2.12.0 # via sphinx +pyjwt==2.4.0 + # via python-arango pymongo==4.1.1 # via testcontainers pymssql==2.2.5 @@ -182,6 +202,8 @@ pytest==7.0.1 # pytest-cov pytest-cov==3.0.0 # via -r requirements.in +python-arango==7.3.1 + # via testcontainers python-dotenv==0.20.0 # via docker-compose python-jose==3.3.0 @@ -201,12 +223,21 @@ redis==4.2.2 # via testcontainers requests==2.27.1 # via + # azure-core # codecov # docker # docker-compose # google-api-core + # msrest + # python-arango # python-keycloak + # requests-oauthlib + # requests-toolbelt # sphinx +requests-oauthlib==1.3.1 + # via msrest +requests-toolbelt==0.9.1 + # via python-arango rsa==4.8 # via # google-auth @@ -215,12 +246,16 @@ scramp==1.4.1 # via pg8000 selenium==3.141.0 # via testcontainers +setuptools-scm[toml]==6.4.2 + # via python-arango six==1.16.0 # via + # azure-core # dockerpty # ecdsa # google-auth # grpcio + # isodate # jsonschema # paramiko # websocket-client @@ -248,9 +283,11 @@ tomli==1.2.3 # via # coverage # pytest + # setuptools-scm typing-extensions==4.1.1 # via # async-timeout + # azure-core # importlib-metadata # redis tzdata==2022.1 @@ -259,6 +296,7 @@ tzlocal==4.2 # via clickhouse-driver urllib3==1.26.9 # via + # python-arango # requests # selenium websocket-client==0.59.0 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index ebc2f128d..cadea609b 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -22,6 +22,12 @@ attrs==21.4.0 # outcome # pytest # trio +azure-core==1.25.0 + # via + # azure-storage-blob + # msrest +azure-storage-blob==12.13.1 + # via testcontainers babel==2.10.1 # via sphinx backports-zoneinfo==0.2.1 @@ -36,6 +42,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.5.18.1 # via + # msrest # requests # urllib3 cffi==1.15.0 @@ -56,6 +63,7 @@ coverage[toml]==6.4.1 cryptography==36.0.2 # via # -r requirements.in + # azure-storage-blob # paramiko # pyopenssl # urllib3 @@ -128,6 +136,8 @@ importlib-metadata==4.11.4 # sqlalchemy iniconfig==1.1.1 # via pytest +isodate==0.6.1 + # via msrest jinja2==3.1.2 # via sphinx jsonschema==3.2.0 @@ -138,8 +148,12 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +msrest==0.7.1 + # via azure-storage-blob neo4j==4.4.4 # via testcontainers +oauthlib==3.2.0 + # via requests-oauthlib outcome==1.1.0 # via trio packaging==21.3 @@ -225,14 +239,19 @@ redis==4.3.3 # via testcontainers requests==2.27.1 # via + # azure-core # codecov # docker # docker-compose # google-api-core + # msrest # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx +requests-oauthlib==1.3.1 + # via msrest requests-toolbelt==0.9.1 # via python-arango rsa==4.8 @@ -245,10 +264,12 @@ selenium==4.2.0 # via testcontainers six==1.16.0 # via + # azure-core # dockerpty # ecdsa # google-auth # grpcio + # isodate # jsonschema # paramiko # websocket-client @@ -289,6 +310,7 @@ trio-websocket==0.9.2 typing-extensions==4.2.0 # via # async-timeout + # azure-core # h11 # importlib-metadata # redis diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 823b0a9d2..4d9cd0942 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -22,6 +22,12 @@ attrs==21.4.0 # outcome # pytest # trio +azure-core==1.25.0 + # via + # azure-storage-blob + # msrest +azure-storage-blob==12.13.1 + # via testcontainers babel==2.10.1 # via sphinx backports-zoneinfo==0.2.1 @@ -34,6 +40,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.5.18.1 # via + # msrest # requests # urllib3 cffi==1.15.0 @@ -54,6 +61,7 @@ coverage[toml]==6.4.1 cryptography==36.0.2 # via # -r requirements.in + # azure-storage-blob # paramiko # pyopenssl # urllib3 @@ -119,6 +127,8 @@ importlib-metadata==4.11.4 # via sphinx iniconfig==1.1.1 # via pytest +isodate==0.6.1 + # via msrest jinja2==3.1.2 # via sphinx jsonschema==3.2.0 @@ -129,8 +139,12 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +msrest==0.7.1 + # via azure-storage-blob neo4j==4.4.4 # via testcontainers +oauthlib==3.2.0 + # via requests-oauthlib outcome==1.1.0 # via trio packaging==21.3 @@ -216,14 +230,19 @@ redis==4.3.3 # via testcontainers requests==2.27.1 # via + # azure-core # codecov # docker # docker-compose # google-api-core + # msrest # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx +requests-oauthlib==1.3.1 + # via msrest requests-toolbelt==0.9.1 # via python-arango rsa==4.8 @@ -236,10 +255,12 @@ selenium==4.2.0 # via testcontainers six==1.16.0 # via + # azure-core # dockerpty # ecdsa # google-auth # grpcio + # isodate # jsonschema # paramiko # websocket-client @@ -277,6 +298,8 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium +typing-extensions==4.3.0 + # via azure-core tzdata==2022.1 # via pytz-deprecation-shim tzlocal==4.2 diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 8d9d6a15c..ac73ba552 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -22,6 +22,12 @@ attrs==21.4.0 # outcome # pytest # trio +azure-core==1.25.0 + # via + # azure-storage-blob + # msrest +azure-storage-blob==12.13.1 + # via testcontainers babel==2.10.1 # via sphinx bcrypt==3.2.2 @@ -30,6 +36,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.5.18.1 # via + # msrest # requests # urllib3 cffi==1.15.0 @@ -50,6 +57,7 @@ coverage[toml]==6.4.1 cryptography==36.0.2 # via # -r requirements.in + # azure-storage-blob # paramiko # pyopenssl # urllib3 @@ -115,6 +123,8 @@ importlib-metadata==4.11.4 # via sphinx iniconfig==1.1.1 # via pytest +isodate==0.6.1 + # via msrest jinja2==3.1.2 # via sphinx jsonschema==3.2.0 @@ -125,8 +135,12 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +msrest==0.7.1 + # via azure-storage-blob neo4j==4.4.4 # via testcontainers +oauthlib==3.2.0 + # via requests-oauthlib outcome==1.1.0 # via trio packaging==21.3 @@ -212,14 +226,19 @@ redis==4.3.3 # via testcontainers requests==2.27.1 # via + # azure-core # codecov # docker # docker-compose # google-api-core + # msrest # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx +requests-oauthlib==1.3.1 + # via msrest requests-toolbelt==0.9.1 # via python-arango rsa==4.8 @@ -232,10 +251,12 @@ selenium==4.2.0 # via testcontainers six==1.16.0 # via + # azure-core # dockerpty # ecdsa # google-auth # grpcio + # isodate # jsonschema # paramiko # websocket-client @@ -273,6 +294,8 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium +typing-extensions==4.3.0 + # via azure-core tzdata==2022.1 # via pytz-deprecation-shim tzlocal==4.2 diff --git a/setup.py b/setup.py index 5eb485455..fe8428993 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,7 @@ 'clickhouse': ['clickhouse-driver'], 'keycloak': ['python-keycloak'], 'arangodb': ['python-arango'], + 'azurite': ['azure-storage-blob'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py new file mode 100644 index 000000000..7fed01741 --- /dev/null +++ b/testcontainers/azurite.py @@ -0,0 +1,110 @@ +# +# 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 os +import socket + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class AzuriteContainer(DockerContainer): + """ + Azurite container. + + Example + ------- + :: + + with AzuriteContainer() as azurite: + connection_string = azurite.get_connection_string() + BlobServiceClient.from_connection_string( + connection_string, + api_version="2019-12-12" + ) + """ + + _AZURITE_ACCOUNT_NAME = os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") + _AZURITE_ACCOUNT_KEY = os.environ.get("AZURITE_ACCOUNT_KEY", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDX" + "J1OUzFT50uSRZ6IFsuFq2UVErCz4I6" + "tq/K1SZFPTOtr/KBHBeksoGMGw==") + + _BLOB_SERVICE_PORT = 10_000 + _QUEUE_SERVICE_PORT = 10_001 + _TABLE_SERVICE_PORT = 10_002 + + def __init__( + self, + image="mcr.microsoft.com/azure-storage/azurite:latest", + ports_to_expose=None, + **kwargs + ): + """ Constructs an AzuriteContainer. + + Parameters + ---------- + image: str + Expects an image with tag. + ports_to_expose: List[int] + Expects a list with port numbers to expose. + kwargs + """ + super().__init__(image=image, **kwargs) + + if ports_to_expose is None: + ports_to_expose = [ + self._BLOB_SERVICE_PORT, + self._QUEUE_SERVICE_PORT, + self._TABLE_SERVICE_PORT + ] + + if len(ports_to_expose) == 0: + raise ValueError("Expected a list with port numbers to expose") + + self.ports_to_expose = ports_to_expose + + self.with_exposed_ports(*ports_to_expose) + self.with_env("AZURITE_ACCOUNTS", f"{self._AZURITE_ACCOUNT_NAME}:{self._AZURITE_ACCOUNT_KEY}") + + def get_connection_string(self): + host_ip = self.get_container_host_ip() + connection_string = f"DefaultEndpointsProtocol=http;" \ + f"AccountName={self._AZURITE_ACCOUNT_NAME};" \ + f"AccountKey={self._AZURITE_ACCOUNT_KEY};" + + if self._BLOB_SERVICE_PORT in self.ports_to_expose: + connection_string += f"BlobEndpoint=http://{host_ip}:" \ + f"{self.get_exposed_port(self._BLOB_SERVICE_PORT)}" \ + f"/{self._AZURITE_ACCOUNT_NAME};" + + if self._QUEUE_SERVICE_PORT in self.ports_to_expose: + connection_string += f"QueueEndpoint=http://{host_ip}:" \ + f"{self.get_exposed_port(self._QUEUE_SERVICE_PORT)}" \ + f"/{self._AZURITE_ACCOUNT_NAME};" + + if self._TABLE_SERVICE_PORT in self.ports_to_expose: + connection_string += f"TableEndpoint=http://{host_ip}:" \ + f"{self.get_exposed_port(self._TABLE_SERVICE_PORT)}" \ + f"/{self._AZURITE_ACCOUNT_NAME};" + + return connection_string + + def start(self): + super().start() + self._connect() + return self + + @wait_container_is_ready(OSError) + def _connect(self): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((self.get_container_host_ip(), + int(self.get_exposed_port(self.ports_to_expose[0])))) diff --git a/tests/test_azurite.py b/tests/test_azurite.py new file mode 100644 index 000000000..18b6cd3f5 --- /dev/null +++ b/tests/test_azurite.py @@ -0,0 +1,13 @@ +from testcontainers.azurite import AzuriteContainer +from azure.storage.blob import BlobServiceClient + + +def test_docker_run_azurite(): + with AzuriteContainer() as azurite_container: + blob_service_client = BlobServiceClient.from_connection_string( + azurite_container.get_connection_string(), + api_version="2019-12-12" + ) + + blob_service_client.create_container("test-container") + From 7455ca36c57180b7d8481d6c5f9ef526e5c2207b Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 23 Aug 2022 16:27:41 +0300 Subject: [PATCH 059/425] Run autopep8 to format the code --- testcontainers/arangodb.py | 1 + testcontainers/compose.py | 1 + testcontainers/elasticsearch.py | 1 + testcontainers/google/pubsub.py | 1 + testcontainers/keycloak.py | 12 ++++++------ testcontainers/mssql.py | 1 + testcontainers/mysql.py | 1 + testcontainers/oracle.py | 1 + testcontainers/selenium.py | 1 + tests/test_nginx.py | 4 ++-- 10 files changed, 16 insertions(+), 8 deletions(-) diff --git a/testcontainers/arangodb.py b/testcontainers/arangodb.py index ec4f005ea..0ab82ef4a 100644 --- a/testcontainers/arangodb.py +++ b/testcontainers/arangodb.py @@ -28,6 +28,7 @@ class ArangoDbContainer(DbContainer): # Create a new database named "test". sys_db.create_database("test") """ + def __init__(self, image: str = "arangodb:latest", port_to_expose: int = 8529, diff --git a/testcontainers/compose.py b/testcontainers/compose.py index 1cac63af8..b5961aa81 100644 --- a/testcontainers/compose.py +++ b/testcontainers/compose.py @@ -67,6 +67,7 @@ class DockerCompose(object): expose: - "5555" """ + def __init__( self, filepath, diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index 6b6f24b35..458d4e448 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -67,6 +67,7 @@ class ElasticSearchContainer(DockerContainer): with ElasticSearchContainer() as es: connection_url = es.get_url() """ + def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs): super(ElasticSearchContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose diff --git a/testcontainers/google/pubsub.py b/testcontainers/google/pubsub.py index 0400e3668..e4074dccd 100644 --- a/testcontainers/google/pubsub.py +++ b/testcontainers/google/pubsub.py @@ -33,6 +33,7 @@ def test_docker_run_pubsub(): topic_path = publisher.topic_path(pubsub.project, "my-topic") topic = publisher.create_topic(topic_path) """ + def __init__(self, image="google/cloud-sdk:latest", project="test-project", port=8432, **kwargs): super(PubSubContainer, self).__init__(image=image, **kwargs) diff --git a/testcontainers/keycloak.py b/testcontainers/keycloak.py index 5f03e238c..05e876d0d 100644 --- a/testcontainers/keycloak.py +++ b/testcontainers/keycloak.py @@ -61,12 +61,12 @@ def start(self): def get_client(self, **kwargs): default_kwargs = dict( - server_url="{}/auth/".format(self.get_url()), - username=self.KEYCLOAK_USER, - password=self.KEYCLOAK_PASSWORD, - realm_name="master", - verify=True, - ) + server_url="{}/auth/".format(self.get_url()), + username=self.KEYCLOAK_USER, + password=self.KEYCLOAK_PASSWORD, + realm_name="master", + verify=True, + ) kwargs = { **default_kwargs, **kwargs diff --git a/testcontainers/mssql.py b/testcontainers/mssql.py index f41fa57b4..e3a10276b 100644 --- a/testcontainers/mssql.py +++ b/testcontainers/mssql.py @@ -20,6 +20,7 @@ class SqlServerContainer(DbContainer): Requires `ODBC Driver 17 for SQL Server `_. """ + def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA", password=None, port=1433, dbname="tempdb", dialect='mssql+pymssql', **kwargs): super(SqlServerContainer, self).__init__(image, **kwargs) diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 8570589f8..ca9c3790e 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -33,6 +33,7 @@ class MySqlContainer(DbContainer): result = e.execute("select version()") version, = result.fetchone() """ + def __init__(self, image="mysql:latest", **kwargs): super(MySqlContainer, self).__init__(image) self.port_to_expose = 3306 diff --git a/testcontainers/oracle.py b/testcontainers/oracle.py index b8a426445..341aaa108 100644 --- a/testcontainers/oracle.py +++ b/testcontainers/oracle.py @@ -13,6 +13,7 @@ class OracleDbContainer(DbContainer): e = sqlalchemy.create_engine(oracle.get_connection_url()) result = e.execute("select 1 from dual") """ + def __init__(self, image="wnameless/oracle-xe-11g-r2:latest", **kwargs): super(OracleDbContainer, self).__init__(image=image, **kwargs) self.container_port = 1521 diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index f9b96753e..34843e76e 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -49,6 +49,7 @@ class BrowserWebDriverContainer(DockerContainer): You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ + def __init__(self, capabilities, image=None, **kwargs): self.capabilities = capabilities self.image = image or get_image_name(capabilities) diff --git a/tests/test_nginx.py b/tests/test_nginx.py index e2af43932..cd4d68ca7 100644 --- a/tests/test_nginx.py +++ b/tests/test_nginx.py @@ -10,5 +10,5 @@ def test_docker_run_nginx(): url = "http://{}:{}/".format(nginx.get_container_host_ip(), nginx.get_exposed_port(port)) r = requests.get(url) - assert(r.status_code == 200) - assert('Welcome to nginx!' in r.text) + assert (r.status_code == 200) + assert ('Welcome to nginx!' in r.text) From 45586c6adcdb7b483300e44cfb3827bdaccff9c9 Mon Sep 17 00:00:00 2001 From: Pepijn Date: Wed, 24 Aug 2022 22:28:15 +0200 Subject: [PATCH 060/425] Applied autopep8 to new code --- tests/test_azurite.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_azurite.py b/tests/test_azurite.py index 18b6cd3f5..5c92e48ed 100644 --- a/tests/test_azurite.py +++ b/tests/test_azurite.py @@ -4,10 +4,9 @@ def test_docker_run_azurite(): with AzuriteContainer() as azurite_container: - blob_service_client = BlobServiceClient.from_connection_string( - azurite_container.get_connection_string(), - api_version="2019-12-12" - ) - - blob_service_client.create_container("test-container") + blob_service_client = BlobServiceClient.from_connection_string( + azurite_container.get_connection_string(), + api_version="2019-12-12" + ) + blob_service_client.create_container("test-container") From 86b696655a2475aa524cfeda2cf10930fe8743a2 Mon Sep 17 00:00:00 2001 From: Pepijn Date: Fri, 26 Aug 2022 11:32:48 +0200 Subject: [PATCH 061/425] fixed styling issues --- testcontainers/azurite.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py index 7fed01741..27acec87a 100644 --- a/testcontainers/azurite.py +++ b/testcontainers/azurite.py @@ -35,8 +35,8 @@ class AzuriteContainer(DockerContainer): _AZURITE_ACCOUNT_NAME = os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") _AZURITE_ACCOUNT_KEY = os.environ.get("AZURITE_ACCOUNT_KEY", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDX" - "J1OUzFT50uSRZ6IFsuFq2UVErCz4I6" - "tq/K1SZFPTOtr/KBHBeksoGMGw==") + "J1OUzFT50uSRZ6IFsuFq2UVErCz4I6" + "tq/K1SZFPTOtr/KBHBeksoGMGw==") _BLOB_SERVICE_PORT = 10_000 _QUEUE_SERVICE_PORT = 10_001 @@ -73,7 +73,8 @@ def __init__( self.ports_to_expose = ports_to_expose self.with_exposed_ports(*ports_to_expose) - self.with_env("AZURITE_ACCOUNTS", f"{self._AZURITE_ACCOUNT_NAME}:{self._AZURITE_ACCOUNT_KEY}") + self.with_env("AZURITE_ACCOUNTS", + f"{self._AZURITE_ACCOUNT_NAME}:{self._AZURITE_ACCOUNT_KEY}") def get_connection_string(self): host_ip = self.get_container_host_ip() From 8a46c63997e9edc2a0f89decd6a259b5cad73406 Mon Sep 17 00:00:00 2001 From: sfavre Date: Tue, 30 Aug 2022 17:43:28 +0200 Subject: [PATCH 062/425] Make kafka testcontainer compatible with confluentinc/cp-kafka:7.1.3 --- testcontainers/kafka.py | 2 +- tests/test_kafka.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py index be5cadc1a..433c79975 100644 --- a/testcontainers/kafka.py +++ b/testcontainers/kafka.py @@ -39,7 +39,7 @@ def get_bootstrap_server(self): def _connect(self): bootstrap_server = self.get_bootstrap_server() consumer = KafkaConsumer(group_id='test', bootstrap_servers=[bootstrap_server]) - if not consumer.topics(): + if not consumer.bootstrap_connected(): raise KafkaError("Unable to connect with kafka container!") def tc_start(self): diff --git a/tests/test_kafka.py b/tests/test_kafka.py index 4efda4377..ca6b6710a 100644 --- a/tests/test_kafka.py +++ b/tests/test_kafka.py @@ -13,6 +13,11 @@ def test_kafka_producer_consumer_custom_port(): produce_and_consume_kafka_message(container) +def test_kafka_confluent_7_1_3(): + with KafkaContainer(image='confluentinc/cp-kafka:7.1.3') as container: + produce_and_consume_kafka_message(container) + + def produce_and_consume_kafka_message(container): topic = 'test-topic' bootstrap_server = container.get_bootstrap_server() From 5eaf9be06719daab74a2d5d251d8e79bd5a9fb16 Mon Sep 17 00:00:00 2001 From: Pepijn Date: Tue, 30 Aug 2022 20:49:20 +0200 Subject: [PATCH 063/425] Removed ports_to_expose attribute and start using the ports attribute for azurite container logic --- testcontainers/azurite.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py index 27acec87a..748caadd0 100644 --- a/testcontainers/azurite.py +++ b/testcontainers/azurite.py @@ -70,8 +70,6 @@ def __init__( if len(ports_to_expose) == 0: raise ValueError("Expected a list with port numbers to expose") - self.ports_to_expose = ports_to_expose - self.with_exposed_ports(*ports_to_expose) self.with_env("AZURITE_ACCOUNTS", f"{self._AZURITE_ACCOUNT_NAME}:{self._AZURITE_ACCOUNT_KEY}") @@ -82,17 +80,17 @@ def get_connection_string(self): f"AccountName={self._AZURITE_ACCOUNT_NAME};" \ f"AccountKey={self._AZURITE_ACCOUNT_KEY};" - if self._BLOB_SERVICE_PORT in self.ports_to_expose: + if self._BLOB_SERVICE_PORT in self.ports: connection_string += f"BlobEndpoint=http://{host_ip}:" \ f"{self.get_exposed_port(self._BLOB_SERVICE_PORT)}" \ f"/{self._AZURITE_ACCOUNT_NAME};" - if self._QUEUE_SERVICE_PORT in self.ports_to_expose: + if self._QUEUE_SERVICE_PORT in self.ports: connection_string += f"QueueEndpoint=http://{host_ip}:" \ f"{self.get_exposed_port(self._QUEUE_SERVICE_PORT)}" \ f"/{self._AZURITE_ACCOUNT_NAME};" - if self._TABLE_SERVICE_PORT in self.ports_to_expose: + if self._TABLE_SERVICE_PORT in self.ports: connection_string += f"TableEndpoint=http://{host_ip}:" \ f"{self.get_exposed_port(self._TABLE_SERVICE_PORT)}" \ f"/{self._AZURITE_ACCOUNT_NAME};" @@ -108,4 +106,4 @@ def start(self): def _connect(self): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((self.get_container_host_ip(), - int(self.get_exposed_port(self.ports_to_expose[0])))) + int(self.get_exposed_port(next(iter(self.ports)))))) From f89ca9801c968f3cb2a552c27e004e23a90d4cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Ribeiro=20Garcia?= Date: Tue, 13 Sep 2022 22:41:16 +0100 Subject: [PATCH 064/425] Added port_to_expose argument on __init__ to configure MongoDB with different exposed ports --- testcontainers/mongodb.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/testcontainers/mongodb.py b/testcontainers/mongodb.py index aedb3eb81..0b278d405 100644 --- a/testcontainers/mongodb.py +++ b/testcontainers/mongodb.py @@ -50,10 +50,13 @@ class MongoDbContainer(DbContainer): MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") MONGO_DB = os.environ.get("MONGO_DB", "test") - def __init__(self, image="mongo:latest", **kwargs): + def __init__(self, + image: str = "mongo:latest", + port_to_expose: int = 27017, + **kwargs): super(MongoDbContainer, self).__init__(image=image, **kwargs) self.command = "mongo" - self.port_to_expose = 27017 + self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) def _configure(self): From f77321e13c4ac3a35e0604b5ccf0d4dabbf195d5 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 13 Sep 2022 18:20:37 -0400 Subject: [PATCH 065/425] Run tests on `ubuntu-latest`. --- .github/workflows/main.yml | 2 +- .github/workflows/pypi-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d749a65e2..a88eb31c8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: - webdriver.py - keycloak.py - arangodb.py - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup python ${{ matrix.python-version }} diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 5339e0260..083bcc27f 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest env: python-version: 3.8 steps: From 4786acc4dbaacc4aa27fd5c71e30ffea8443e4b3 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 21 Sep 2022 19:27:14 -0400 Subject: [PATCH 066/425] Bump requirements. --- requirements/3.10.txt | 105 +++++++------- requirements/3.6.txt | 316 ------------------------------------------ requirements/3.7.txt | 111 ++++++++------- requirements/3.8.txt | 109 ++++++++------- requirements/3.9.txt | 109 ++++++++------- 5 files changed, 215 insertions(+), 535 deletions(-) delete mode 100644 requirements/3.6.txt diff --git a/requirements/3.10.txt b/requirements/3.10.txt index f2ee01781..2f3583989 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -16,41 +16,40 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==21.4.0 +attrs==22.1.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.25.0 +azure-core==1.25.1 # via # azure-storage-blob # msrest azure-storage-blob==12.13.1 # via testcontainers -babel==2.10.1 +babel==2.10.3 # via sphinx -bcrypt==3.2.2 +bcrypt==4.0.0 # via paramiko cachetools==5.2.0 # via google-auth -certifi==2022.5.18.1 +certifi==2022.9.14 # via # msrest # requests - # urllib3 -cffi==1.15.0 + # selenium +cffi==1.15.1 # via - # bcrypt # cryptography # pynacl -charset-normalizer==2.0.12 +charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.3 +clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.1 +coverage[toml]==6.4.4 # via # codecov # pytest-cov @@ -59,8 +58,6 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko - # pyopenssl - # urllib3 cx-oracle==8.3.0 # via testcontainers deprecated==1.2.13 @@ -69,7 +66,7 @@ deprecation==2.1.0 # via testcontainers distro==1.7.0 # via docker-compose -docker[ssh]==5.0.3 +docker[ssh]==6.0.0 # via # docker-compose # testcontainers @@ -79,45 +76,44 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.18.1 +docutils==0.19 # via sphinx -ecdsa==0.17.0 +ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.8.1 +google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.6.6 +google-auth==2.11.1 # via google-api-core -google-cloud-pubsub==1.7.1 +google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.2 +googleapis-common-protos[grpc]==1.56.4 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.2 +greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.3 +grpcio==1.48.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.3 +grpcio-status==1.48.1 # via google-api-core h11==0.13.0 # via wsproto -idna==3.3 +idna==3.4 # via # requests # trio - # urllib3 -imagesize==1.3.0 +imagesize==1.4.1 # via sphinx iniconfig==1.1.1 # via pytest @@ -135,15 +131,16 @@ mccabe==0.6.1 # via flake8 msrest==0.7.1 # via azure-storage-blob -neo4j==4.4.4 +neo4j==5.0.1 # via testcontainers -oauthlib==3.2.0 +oauthlib==3.2.1 # via requests-oauthlib -outcome==1.1.0 +outcome==1.2.0 # via trio packaging==21.3 # via # deprecation + # docker # pytest # redis # sphinx @@ -151,13 +148,14 @@ paramiko==2.11.0 # via docker pg8000==1.29.1 # via -r requirements.in -pika==1.2.1 +pika==1.3.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.1 +protobuf==3.20.2 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.3 @@ -177,11 +175,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.12.0 +pygments==2.13.0 # via sphinx -pyjwt==2.4.0 +pyjwt==2.5.0 # via python-arango -pymongo==4.1.1 +pymongo==4.2.0 # via testcontainers pymssql==2.2.5 # via testcontainers @@ -189,29 +187,27 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyopenssl==22.0.0 - # via urllib3 pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.2 +pytest==7.1.3 # via # -r requirements.in # pytest-cov pytest-cov==3.0.0 # via -r requirements.in -python-arango==7.3.4 +python-arango==7.4.1 # via testcontainers -python-dotenv==0.20.0 +python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==1.5.0 +python-keycloak==2.5.0 # via testcontainers -pytz==2022.1 +pytz==2022.2.1 # via # babel # clickhouse-driver @@ -220,9 +216,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.3 +redis==4.3.4 # via testcontainers -requests==2.27.1 +requests==2.28.1 # via # azure-core # codecov @@ -238,14 +234,16 @@ requests==2.27.1 requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 - # via python-arango -rsa==4.8 + # via + # python-arango + # python-keycloak +rsa==4.9 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.2.0 +selenium==4.4.3 # via testcontainers six==1.16.0 # via @@ -258,13 +256,13 @@ six==1.16.0 # jsonschema # paramiko # websocket-client -sniffio==1.2.0 +sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.0.1 +sphinx==5.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -278,7 +276,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.37 +sqlalchemy==1.4.41 # via testcontainers texttable==1.6.4 # via docker-compose @@ -286,7 +284,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.20.0 +trio==0.21.0 # via # selenium # trio-websocket @@ -294,12 +292,13 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.3.0 # via azure-core -tzdata==2022.1 +tzdata==2022.2 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[secure,socks]==1.26.9 +urllib3[socks]==1.26.12 # via + # docker # python-arango # python-keycloak # requests @@ -312,7 +311,7 @@ wrapt==1.14.1 # via # deprecated # testcontainers -wsproto==1.1.0 +wsproto==1.2.0 # via trio-websocket # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.6.txt b/requirements/3.6.txt deleted file mode 100644 index d7ee5c3ff..000000000 --- a/requirements/3.6.txt +++ /dev/null @@ -1,316 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.6 -# To update, run: -# -# pip-compile --output-file=requirements/3.6.txt requirements.in -# --e file:. - # via -r requirements.in -alabaster==0.7.12 - # via sphinx -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.2 - # via redis -attrs==21.4.0 - # via - # jsonschema - # pytest -azure-core==1.24.2 - # via - # azure-storage-blob - # msrest -azure-storage-blob==12.13.1 - # via testcontainers -babel==2.10.1 - # via sphinx -backports.zoneinfo==0.2.1 - # via - # pytz-deprecation-shim - # tzlocal -bcrypt==3.2.2 - # via paramiko -cached-property==1.5.2 - # via docker-compose -cachetools==4.2.4 - # via google-auth -certifi==2021.10.8 - # via - # msrest - # requests -cffi==1.15.0 - # via - # bcrypt - # cryptography - # pynacl -charset-normalizer==2.0.12 - # via requests -clickhouse-driver==0.2.3 - # via testcontainers -codecov==2.1.12 - # via -r requirements.in -coverage[toml]==6.2 - # via - # codecov - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # paramiko -cx-oracle==8.3.0 - # via testcontainers -dataclasses==0.8 - # via python-arango -deprecated==1.2.13 - # via redis -deprecation==2.1.0 - # via testcontainers -distro==1.7.0 - # via docker-compose -docker[ssh]==5.0.3 - # via - # docker-compose - # testcontainers -docker-compose==1.29.2 - # via testcontainers -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose -docutils==0.17.1 - # via sphinx -ecdsa==0.17.0 - # via python-jose -entrypoints==0.3 - # via flake8 -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.7.3 - # via google-cloud-pubsub -google-auth==2.6.6 - # via google-api-core -google-cloud-pubsub==1.7.1 - # via testcontainers -googleapis-common-protos[grpc]==1.56.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==1.1.2 - # via sqlalchemy -grpc-google-iam-v1==0.12.4 - # via google-cloud-pubsub -grpcio==1.46.0 - # via - # google-api-core - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.46.0 - # via google-api-core -idna==3.3 - # via requests -imagesize==1.3.0 - # via sphinx -importlib-metadata==4.8.3 - # via - # jsonschema - # pluggy - # pytest - # redis - # sphinx - # sqlalchemy -importlib-resources==5.4.0 - # via backports.zoneinfo -iniconfig==1.1.1 - # via pytest -isodate==0.6.1 - # via msrest -jinja2==3.0.3 - # via sphinx -jsonschema==3.2.0 - # via docker-compose -kafka-python==2.0.2 - # via testcontainers -markupsafe==2.0.1 - # via jinja2 -mccabe==0.6.1 - # via flake8 -msrest==0.7.1 - # via azure-storage-blob -neo4j==4.4.3 - # via testcontainers -oauthlib==3.2.0 - # via requests-oauthlib -packaging==21.3 - # via - # deprecation - # pytest - # redis - # setuptools-scm - # sphinx -paramiko==2.10.4 - # via docker -pg8000==1.26.0 - # via -r requirements.in -pika==1.2.1 - # via testcontainers -pluggy==1.0.0 - # via pytest -protobuf==3.19.4 - # via - # google-api-core - # googleapis-common-protos - # grpcio-status -psycopg2-binary==2.9.3 - # via testcontainers -py==1.11.0 - # via pytest -pyasn1==0.4.8 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.2.8 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pyflakes==2.1.1 - # via flake8 -pygments==2.12.0 - # via sphinx -pyjwt==2.4.0 - # via python-arango -pymongo==4.1.1 - # via testcontainers -pymssql==2.2.5 - # via testcontainers -pymysql==1.0.2 - # via testcontainers -pynacl==1.5.0 - # via paramiko -pyparsing==3.0.8 - # via packaging -pyrsistent==0.18.0 - # via jsonschema -pytest==7.0.1 - # via - # -r requirements.in - # pytest-cov -pytest-cov==3.0.0 - # via -r requirements.in -python-arango==7.3.1 - # via testcontainers -python-dotenv==0.20.0 - # via docker-compose -python-jose==3.3.0 - # via python-keycloak -python-keycloak==0.27.0 - # via testcontainers -pytz==2022.1 - # via - # babel - # clickhouse-driver - # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal -pyyaml==5.4.1 - # via docker-compose -redis==4.2.2 - # via testcontainers -requests==2.27.1 - # via - # azure-core - # codecov - # docker - # docker-compose - # google-api-core - # msrest - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx -requests-oauthlib==1.3.1 - # via msrest -requests-toolbelt==0.9.1 - # via python-arango -rsa==4.8 - # via - # google-auth - # python-jose -scramp==1.4.1 - # via pg8000 -selenium==3.141.0 - # via testcontainers -setuptools-scm[toml]==6.4.2 - # via python-arango -six==1.16.0 - # via - # azure-core - # dockerpty - # ecdsa - # google-auth - # grpcio - # isodate - # jsonschema - # paramiko - # websocket-client -snowballstemmer==2.2.0 - # via sphinx -sphinx==4.5.0 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.0 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx -sqlalchemy==1.4.36 - # via testcontainers -texttable==1.6.4 - # via docker-compose -tomli==1.2.3 - # via - # coverage - # pytest - # setuptools-scm -typing-extensions==4.1.1 - # via - # async-timeout - # azure-core - # importlib-metadata - # redis -tzdata==2022.1 - # via pytz-deprecation-shim -tzlocal==4.2 - # via clickhouse-driver -urllib3==1.26.9 - # via - # python-arango - # requests - # selenium -websocket-client==0.59.0 - # via - # docker - # docker-compose -wrapt==1.14.1 - # via - # deprecated - # testcontainers -zipp==3.6.0 - # via - # importlib-metadata - # importlib-resources - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/3.7.txt b/requirements/3.7.txt index cadea609b..9d778343e 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -16,47 +16,46 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==21.4.0 +attrs==22.1.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.25.0 +azure-core==1.25.1 # via # azure-storage-blob # msrest azure-storage-blob==12.13.1 # via testcontainers -babel==2.10.1 +babel==2.10.3 # via sphinx backports-zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==3.2.2 +bcrypt==4.0.0 # via paramiko cached-property==1.5.2 # via docker-compose cachetools==5.2.0 # via google-auth -certifi==2022.5.18.1 +certifi==2022.9.14 # via # msrest # requests - # urllib3 -cffi==1.15.0 + # selenium +cffi==1.15.1 # via - # bcrypt # cryptography # pynacl -charset-normalizer==2.0.12 +charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.3 +clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.1 +coverage[toml]==6.4.4 # via # codecov # pytest-cov @@ -65,8 +64,6 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko - # pyopenssl - # urllib3 cx-oracle==8.3.0 # via testcontainers deprecated==1.2.13 @@ -75,7 +72,7 @@ deprecation==2.1.0 # via testcontainers distro==1.7.0 # via docker-compose -docker[ssh]==5.0.3 +docker[ssh]==6.0.0 # via # docker-compose # testcontainers @@ -85,47 +82,46 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.18.1 +docutils==0.19 # via sphinx -ecdsa==0.17.0 +ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.8.1 +google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.6.6 +google-auth==2.11.1 # via google-api-core -google-cloud-pubsub==1.7.1 +google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.2 +googleapis-common-protos[grpc]==1.56.4 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.2 +greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.3 +grpcio==1.48.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.3 +grpcio-status==1.48.1 # via google-api-core h11==0.13.0 # via wsproto -idna==3.3 +idna==3.4 # via # requests # trio - # urllib3 -imagesize==1.3.0 +imagesize==1.4.1 # via sphinx -importlib-metadata==4.11.4 +importlib-metadata==4.12.0 # via # jsonschema # pg8000 @@ -150,15 +146,16 @@ mccabe==0.6.1 # via flake8 msrest==0.7.1 # via azure-storage-blob -neo4j==4.4.4 +neo4j==5.0.1 # via testcontainers -oauthlib==3.2.0 +oauthlib==3.2.1 # via requests-oauthlib -outcome==1.1.0 +outcome==1.2.0 # via trio packaging==21.3 # via # deprecation + # docker # pytest # redis # sphinx @@ -166,13 +163,14 @@ paramiko==2.11.0 # via docker pg8000==1.29.1 # via -r requirements.in -pika==1.2.1 +pika==1.3.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.1 +protobuf==3.20.2 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.3 @@ -192,11 +190,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.12.0 +pygments==2.13.0 # via sphinx -pyjwt==2.4.0 +pyjwt==2.5.0 # via python-arango -pymongo==4.1.1 +pymongo==4.2.0 # via testcontainers pymssql==2.2.5 # via testcontainers @@ -204,29 +202,27 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyopenssl==22.0.0 - # via urllib3 pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.2 +pytest==7.1.3 # via # -r requirements.in # pytest-cov pytest-cov==3.0.0 # via -r requirements.in -python-arango==7.3.4 +python-arango==7.4.1 # via testcontainers -python-dotenv==0.20.0 +python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==1.5.0 +python-keycloak==2.5.0 # via testcontainers -pytz==2022.1 +pytz==2022.2.1 # via # babel # clickhouse-driver @@ -235,9 +231,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.3 +redis==4.3.4 # via testcontainers -requests==2.27.1 +requests==2.28.1 # via # azure-core # codecov @@ -253,14 +249,16 @@ requests==2.27.1 requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 - # via python-arango -rsa==4.8 + # via + # python-arango + # python-keycloak +rsa==4.9 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.2.0 +selenium==4.4.3 # via testcontainers six==1.16.0 # via @@ -273,13 +271,13 @@ six==1.16.0 # jsonschema # paramiko # websocket-client -sniffio==1.2.0 +sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.0.1 +sphinx==5.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -293,7 +291,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.37 +sqlalchemy==1.4.41 # via testcontainers texttable==1.6.4 # via docker-compose @@ -301,25 +299,26 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.20.0 +trio==0.21.0 # via # selenium # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.2.0 +typing-extensions==4.3.0 # via # async-timeout # azure-core # h11 # importlib-metadata # redis -tzdata==2022.1 +tzdata==2022.2 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[secure,socks]==1.26.9 +urllib3[socks]==1.26.12 # via + # docker # python-arango # python-keycloak # requests @@ -332,9 +331,9 @@ wrapt==1.14.1 # via # deprecated # testcontainers -wsproto==1.1.0 +wsproto==1.2.0 # via trio-websocket -zipp==3.8.0 +zipp==3.8.1 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 4d9cd0942..3f3db8b8a 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -16,45 +16,44 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==21.4.0 +attrs==22.1.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.25.0 +azure-core==1.25.1 # via # azure-storage-blob # msrest azure-storage-blob==12.13.1 # via testcontainers -babel==2.10.1 +babel==2.10.3 # via sphinx backports-zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==3.2.2 +bcrypt==4.0.0 # via paramiko cachetools==5.2.0 # via google-auth -certifi==2022.5.18.1 +certifi==2022.9.14 # via # msrest # requests - # urllib3 -cffi==1.15.0 + # selenium +cffi==1.15.1 # via - # bcrypt # cryptography # pynacl -charset-normalizer==2.0.12 +charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.3 +clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.1 +coverage[toml]==6.4.4 # via # codecov # pytest-cov @@ -63,8 +62,6 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko - # pyopenssl - # urllib3 cx-oracle==8.3.0 # via testcontainers deprecated==1.2.13 @@ -73,7 +70,7 @@ deprecation==2.1.0 # via testcontainers distro==1.7.0 # via docker-compose -docker[ssh]==5.0.3 +docker[ssh]==6.0.0 # via # docker-compose # testcontainers @@ -83,47 +80,46 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.18.1 +docutils==0.19 # via sphinx -ecdsa==0.17.0 +ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.8.1 +google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.6.6 +google-auth==2.11.1 # via google-api-core -google-cloud-pubsub==1.7.1 +google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.2 +googleapis-common-protos[grpc]==1.56.4 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.2 +greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.3 +grpcio==1.48.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.3 +grpcio-status==1.48.1 # via google-api-core h11==0.13.0 # via wsproto -idna==3.3 +idna==3.4 # via # requests # trio - # urllib3 -imagesize==1.3.0 +imagesize==1.4.1 # via sphinx -importlib-metadata==4.11.4 +importlib-metadata==4.12.0 # via sphinx iniconfig==1.1.1 # via pytest @@ -141,15 +137,16 @@ mccabe==0.6.1 # via flake8 msrest==0.7.1 # via azure-storage-blob -neo4j==4.4.4 +neo4j==5.0.1 # via testcontainers -oauthlib==3.2.0 +oauthlib==3.2.1 # via requests-oauthlib -outcome==1.1.0 +outcome==1.2.0 # via trio packaging==21.3 # via # deprecation + # docker # pytest # redis # sphinx @@ -157,13 +154,14 @@ paramiko==2.11.0 # via docker pg8000==1.29.1 # via -r requirements.in -pika==1.2.1 +pika==1.3.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.1 +protobuf==3.20.2 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.3 @@ -183,11 +181,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.12.0 +pygments==2.13.0 # via sphinx -pyjwt==2.4.0 +pyjwt==2.5.0 # via python-arango -pymongo==4.1.1 +pymongo==4.2.0 # via testcontainers pymssql==2.2.5 # via testcontainers @@ -195,29 +193,27 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyopenssl==22.0.0 - # via urllib3 pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.2 +pytest==7.1.3 # via # -r requirements.in # pytest-cov pytest-cov==3.0.0 # via -r requirements.in -python-arango==7.3.4 +python-arango==7.4.1 # via testcontainers -python-dotenv==0.20.0 +python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==1.5.0 +python-keycloak==2.5.0 # via testcontainers -pytz==2022.1 +pytz==2022.2.1 # via # babel # clickhouse-driver @@ -226,9 +222,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.3 +redis==4.3.4 # via testcontainers -requests==2.27.1 +requests==2.28.1 # via # azure-core # codecov @@ -244,14 +240,16 @@ requests==2.27.1 requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 - # via python-arango -rsa==4.8 + # via + # python-arango + # python-keycloak +rsa==4.9 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.2.0 +selenium==4.4.3 # via testcontainers six==1.16.0 # via @@ -264,13 +262,13 @@ six==1.16.0 # jsonschema # paramiko # websocket-client -sniffio==1.2.0 +sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.0.1 +sphinx==5.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -284,7 +282,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.37 +sqlalchemy==1.4.41 # via testcontainers texttable==1.6.4 # via docker-compose @@ -292,7 +290,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.20.0 +trio==0.21.0 # via # selenium # trio-websocket @@ -300,12 +298,13 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.3.0 # via azure-core -tzdata==2022.1 +tzdata==2022.2 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[secure,socks]==1.26.9 +urllib3[socks]==1.26.12 # via + # docker # python-arango # python-keycloak # requests @@ -318,9 +317,9 @@ wrapt==1.14.1 # via # deprecated # testcontainers -wsproto==1.1.0 +wsproto==1.2.0 # via trio-websocket -zipp==3.8.0 +zipp==3.8.1 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.9.txt b/requirements/3.9.txt index ac73ba552..2ecfcd63c 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -16,41 +16,40 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==21.4.0 +attrs==22.1.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.25.0 +azure-core==1.25.1 # via # azure-storage-blob # msrest azure-storage-blob==12.13.1 # via testcontainers -babel==2.10.1 +babel==2.10.3 # via sphinx -bcrypt==3.2.2 +bcrypt==4.0.0 # via paramiko cachetools==5.2.0 # via google-auth -certifi==2022.5.18.1 +certifi==2022.9.14 # via # msrest # requests - # urllib3 -cffi==1.15.0 + # selenium +cffi==1.15.1 # via - # bcrypt # cryptography # pynacl -charset-normalizer==2.0.12 +charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.3 +clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.1 +coverage[toml]==6.4.4 # via # codecov # pytest-cov @@ -59,8 +58,6 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko - # pyopenssl - # urllib3 cx-oracle==8.3.0 # via testcontainers deprecated==1.2.13 @@ -69,7 +66,7 @@ deprecation==2.1.0 # via testcontainers distro==1.7.0 # via docker-compose -docker[ssh]==5.0.3 +docker[ssh]==6.0.0 # via # docker-compose # testcontainers @@ -79,47 +76,46 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.18.1 +docutils==0.19 # via sphinx -ecdsa==0.17.0 +ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.8.1 +google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.6.6 +google-auth==2.11.1 # via google-api-core -google-cloud-pubsub==1.7.1 +google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.2 +googleapis-common-protos[grpc]==1.56.4 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.2 +greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.46.3 +grpcio==1.48.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.46.3 +grpcio-status==1.48.1 # via google-api-core h11==0.13.0 # via wsproto -idna==3.3 +idna==3.4 # via # requests # trio - # urllib3 -imagesize==1.3.0 +imagesize==1.4.1 # via sphinx -importlib-metadata==4.11.4 +importlib-metadata==4.12.0 # via sphinx iniconfig==1.1.1 # via pytest @@ -137,15 +133,16 @@ mccabe==0.6.1 # via flake8 msrest==0.7.1 # via azure-storage-blob -neo4j==4.4.4 +neo4j==5.0.1 # via testcontainers -oauthlib==3.2.0 +oauthlib==3.2.1 # via requests-oauthlib -outcome==1.1.0 +outcome==1.2.0 # via trio packaging==21.3 # via # deprecation + # docker # pytest # redis # sphinx @@ -153,13 +150,14 @@ paramiko==2.11.0 # via docker pg8000==1.29.1 # via -r requirements.in -pika==1.2.1 +pika==1.3.0 # via testcontainers pluggy==1.0.0 # via pytest -protobuf==3.20.1 +protobuf==3.20.2 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.3 @@ -179,11 +177,11 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.12.0 +pygments==2.13.0 # via sphinx -pyjwt==2.4.0 +pyjwt==2.5.0 # via python-arango -pymongo==4.1.1 +pymongo==4.2.0 # via testcontainers pymssql==2.2.5 # via testcontainers @@ -191,29 +189,27 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyopenssl==22.0.0 - # via urllib3 pyparsing==3.0.9 # via packaging pyrsistent==0.18.1 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.2 +pytest==7.1.3 # via # -r requirements.in # pytest-cov pytest-cov==3.0.0 # via -r requirements.in -python-arango==7.3.4 +python-arango==7.4.1 # via testcontainers -python-dotenv==0.20.0 +python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==1.5.0 +python-keycloak==2.5.0 # via testcontainers -pytz==2022.1 +pytz==2022.2.1 # via # babel # clickhouse-driver @@ -222,9 +218,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.3 +redis==4.3.4 # via testcontainers -requests==2.27.1 +requests==2.28.1 # via # azure-core # codecov @@ -240,14 +236,16 @@ requests==2.27.1 requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 - # via python-arango -rsa==4.8 + # via + # python-arango + # python-keycloak +rsa==4.9 # via # google-auth # python-jose scramp==1.4.1 # via pg8000 -selenium==4.2.0 +selenium==4.4.3 # via testcontainers six==1.16.0 # via @@ -260,13 +258,13 @@ six==1.16.0 # jsonschema # paramiko # websocket-client -sniffio==1.2.0 +sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.0.1 +sphinx==5.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -280,7 +278,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.37 +sqlalchemy==1.4.41 # via testcontainers texttable==1.6.4 # via docker-compose @@ -288,7 +286,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.20.0 +trio==0.21.0 # via # selenium # trio-websocket @@ -296,12 +294,13 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.3.0 # via azure-core -tzdata==2022.1 +tzdata==2022.2 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[secure,socks]==1.26.9 +urllib3[socks]==1.26.12 # via + # docker # python-arango # python-keycloak # requests @@ -314,9 +313,9 @@ wrapt==1.14.1 # via # deprecated # testcontainers -wsproto==1.1.0 +wsproto==1.2.0 # via trio-websocket -zipp==3.8.0 +zipp==3.8.1 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: From 16451d7df99755986e75b0e4d53a33ab8521fb4c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 21 Sep 2022 19:37:39 -0400 Subject: [PATCH 067/425] Replace deprecated `find_element_by_name`. --- testcontainers/selenium.py | 2 +- tests/test_webdriver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index 34843e76e..9e98538f4 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -45,7 +45,7 @@ class BrowserWebDriverContainer(DockerContainer): with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as chrome: webdriver = chrome.get_driver() webdriver.get("http://google.com") - webdriver.find_element_by_name("q").send_keys("Hello") + webdriver.find_element("name", "q").send_keys("Hello") You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ diff --git a/tests/test_webdriver.py b/tests/test_webdriver.py index e7282aa1b..512af97f9 100644 --- a/tests/test_webdriver.py +++ b/tests/test_webdriver.py @@ -12,4 +12,4 @@ def test_webdriver_container_container(caps): with BrowserWebDriverContainer(caps).maybe_emulate_amd64() as chrome: webdriver = chrome.get_driver() webdriver.get("http://google.com") - webdriver.find_element_by_name("q").send_keys("Hello") + webdriver.find_element("name", "q").send_keys("Hello") From 9a0998435ff7504626b007ec0d64d42ab223bc18 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 29 Sep 2022 11:13:57 -0400 Subject: [PATCH 068/425] Add simple doctest. --- .github/workflows/main.yml | 2 +- .gitignore | 2 ++ README.rst | 23 +++++++++++++---------- docs/conf.py | 1 + 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 56e5b3e03..fdb088d9c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,9 +63,9 @@ jobs: echo "Container diagnostics with host network" docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - name: Make docs - if: matrix.python-version == '3.7' run: | sphinx-build -nW docs docs/_build/html + sphinx-build -b doctest docs docs/_build/html - name: Run checks run: | flake8 diff --git a/.gitignore b/.gitignore index 9d6acfb77..d6f542436 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ venv # vscode: .devcontainer/ .vscode/ + +.DS_Store diff --git a/README.rst b/README.rst index 2f55e0567..e77d6b125 100644 --- a/README.rst +++ b/README.rst @@ -44,22 +44,25 @@ The testcontainers package is available from `PyPI >> from testcontainers.postgres import PostgresContainer + >>> import sqlalchemy - with MySqlContainer('mysql:5.7.17') as mysql: - engine = sqlalchemy.create_engine(mysql.get_connection_url()) - version, = engine.execute("select version()").fetchone() - print(version) # 5.7.17 + >>> postgres_container = PostgresContainer("postgres:9.5") + >>> with postgres_container as postgres: + ... e = sqlalchemy.create_engine(postgres.get_connection_url()) + ... result = e.execute("select version()") + ... version, = result.fetchone() + >>> version + 'PostgreSQL 9.5...' -The snippet above will spin up a MySql database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. +The snippet above will spin up a Postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. More extensive documentation can be found at `Read The Docs `_. -Usage within Docker (i.e. in a CI) ----------------------------------- +Usage within Docker (e.g., in a CI) +----------------------------------- When trying to launch a testcontainer from within a Docker container two things have to be provided: diff --git a/docs/conf.py b/docs/conf.py index 6077599ec..354df4d01 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,6 +32,7 @@ # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', 'sphinx.ext.napoleon', ] From 2877828ac4ab76a4742b40ca0843ffb2653e048f Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 29 Sep 2022 11:16:06 -0400 Subject: [PATCH 069/425] Separate build steps. --- .github/workflows/main.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fdb088d9c..5e9d3ed25 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,12 +62,13 @@ jobs: docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=bridge testcontainers-python python diagnostics.py echo "Container diagnostics with host network" docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - - name: Make docs + - name: Build documentation + run: sphinx-build -nW docs docs/_build/html + - name: Run doctests + run: sphinx-build -b doctest docs docs/_build/html + - name: Lint the code + run: flake8 + - name: Run tests run: | - sphinx-build -nW docs docs/_build/html - sphinx-build -b doctest docs docs/_build/html - - name: Run checks - run: | - flake8 py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/test_${{ matrix.test-component }} codecov From 08c8cfddd01d5a2d22bb9bda02f82aa4d572bd2c Mon Sep 17 00:00:00 2001 From: Shunsuke Kirino Date: Fri, 30 Sep 2022 16:15:33 +0900 Subject: [PATCH 070/425] Pass `**kwargs` from MySqlContainer constructor to DbContainer constructor --- testcontainers/mysql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index ca9c3790e..4e52b0908 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -35,7 +35,7 @@ class MySqlContainer(DbContainer): """ def __init__(self, image="mysql:latest", **kwargs): - super(MySqlContainer, self).__init__(image) + super(MySqlContainer, self).__init__(image, **kwargs) self.port_to_expose = 3306 self.with_exposed_ports(self.port_to_expose) self.MYSQL_USER = kwargs.get('MYSQL_USER', environ.get('MYSQL_USER', 'test')) From 322e55f1cad1c181e593ecb03d8bc40be0fefe9e Mon Sep 17 00:00:00 2001 From: Shunsuke Kirino Date: Sat, 1 Oct 2022 00:24:50 +0900 Subject: [PATCH 071/425] Receive MYSQL_* as separate parameters of MySqlContainer constructor --- testcontainers/mysql.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 4e52b0908..814eee744 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -34,15 +34,20 @@ class MySqlContainer(DbContainer): version, = result.fetchone() """ - def __init__(self, image="mysql:latest", **kwargs): + def __init__(self, + image="mysql:latest", + MYSQL_USER=None, + MYSQL_ROOT_PASSWORD=None, + MYSQL_PASSWORD=None, + MYSQL_DATABASE=None, + **kwargs): super(MySqlContainer, self).__init__(image, **kwargs) self.port_to_expose = 3306 self.with_exposed_ports(self.port_to_expose) - self.MYSQL_USER = kwargs.get('MYSQL_USER', environ.get('MYSQL_USER', 'test')) - self.MYSQL_ROOT_PASSWORD = kwargs.get('MYSQL_ROOT_PASSWORD', - environ.get('MYSQL_ROOT_PASSWORD', 'test')) - self.MYSQL_PASSWORD = kwargs.get('MYSQL_PASSWORD', environ.get('MYSQL_PASSWORD', 'test')) - self.MYSQL_DATABASE = kwargs.get('MYSQL_DATABASE', environ.get('MYSQL_DATABASE', 'test')) + self.MYSQL_USER = MYSQL_USER or environ.get('MYSQL_USER', 'test') + self.MYSQL_ROOT_PASSWORD = MYSQL_ROOT_PASSWORD or environ.get('MYSQL_ROOT_PASSWORD', 'test') + self.MYSQL_PASSWORD = MYSQL_PASSWORD or environ.get('MYSQL_PASSWORD', 'test') + self.MYSQL_DATABASE = MYSQL_DATABASE or environ.get('MYSQL_DATABASE', 'test') if self.MYSQL_USER == 'root': self.MYSQL_ROOT_PASSWORD = self.MYSQL_PASSWORD From d792464316a1c1fca05b13465a2b9ab1e30a2ea4 Mon Sep 17 00:00:00 2001 From: yakimka Date: Mon, 31 Oct 2022 20:07:58 +0200 Subject: [PATCH 072/425] Move `self.with_exposed_ports` to initializer --- testcontainers/clickhouse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/clickhouse.py b/testcontainers/clickhouse.py index 585c35e49..b77720fb2 100644 --- a/testcontainers/clickhouse.py +++ b/testcontainers/clickhouse.py @@ -52,6 +52,7 @@ def __init__( self.CLICKHOUSE_PASSWORD = password or self.CLICKHOUSE_PASSWORD self.CLICKHOUSE_DB = dbname or self.CLICKHOUSE_DB self.port_to_expose = port + self.with_exposed_ports(self.port_to_expose) @wait_container_is_ready(Error, EOFError) def _connect(self): @@ -59,7 +60,6 @@ def _connect(self): client.execute("SELECT version()") def _configure(self): - self.with_exposed_ports(self.port_to_expose) self.with_env("CLICKHOUSE_USER", self.CLICKHOUSE_USER) self.with_env("CLICKHOUSE_PASSWORD", self.CLICKHOUSE_PASSWORD) self.with_env("CLICKHOUSE_DB", self.CLICKHOUSE_DB) From 418ddc4fbc55fc430797a4660641ef6de6c9f39a Mon Sep 17 00:00:00 2001 From: Tim Voets Date: Sat, 5 Nov 2022 13:39:04 +0100 Subject: [PATCH 073/425] attempt dind fix Signed-off-by: Tim Voets --- testcontainers/core/container.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index f8b006857..695d05a68 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -1,3 +1,4 @@ +import os from deprecation import deprecated from docker.models.containers import Container @@ -89,7 +90,7 @@ def get_container_host_ip(self) -> str: return "localhost" # check testcontainers itself runs inside docker container - if inside_container(): + if inside_container() and not os.getenv("DOCKER_HOST"): # If newly spawned container's gateway IP address from the docker # "bridge" network is equal to detected host address, we should use # container IP address, otherwise fall back to detected host From 744453d8fe9c02f12333dcd143a3021066a3801c Mon Sep 17 00:00:00 2001 From: Tim Voets Date: Sun, 6 Nov 2022 17:12:26 +0100 Subject: [PATCH 074/425] add 'docker-in-docker' test Signed-off-by: Tim Voets --- tests/test_core/test_docker_in_docker.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/test_core/test_docker_in_docker.py diff --git a/tests/test_core/test_docker_in_docker.py b/tests/test_core/test_docker_in_docker.py new file mode 100644 index 000000000..694fb7980 --- /dev/null +++ b/tests/test_core/test_docker_in_docker.py @@ -0,0 +1,39 @@ +import pytest +import pprint + +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.waiting_utils import wait_for_logs, wait_container_is_ready + + +def test_wait_for_logs_docker_in_docker(): + # real dind isn't possible in CI, forwarding the socket to a container port is at least somewhat the same + client = DockerClient() + dind = client.run( + image="alpine/socat", + command="tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock", + volumes={'/var/run/docker.sock': {'bind': '/var/run/docker.sock'}}, + detach=True, + ) + + dind.start() + + specs = client.get_container(dind.id) + docker_host_ip = specs['NetworkSettings']['Networks']['bridge']['IPAddress'] + docker_host = f"tcp://{docker_host_ip}:2375" + + with DockerContainer( + image="hello-world", + docker_client_kw={ + "environment": { + "DOCKER_HOST": docker_host, + "DOCKER_TLS_CERTDIR": "" + } + }) as container: + assert container.get_container_host_ip() == docker_host_ip + wait_for_logs(container, "Hello from Docker!") + stdout, stderr = container.get_logs() + assert stdout, 'There should be something on stdout' + + dind.stop() + dind.remove() From ad8cd8e36a1fe51aa95a0651c220d56a0d178b2d Mon Sep 17 00:00:00 2001 From: Tim Voets Date: Sun, 6 Nov 2022 19:13:26 +0100 Subject: [PATCH 075/425] refactor, small fixes Signed-off-by: Tim Voets --- tests/test_core/test_docker_in_docker.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/test_core/test_docker_in_docker.py b/tests/test_core/test_docker_in_docker.py index 694fb7980..c6a6c5962 100644 --- a/tests/test_core/test_docker_in_docker.py +++ b/tests/test_core/test_docker_in_docker.py @@ -1,24 +1,22 @@ -import pytest -import pprint - from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient -from testcontainers.core.waiting_utils import wait_for_logs, wait_container_is_ready +from testcontainers.core.waiting_utils import wait_for_logs def test_wait_for_logs_docker_in_docker(): - # real dind isn't possible in CI, forwarding the socket to a container port is at least somewhat the same + # real dind isn't possible (AFAIK) in CI, forwarding the socket to a container port is at least somewhat the same client = DockerClient() - dind = client.run( + not_really_dind = client.run( image="alpine/socat", command="tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock", volumes={'/var/run/docker.sock': {'bind': '/var/run/docker.sock'}}, detach=True, ) - dind.start() + not_really_dind.start() - specs = client.get_container(dind.id) + # get ip address for DOCKER_HOST, avoiding DockerContainer class here to prevent code changes affecting the test + specs = client.get_container(not_really_dind.id) docker_host_ip = specs['NetworkSettings']['Networks']['bridge']['IPAddress'] docker_host = f"tcp://{docker_host_ip}:2375" @@ -27,7 +25,8 @@ def test_wait_for_logs_docker_in_docker(): docker_client_kw={ "environment": { "DOCKER_HOST": docker_host, - "DOCKER_TLS_CERTDIR": "" + "DOCKER_CERT_PATH": "", + "DOCKER_TLS_VERIFY": "" } }) as container: assert container.get_container_host_ip() == docker_host_ip @@ -35,5 +34,5 @@ def test_wait_for_logs_docker_in_docker(): stdout, stderr = container.get_logs() assert stdout, 'There should be something on stdout' - dind.stop() - dind.remove() + not_really_dind.stop() + not_really_dind.remove() From 88519a4d64316306d8187f232b692380f41e0a71 Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Mon, 21 Nov 2022 22:13:50 +0100 Subject: [PATCH 076/425] Removed Python 3.6 support --- README.rst | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index e77d6b125..c8a7aa3e9 100644 --- a/README.rst +++ b/README.rst @@ -73,7 +73,7 @@ When trying to launch a testcontainer from within a Docker container two things Setting up a development environment ------------------------------------ -We recommend you use a `virtual environment `_ for development. Note that a python version :code:`>=3.6` is required. After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. +We recommend you use a `virtual environment `_ for development. Note that a python version :code:`>=3.7` is required. After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. .. code-block:: bash diff --git a/setup.py b/setup.py index fe8428993..b73bcf743 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ 'Intended Audience :: Information Technology', 'Intended Audience :: Developers', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Software Development :: Libraries :: Python Modules', @@ -72,5 +71,5 @@ }, long_description_content_type="text/x-rst", long_description=long_description, - python_requires='>=3.6', + python_requires='>=3.7', ) From dc7cfe69ca7d68909a43ad633c7e8d40ba3e75e9 Mon Sep 17 00:00:00 2001 From: Tim Voets Date: Wed, 23 Nov 2022 23:33:50 +0100 Subject: [PATCH 077/425] make linter happy Signed-off-by: Tim Voets --- tests/test_core/test_docker_in_docker.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_core/test_docker_in_docker.py b/tests/test_core/test_docker_in_docker.py index c6a6c5962..66c8a4a96 100644 --- a/tests/test_core/test_docker_in_docker.py +++ b/tests/test_core/test_docker_in_docker.py @@ -4,7 +4,8 @@ def test_wait_for_logs_docker_in_docker(): - # real dind isn't possible (AFAIK) in CI, forwarding the socket to a container port is at least somewhat the same + # real dind isn't possible (AFAIK) in CI + # forwarding the socket to a container port is at least somewhat the same client = DockerClient() not_really_dind = client.run( image="alpine/socat", @@ -15,7 +16,8 @@ def test_wait_for_logs_docker_in_docker(): not_really_dind.start() - # get ip address for DOCKER_HOST, avoiding DockerContainer class here to prevent code changes affecting the test + # get ip address for DOCKER_HOST + # avoiding DockerContainer class here to prevent code changes affecting the test specs = client.get_container(not_really_dind.id) docker_host_ip = specs['NetworkSettings']['Networks']['bridge']['IPAddress'] docker_host = f"tcp://{docker_host_ip}:2375" From 861040e743400b17faa8de94a7655b5638685846 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 23 Nov 2022 19:41:51 -0500 Subject: [PATCH 078/425] Add `twine check` to CI. --- .github/workflows/main.yml | 4 + Makefile | 4 +- requirements.in | 1 + requirements/3.10.txt | 149 +++++++++++++++++++++++------------ requirements/3.7.txt | 151 ++++++++++++++++++++++------------- requirements/3.8.txt | 156 ++++++++++++++++++++++++------------- requirements/3.9.txt | 152 +++++++++++++++++++++++------------- setup.py | 1 + 8 files changed, 403 insertions(+), 215 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e9d3ed25..147e24d5f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,3 +72,7 @@ jobs: run: | py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/test_${{ matrix.test-component }} codecov + - name: Build package and check it + run: | + python setup.py bdist_wheel + twine check dist/* diff --git a/Makefile b/Makefile index 1309ebe2b..f7e2981d0 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ requirements : ${REQUIREMENTS} ${REQUIREMENTS} : requirements/%.txt : requirements.in setup.py mkdir -p $(dir $@) - ${RUN} -w /workspace -v `pwd`:/workspace python:$* bash -c \ - "pip install pip-tools && pip-compile -v --upgrade -o $@ $<" + ${RUN} -w /workspace -v `pwd`:/workspace --platform=linux/amd64 python:$* bash -c \ + "pip install pip-tools && pip-compile --resolver=backtracking -v --upgrade -o $@ $<" # Targets to build docker images diff --git a/requirements.in b/requirements.in index 90406ba20..cd3bc8b87 100644 --- a/requirements.in +++ b/requirements.in @@ -6,3 +6,4 @@ pg8000 pytest pytest-cov sphinx +twine diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 2f3583989..a35dc9ee3 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with python 3.10 # To update, run: # -# pip-compile --output-file=requirements/3.10.txt requirements.in +# pip-compile --output-file=requirements/3.10.txt --resolver=backtracking requirements.in # -e file:. # via -r requirements.in @@ -22,19 +22,21 @@ attrs==22.1.0 # outcome # pytest # trio -azure-core==1.25.1 +azure-core==1.26.1 # via # azure-storage-blob # msrest -azure-storage-blob==12.13.1 +azure-storage-blob==12.14.1 # via testcontainers -babel==2.10.3 +babel==2.11.0 # via sphinx -bcrypt==4.0.0 +bcrypt==4.0.1 # via paramiko +bleach==5.0.1 + # via readme-renderer cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -49,7 +51,9 @@ clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.4 +commonmark==0.9.1 + # via rich +coverage[toml]==6.5.0 # via # codecov # pytest-cov @@ -58,15 +62,16 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko + # secretstorage cx-oracle==8.3.0 # via testcontainers -deprecated==1.2.13 - # via redis deprecation==2.1.0 # via testcontainers -distro==1.7.0 +distro==1.8.0 # via docker-compose -docker[ssh]==6.0.0 +dnspython==2.2.1 + # via pymongo +docker[ssh]==6.0.1 # via # docker-compose # testcontainers @@ -77,37 +82,43 @@ dockerpty==0.4.1 docopt==0.6.2 # via docker-compose docutils==0.19 - # via sphinx + # via + # readme-renderer + # sphinx ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.4 + # via + # pytest + # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.1 +google-api-core[grpc]==2.10.2 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.14.1 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.4 +googleapis-common-protos[grpc]==1.57.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.3 +greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.50.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 +grpcio-status==1.48.2 # via google-api-core -h11==0.13.0 +h11==0.14.0 # via wsproto idna==3.4 # via @@ -115,25 +126,39 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx +importlib-metadata==5.0.0 + # via + # keyring + # twine iniconfig==1.1.1 # via pytest isodate==0.6.1 # via msrest +jaraco-classes==3.2.3 + # via keyring +jeepney==0.8.0 + # via + # keyring + # secretstorage jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers +keyring==23.11.0 + # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +more-itertools==9.0.0 + # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.0.1 +neo4j==5.2.1 # via testcontainers -oauthlib==3.2.1 +oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio @@ -144,24 +169,24 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.11.0 +paramiko==2.12.0 # via docker -pg8000==1.29.1 +pg8000==1.29.3 # via -r requirements.in -pika==1.3.0 +pika==1.3.1 # via testcontainers +pkginfo==1.8.3 + # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.2 +protobuf==3.20.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpcio-status -psycopg2-binary==2.9.3 +psycopg2-binary==2.9.5 # via testcontainers -py==1.11.0 - # via pytest pyasn1==0.4.8 # via # pyasn1-modules @@ -176,12 +201,15 @@ pycparser==2.21 pyflakes==2.1.1 # via flake8 pygments==2.13.0 - # via sphinx -pyjwt==2.5.0 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 # via python-arango -pymongo==4.2.0 +pymongo==4.3.3 # via testcontainers -pymssql==2.2.5 +pymssql==2.2.7 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -189,25 +217,27 @@ pynacl==1.5.0 # via paramiko pyparsing==3.0.9 # via packaging -pyrsistent==0.18.1 +pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.3 +pytest==7.2.0 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.2 # via testcontainers +python-dateutil==2.8.2 + # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.5.0 +python-keycloak==2.6.0 # via testcontainers -pytz==2022.2.1 +pytz==2022.6 # via # babel # clickhouse-driver @@ -216,7 +246,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.4 +readme-renderer==37.3 + # via twine +redis==4.3.5 # via testcontainers requests==2.28.1 # via @@ -231,23 +263,32 @@ requests==2.28.1 # requests-oauthlib # requests-toolbelt # sphinx + # twine requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 # via # python-arango # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==12.6.0 + # via twine rsa==4.9 # via # google-auth # python-jose -scramp==1.4.1 +scramp==1.4.4 # via pg8000 -selenium==4.4.3 +secretstorage==3.3.3 + # via keyring +selenium==4.6.0 # via testcontainers six==1.16.0 # via # azure-core + # bleach # dockerpty # ecdsa # google-auth @@ -255,6 +296,7 @@ six==1.16.0 # isodate # jsonschema # paramiko + # python-dateutil # websocket-client sniffio==1.3.0 # via trio @@ -262,7 +304,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -276,43 +318,48 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.41 +sqlalchemy==1.4.44 # via testcontainers -texttable==1.6.4 +texttable==1.6.7 # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.3.0 +twine==4.0.1 + # via -r requirements.in +typing-extensions==4.4.0 # via azure-core -tzdata==2022.2 +tzdata==2022.6 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.12 +urllib3[socks]==1.26.13 # via # docker # python-arango # python-keycloak # requests # selenium + # twine +webencodings==0.5.1 + # via bleach websocket-client==0.59.0 # via # docker # docker-compose wrapt==1.14.1 - # via - # deprecated - # testcontainers + # via testcontainers wsproto==1.2.0 # via trio-websocket +zipp==3.10.0 + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 9d778343e..6252d8354 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with python 3.7 # To update, run: # -# pip-compile --output-file=requirements/3.7.txt requirements.in +# pip-compile --output-file=requirements/3.7.txt --resolver=backtracking requirements.in # -e file:. # via -r requirements.in @@ -22,25 +22,27 @@ attrs==22.1.0 # outcome # pytest # trio -azure-core==1.25.1 +azure-core==1.26.1 # via # azure-storage-blob # msrest -azure-storage-blob==12.13.1 +azure-storage-blob==12.14.1 # via testcontainers -babel==2.10.3 +babel==2.11.0 # via sphinx backports-zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==4.0.0 +bcrypt==4.0.1 # via paramiko +bleach==5.0.1 + # via readme-renderer cached-property==1.5.2 # via docker-compose cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -55,7 +57,9 @@ clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.4 +commonmark==0.9.1 + # via rich +coverage[toml]==6.5.0 # via # codecov # pytest-cov @@ -64,15 +68,16 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko + # secretstorage cx-oracle==8.3.0 # via testcontainers -deprecated==1.2.13 - # via redis deprecation==2.1.0 # via testcontainers -distro==1.7.0 +distro==1.8.0 # via docker-compose -docker[ssh]==6.0.0 +dnspython==2.2.1 + # via pymongo +docker[ssh]==6.0.1 # via # docker-compose # testcontainers @@ -83,37 +88,43 @@ dockerpty==0.4.1 docopt==0.6.2 # via docker-compose docutils==0.19 - # via sphinx + # via + # readme-renderer + # sphinx ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.4 + # via + # pytest + # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.1 +google-api-core[grpc]==2.10.2 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.14.1 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.4 +googleapis-common-protos[grpc]==1.57.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.3 +greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.50.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 +grpcio-status==1.48.2 # via google-api-core -h11==0.13.0 +h11==0.14.0 # via wsproto idna==3.4 # via @@ -121,34 +132,47 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==4.12.0 +importlib-metadata==5.0.0 # via # jsonschema + # keyring # pg8000 # pluggy # pytest # redis + # scramp # sphinx # sqlalchemy + # twine iniconfig==1.1.1 # via pytest isodate==0.6.1 # via msrest +jaraco-classes==3.2.3 + # via keyring +jeepney==0.8.0 + # via + # keyring + # secretstorage jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers +keyring==23.11.0 + # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +more-itertools==9.0.0 + # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.0.1 +neo4j==5.2.1 # via testcontainers -oauthlib==3.2.1 +oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio @@ -159,24 +183,24 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.11.0 +paramiko==2.12.0 # via docker -pg8000==1.29.1 +pg8000==1.29.3 # via -r requirements.in -pika==1.3.0 +pika==1.3.1 # via testcontainers +pkginfo==1.8.3 + # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.2 +protobuf==3.20.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpcio-status -psycopg2-binary==2.9.3 +psycopg2-binary==2.9.5 # via testcontainers -py==1.11.0 - # via pytest pyasn1==0.4.8 # via # pyasn1-modules @@ -191,12 +215,15 @@ pycparser==2.21 pyflakes==2.1.1 # via flake8 pygments==2.13.0 - # via sphinx -pyjwt==2.5.0 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 # via python-arango -pymongo==4.2.0 +pymongo==4.3.3 # via testcontainers -pymssql==2.2.5 +pymssql==2.2.7 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -204,25 +231,27 @@ pynacl==1.5.0 # via paramiko pyparsing==3.0.9 # via packaging -pyrsistent==0.18.1 +pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.3 +pytest==7.2.0 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.2 # via testcontainers +python-dateutil==2.8.2 + # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.5.0 +python-keycloak==2.6.0 # via testcontainers -pytz==2022.2.1 +pytz==2022.6 # via # babel # clickhouse-driver @@ -231,7 +260,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.4 +readme-renderer==37.3 + # via twine +redis==4.3.5 # via testcontainers requests==2.28.1 # via @@ -246,23 +277,32 @@ requests==2.28.1 # requests-oauthlib # requests-toolbelt # sphinx + # twine requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 # via # python-arango # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==12.6.0 + # via twine rsa==4.9 # via # google-auth # python-jose -scramp==1.4.1 +scramp==1.4.4 # via pg8000 -selenium==4.4.3 +secretstorage==3.3.3 + # via keyring +selenium==4.6.0 # via testcontainers six==1.16.0 # via # azure-core + # bleach # dockerpty # ecdsa # google-auth @@ -270,6 +310,7 @@ six==1.16.0 # isodate # jsonschema # paramiko + # python-dateutil # websocket-client sniffio==1.3.0 # via trio @@ -277,7 +318,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -291,49 +332,53 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.41 +sqlalchemy==1.4.44 # via testcontainers -texttable==1.6.4 +texttable==1.6.7 # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.3.0 +twine==4.0.1 + # via -r requirements.in +typing-extensions==4.4.0 # via # async-timeout # azure-core # h11 # importlib-metadata # redis -tzdata==2022.2 + # rich +tzdata==2022.6 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.12 +urllib3[socks]==1.26.13 # via # docker # python-arango # python-keycloak # requests # selenium + # twine +webencodings==0.5.1 + # via bleach websocket-client==0.59.0 # via # docker # docker-compose wrapt==1.14.1 - # via - # deprecated - # testcontainers + # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.8.1 +zipp==3.10.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 3f3db8b8a..adc6ffac1 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with python 3.8 # To update, run: # -# pip-compile --output-file=requirements/3.8.txt requirements.in +# pip-compile --output-file=requirements/3.8.txt --resolver=backtracking requirements.in # -e file:. # via -r requirements.in @@ -22,23 +22,25 @@ attrs==22.1.0 # outcome # pytest # trio -azure-core==1.25.1 +azure-core==1.26.1 # via # azure-storage-blob # msrest -azure-storage-blob==12.13.1 +azure-storage-blob==12.14.1 # via testcontainers -babel==2.10.3 +babel==2.11.0 # via sphinx backports-zoneinfo==0.2.1 # via # pytz-deprecation-shim # tzlocal -bcrypt==4.0.0 +bcrypt==4.0.1 # via paramiko +bleach==5.0.1 + # via readme-renderer cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -53,7 +55,9 @@ clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.4 +commonmark==0.9.1 + # via rich +coverage[toml]==6.5.0 # via # codecov # pytest-cov @@ -62,15 +66,16 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko + # secretstorage cx-oracle==8.3.0 # via testcontainers -deprecated==1.2.13 - # via redis deprecation==2.1.0 # via testcontainers -distro==1.7.0 +distro==1.8.0 # via docker-compose -docker[ssh]==6.0.0 +dnspython==2.2.1 + # via pymongo +docker[ssh]==6.0.1 # via # docker-compose # testcontainers @@ -81,37 +86,43 @@ dockerpty==0.4.1 docopt==0.6.2 # via docker-compose docutils==0.19 - # via sphinx + # via + # readme-renderer + # sphinx ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.4 + # via + # pytest + # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.1 +google-api-core[grpc]==2.10.2 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.14.1 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.4 +googleapis-common-protos[grpc]==1.57.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.3 +greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.50.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 +grpcio-status==1.48.2 # via google-api-core -h11==0.13.0 +h11==0.14.0 # via wsproto idna==3.4 # via @@ -119,27 +130,40 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==4.12.0 - # via sphinx +importlib-metadata==5.0.0 + # via + # keyring + # sphinx + # twine iniconfig==1.1.1 # via pytest isodate==0.6.1 # via msrest +jaraco-classes==3.2.3 + # via keyring +jeepney==0.8.0 + # via + # keyring + # secretstorage jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers +keyring==23.11.0 + # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +more-itertools==9.0.0 + # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.0.1 +neo4j==5.2.1 # via testcontainers -oauthlib==3.2.1 +oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio @@ -150,24 +174,24 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.11.0 +paramiko==2.12.0 # via docker -pg8000==1.29.1 +pg8000==1.29.3 # via -r requirements.in -pika==1.3.0 +pika==1.3.1 # via testcontainers +pkginfo==1.8.3 + # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.2 +protobuf==3.20.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpcio-status -psycopg2-binary==2.9.3 +psycopg2-binary==2.9.5 # via testcontainers -py==1.11.0 - # via pytest pyasn1==0.4.8 # via # pyasn1-modules @@ -182,12 +206,15 @@ pycparser==2.21 pyflakes==2.1.1 # via flake8 pygments==2.13.0 - # via sphinx -pyjwt==2.5.0 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 # via python-arango -pymongo==4.2.0 +pymongo==4.3.3 # via testcontainers -pymssql==2.2.5 +pymssql==2.2.7 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -195,25 +222,27 @@ pynacl==1.5.0 # via paramiko pyparsing==3.0.9 # via packaging -pyrsistent==0.18.1 +pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.3 +pytest==7.2.0 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.2 # via testcontainers +python-dateutil==2.8.2 + # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.5.0 +python-keycloak==2.6.0 # via testcontainers -pytz==2022.2.1 +pytz==2022.6 # via # babel # clickhouse-driver @@ -222,7 +251,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.4 +readme-renderer==37.3 + # via twine +redis==4.3.5 # via testcontainers requests==2.28.1 # via @@ -237,23 +268,32 @@ requests==2.28.1 # requests-oauthlib # requests-toolbelt # sphinx + # twine requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 # via # python-arango # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==12.6.0 + # via twine rsa==4.9 # via # google-auth # python-jose -scramp==1.4.1 +scramp==1.4.4 # via pg8000 -selenium==4.4.3 +secretstorage==3.3.3 + # via keyring +selenium==4.6.0 # via testcontainers six==1.16.0 # via # azure-core + # bleach # dockerpty # ecdsa # google-auth @@ -261,6 +301,7 @@ six==1.16.0 # isodate # jsonschema # paramiko + # python-dateutil # websocket-client sniffio==1.3.0 # via trio @@ -268,7 +309,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -282,44 +323,49 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.41 +sqlalchemy==1.4.44 # via testcontainers -texttable==1.6.4 +texttable==1.6.7 # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.3.0 - # via azure-core -tzdata==2022.2 +twine==4.0.1 + # via -r requirements.in +typing-extensions==4.4.0 + # via + # azure-core + # rich +tzdata==2022.6 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.12 +urllib3[socks]==1.26.13 # via # docker # python-arango # python-keycloak # requests # selenium + # twine +webencodings==0.5.1 + # via bleach websocket-client==0.59.0 # via # docker # docker-compose wrapt==1.14.1 - # via - # deprecated - # testcontainers + # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.8.1 +zipp==3.10.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 2ecfcd63c..e43224747 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with python 3.9 # To update, run: # -# pip-compile --output-file=requirements/3.9.txt requirements.in +# pip-compile --output-file=requirements/3.9.txt --resolver=backtracking requirements.in # -e file:. # via -r requirements.in @@ -22,19 +22,21 @@ attrs==22.1.0 # outcome # pytest # trio -azure-core==1.25.1 +azure-core==1.26.1 # via # azure-storage-blob # msrest -azure-storage-blob==12.13.1 +azure-storage-blob==12.14.1 # via testcontainers -babel==2.10.3 +babel==2.11.0 # via sphinx -bcrypt==4.0.0 +bcrypt==4.0.1 # via paramiko +bleach==5.0.1 + # via readme-renderer cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -49,7 +51,9 @@ clickhouse-driver==0.2.4 # via testcontainers codecov==2.1.12 # via -r requirements.in -coverage[toml]==6.4.4 +commonmark==0.9.1 + # via rich +coverage[toml]==6.5.0 # via # codecov # pytest-cov @@ -58,15 +62,16 @@ cryptography==36.0.2 # -r requirements.in # azure-storage-blob # paramiko + # secretstorage cx-oracle==8.3.0 # via testcontainers -deprecated==1.2.13 - # via redis deprecation==2.1.0 # via testcontainers -distro==1.7.0 +distro==1.8.0 # via docker-compose -docker[ssh]==6.0.0 +dnspython==2.2.1 + # via pymongo +docker[ssh]==6.0.1 # via # docker-compose # testcontainers @@ -77,37 +82,43 @@ dockerpty==0.4.1 docopt==0.6.2 # via docker-compose docutils==0.19 - # via sphinx + # via + # readme-renderer + # sphinx ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.4 + # via + # pytest + # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.1 +google-api-core[grpc]==2.10.2 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.14.1 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers -googleapis-common-protos[grpc]==1.56.4 +googleapis-common-protos[grpc]==1.57.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==1.1.3 +greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.50.0 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 +grpcio-status==1.48.2 # via google-api-core -h11==0.13.0 +h11==0.14.0 # via wsproto idna==3.4 # via @@ -115,27 +126,40 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==4.12.0 - # via sphinx +importlib-metadata==5.0.0 + # via + # keyring + # sphinx + # twine iniconfig==1.1.1 # via pytest isodate==0.6.1 # via msrest +jaraco-classes==3.2.3 + # via keyring +jeepney==0.8.0 + # via + # keyring + # secretstorage jinja2==3.1.2 # via sphinx jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 # via testcontainers +keyring==23.11.0 + # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +more-itertools==9.0.0 + # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.0.1 +neo4j==5.2.1 # via testcontainers -oauthlib==3.2.1 +oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio @@ -146,24 +170,24 @@ packaging==21.3 # pytest # redis # sphinx -paramiko==2.11.0 +paramiko==2.12.0 # via docker -pg8000==1.29.1 +pg8000==1.29.3 # via -r requirements.in -pika==1.3.0 +pika==1.3.1 # via testcontainers +pkginfo==1.8.3 + # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.2 +protobuf==3.20.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpcio-status -psycopg2-binary==2.9.3 +psycopg2-binary==2.9.5 # via testcontainers -py==1.11.0 - # via pytest pyasn1==0.4.8 # via # pyasn1-modules @@ -178,12 +202,15 @@ pycparser==2.21 pyflakes==2.1.1 # via flake8 pygments==2.13.0 - # via sphinx -pyjwt==2.5.0 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 # via python-arango -pymongo==4.2.0 +pymongo==4.3.3 # via testcontainers -pymssql==2.2.5 +pymssql==2.2.7 # via testcontainers pymysql==1.0.2 # via testcontainers @@ -191,25 +218,27 @@ pynacl==1.5.0 # via paramiko pyparsing==3.0.9 # via packaging -pyrsistent==0.18.1 +pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.1.3 +pytest==7.2.0 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.2 # via testcontainers +python-dateutil==2.8.2 + # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.5.0 +python-keycloak==2.6.0 # via testcontainers -pytz==2022.2.1 +pytz==2022.6 # via # babel # clickhouse-driver @@ -218,7 +247,9 @@ pytz-deprecation-shim==0.1.0.post0 # via tzlocal pyyaml==5.4.1 # via docker-compose -redis==4.3.4 +readme-renderer==37.3 + # via twine +redis==4.3.5 # via testcontainers requests==2.28.1 # via @@ -233,23 +264,32 @@ requests==2.28.1 # requests-oauthlib # requests-toolbelt # sphinx + # twine requests-oauthlib==1.3.1 # via msrest requests-toolbelt==0.9.1 # via # python-arango # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==12.6.0 + # via twine rsa==4.9 # via # google-auth # python-jose -scramp==1.4.1 +scramp==1.4.4 # via pg8000 -selenium==4.4.3 +secretstorage==3.3.3 + # via keyring +selenium==4.6.0 # via testcontainers six==1.16.0 # via # azure-core + # bleach # dockerpty # ecdsa # google-auth @@ -257,6 +297,7 @@ six==1.16.0 # isodate # jsonschema # paramiko + # python-dateutil # websocket-client sniffio==1.3.0 # via trio @@ -264,7 +305,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -278,44 +319,47 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.41 +sqlalchemy==1.4.44 # via testcontainers -texttable==1.6.4 +texttable==1.6.7 # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.3.0 +twine==4.0.1 + # via -r requirements.in +typing-extensions==4.4.0 # via azure-core -tzdata==2022.2 +tzdata==2022.6 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.12 +urllib3[socks]==1.26.13 # via # docker # python-arango # python-keycloak # requests # selenium + # twine +webencodings==0.5.1 + # via bleach websocket-client==0.59.0 # via # docker # docker-compose wrapt==1.14.1 - # via - # deprecated - # testcontainers + # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.8.1 +zipp==3.10.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/setup.py b/setup.py index b73bcf743..706333a2e 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ with open('README.rst') as fp: long_description = fp.read() +long_description = long_description.replace(".. doctest::", ".. code-block::") # Load the version number try: From f5af126602d9685edfb9f6df5d1305e17fce3c9a Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Sun, 27 Nov 2022 21:52:32 +0100 Subject: [PATCH 079/425] updated sphinx paging and added some additional documentation --- docs/aws.rst | 6 ++++++ docs/azure.rst | 6 ++++++ docs/database.rst | 1 - docs/elasticsearch.rst | 6 ++++++ docs/index.rst | 9 +++++++++ docs/kafka.rst | 6 ++++++ docs/keycloak.rst | 6 ++++++ docs/rabbitmq.rst | 6 ++++++ docs/redis.rst | 6 ++++++ testcontainers/azurite.py | 4 +++- testcontainers/kafka.py | 10 ++++++++++ testcontainers/localstack.py | 1 + testcontainers/rabbitmq.py | 6 +++--- testcontainers/redis.py | 10 ++++++++++ 14 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 docs/aws.rst create mode 100644 docs/azure.rst create mode 100644 docs/elasticsearch.rst create mode 100644 docs/kafka.rst create mode 100644 docs/keycloak.rst create mode 100644 docs/rabbitmq.rst create mode 100644 docs/redis.rst diff --git a/docs/aws.rst b/docs/aws.rst new file mode 100644 index 000000000..784afa649 --- /dev/null +++ b/docs/aws.rst @@ -0,0 +1,6 @@ +AWS Emulators +=================== + +Allows to spin up AWS emulators, such as the LocalStackContainer. + +.. autoclass:: testcontainers.localstack.LocalStackContainer diff --git a/docs/azure.rst b/docs/azure.rst new file mode 100644 index 000000000..adb5446b3 --- /dev/null +++ b/docs/azure.rst @@ -0,0 +1,6 @@ +Azure Emulators +=================== + +Allows to spin up Azure emulators, such as the Azurite emulator. + +.. autoclass:: testcontainers.azurite.AzuriteContainer diff --git a/docs/database.rst b/docs/database.rst index 49d82eeb2..869487433 100644 --- a/docs/database.rst +++ b/docs/database.rst @@ -7,7 +7,6 @@ Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, .. autoclass:: testcontainers.mysql.MariaDbContainer .. autoclass:: testcontainers.postgres.PostgresContainer .. autoclass:: testcontainers.oracle.OracleDbContainer -.. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer .. autoclass:: testcontainers.mongodb.MongoDbContainer .. autoclass:: testcontainers.mssql.SqlServerContainer .. autoclass:: testcontainers.clickhouse.ClickHouseContainer diff --git a/docs/elasticsearch.rst b/docs/elasticsearch.rst new file mode 100644 index 000000000..2a21aa068 --- /dev/null +++ b/docs/elasticsearch.rst @@ -0,0 +1,6 @@ +Elastic Search Container +=========================== + +Allows to spin up Elastic Search Container. + +.. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer diff --git a/docs/index.rst b/docs/index.rst index 7c90a57bc..7201a495c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,3 +15,12 @@ Usage modes Selenium containers Docker Compose Google Cloud Emulators + Azure Emulators + AWS Emulators + Elastic Search + Kafka container + Keycloak container + RabbitMQ container + Redis container + + diff --git a/docs/kafka.rst b/docs/kafka.rst new file mode 100644 index 000000000..1f80f4c30 --- /dev/null +++ b/docs/kafka.rst @@ -0,0 +1,6 @@ +Kafka Container +=================== + +Allows to spin up Kafka Container. + +.. autoclass:: testcontainers.kafka.KafkaContainer diff --git a/docs/keycloak.rst b/docs/keycloak.rst new file mode 100644 index 000000000..356b3df3e --- /dev/null +++ b/docs/keycloak.rst @@ -0,0 +1,6 @@ +Keycloak Container +=================== + +Allows to spin up Keycloak container. + +.. autoclass:: testcontainers.keycloak.KeycloakContainer \ No newline at end of file diff --git a/docs/rabbitmq.rst b/docs/rabbitmq.rst new file mode 100644 index 000000000..b55c6615e --- /dev/null +++ b/docs/rabbitmq.rst @@ -0,0 +1,6 @@ +RabbitMQ Container +=================== + +Allows to spin up RabbitMQ container. + +.. autoclass:: testcontainers.rabbitmq.RabbitMqContainer \ No newline at end of file diff --git a/docs/redis.rst b/docs/redis.rst new file mode 100644 index 000000000..ce2c220c8 --- /dev/null +++ b/docs/redis.rst @@ -0,0 +1,6 @@ +Redis Container +=================== + +Allows to spin up Redis container. + +.. autoclass:: testcontainers.redis.RedisContainer \ No newline at end of file diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py index 748caadd0..baa2666d1 100644 --- a/testcontainers/azurite.py +++ b/testcontainers/azurite.py @@ -19,7 +19,9 @@ class AzuriteContainer(DockerContainer): """ - Azurite container. + The example below spins up an Azurite container and + shows an example to create a Blob service client with the container. The method get_connection_string + can be used to create a client for Blob service, Queue service and Table service. Example ------- diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py index 433c79975..6cb1b8cdd 100644 --- a/testcontainers/kafka.py +++ b/testcontainers/kafka.py @@ -11,6 +11,16 @@ class KafkaContainer(DockerContainer): + """ + Kafka container. + + Example + ------- + :: + + with KafkaContainer() as kafka: + connection_url = kafka.get_bootstrap_server() + """ KAFKA_PORT = 9093 TC_START_SCRIPT = '/tc-start.sh' diff --git a/testcontainers/localstack.py b/testcontainers/localstack.py index 1b942f264..49cc0770a 100644 --- a/testcontainers/localstack.py +++ b/testcontainers/localstack.py @@ -21,6 +21,7 @@ class LocalStackContainer(DockerContainer): Example ------- :: + localstack = LocalStackContainer(image="localstack/localstack:0.11.4") localstack.with_services("dynamodb", "lambda") localstack.start() diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py index db012fc13..aaa2ec8de 100644 --- a/testcontainers/rabbitmq.py +++ b/testcontainers/rabbitmq.py @@ -8,13 +8,13 @@ class RabbitMqContainer(DockerContainer): """ - Test container for RabbitMQ. + Test container for RabbitMQ. The example below spins up a RabbitMQ broker and uses the `pika` client library + (https://pypi.org/project/pika/) establish a connection to the broker. Example ------- - The example spins up a RabbitMQ broker and uses the `pika` client library - (https://pypi.org/project/pika/) establish a connection to the broker. :: + from testcontainer.rabbitmq import RabbitMqContainer import pika diff --git a/testcontainers/redis.py b/testcontainers/redis.py index 749dc70e9..f866ea9e2 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -17,6 +17,16 @@ class RedisContainer(DockerContainer): + """ + Redis container. + + Example + ------- + :: + + with RedisContainer() as redis: + redis_client = redis.get_client() + """ def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs): super(RedisContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose From 1dbc8c8543913c2472873d7ccea97f6ff695ebcd Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Sun, 27 Nov 2022 22:03:29 +0100 Subject: [PATCH 080/425] fixed the line length to 100 --- testcontainers/azurite.py | 5 +++-- testcontainers/rabbitmq.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py index baa2666d1..213f4af0a 100644 --- a/testcontainers/azurite.py +++ b/testcontainers/azurite.py @@ -20,8 +20,9 @@ class AzuriteContainer(DockerContainer): """ The example below spins up an Azurite container and - shows an example to create a Blob service client with the container. The method get_connection_string - can be used to create a client for Blob service, Queue service and Table service. + shows an example to create a Blob service client with the container. The method + get_connection_string can be used to create a client for Blob service, Queue service + and Table service. Example ------- diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py index aaa2ec8de..81a1b99ba 100644 --- a/testcontainers/rabbitmq.py +++ b/testcontainers/rabbitmq.py @@ -8,8 +8,8 @@ class RabbitMqContainer(DockerContainer): """ - Test container for RabbitMQ. The example below spins up a RabbitMQ broker and uses the `pika` client library - (https://pypi.org/project/pika/) establish a connection to the broker. + Test container for RabbitMQ. The example below spins up a RabbitMQ broker and uses the + `pika` client library (https://pypi.org/project/pika/) establish a connection to the broker. Example ------- From 31851897b3a278cf95f232a13cb0e712343c2af7 Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Thu, 1 Dec 2022 22:19:25 +0100 Subject: [PATCH 081/425] Added docstring directive to containers for automized example testing --- testcontainers/arangodb.py | 19 ++++++++------ testcontainers/azurite.py | 19 ++++++++------ testcontainers/clickhouse.py | 13 +++++++--- testcontainers/elasticsearch.py | 12 ++++++--- testcontainers/kafka.py | 8 +++--- testcontainers/keycloak.py | 8 +++--- testcontainers/localstack.py | 14 ++++++++--- testcontainers/mongodb.py | 44 ++++++++++++++++----------------- testcontainers/mssql.py | 11 ++++++--- testcontainers/mysql.py | 13 ++++++---- testcontainers/neo4j.py | 14 ++++++----- testcontainers/oracle.py | 9 ++++--- testcontainers/postgres.py | 15 ++++++++--- testcontainers/rabbitmq.py | 13 +++++----- testcontainers/redis.py | 8 +++--- testcontainers/selenium.py | 11 ++++----- 16 files changed, 139 insertions(+), 92 deletions(-) diff --git a/testcontainers/arangodb.py b/testcontainers/arangodb.py index 0ab82ef4a..597a1f2b0 100644 --- a/testcontainers/arangodb.py +++ b/testcontainers/arangodb.py @@ -17,16 +17,21 @@ class ArangoDbContainer(DbContainer): The example will spin up a ArangoDB container. You may use the :code:`get_connection_url()` method which returns a arangoclient-compatible url in format :code:`scheme://host:port`. As of now, only a single host is supported (over HTTP). - :: - with ArangoContainer("arangodb:3.9.1") as arango: - client = ArangoClient(hosts=arango.get_connection_url()) + .. doctest:: - # Connect - sys_db = arango_client.db(username="root", password="") + >>> from testcontainers.arangodb import ArangoDbContainer + >>> from arango import ArangoClient - # Create a new database named "test". - sys_db.create_database("test") + >>> with ArangoDbContainer("arangodb:3.9.1") as arango: + ... client = ArangoClient(hosts=arango.get_connection_url()) + ... + ... # Connect + ... sys_db = client.db(username="root", password="passwd") + ... + ... # Create a new database named "test". + ... sys_db.create_database("test") + True """ def __init__(self, diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py index 213f4af0a..fbb013c4a 100644 --- a/testcontainers/azurite.py +++ b/testcontainers/azurite.py @@ -26,14 +26,17 @@ class AzuriteContainer(DockerContainer): Example ------- - :: - - with AzuriteContainer() as azurite: - connection_string = azurite.get_connection_string() - BlobServiceClient.from_connection_string( - connection_string, - api_version="2019-12-12" - ) + .. doctest:: + + >>> from testcontainers.azurite import AzuriteContainer + >>> from azure.storage.blob import BlobServiceClient + + >>> with AzuriteContainer() as azurite_container: + ... connection_string = azurite_container.get_connection_string() + ... client = BlobServiceClient.from_connection_string( + ... connection_string, + ... api_version="2019-12-12" + ... ) """ _AZURITE_ACCOUNT_NAME = os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") diff --git a/testcontainers/clickhouse.py b/testcontainers/clickhouse.py index b77720fb2..4362a080f 100644 --- a/testcontainers/clickhouse.py +++ b/testcontainers/clickhouse.py @@ -27,11 +27,16 @@ class ClickHouseContainer(DbContainer): ------- The example spins up a ClickHouse database and connects to it using the :code:`clickhouse-driver`. - :: - with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: - with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: - result = client.execute("SELECT version()") + .. doctest:: + + >>> import clickhouse_driver + >>> from testcontainers.clickhouse import ClickHouseContainer + + >>> with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: + ... client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) + ... client.execute("select 'working'") + [('working',)] """ CLICKHOUSE_USER = os.environ.get("CLICKHOUSE_USER", "test") diff --git a/testcontainers/elasticsearch.py b/testcontainers/elasticsearch.py index 458d4e448..3a04bad70 100644 --- a/testcontainers/elasticsearch.py +++ b/testcontainers/elasticsearch.py @@ -62,10 +62,16 @@ class ElasticSearchContainer(DockerContainer): Example ------- - :: + .. doctest:: - with ElasticSearchContainer() as es: - connection_url = es.get_url() + >>> import json + >>> import urllib + >>> from testcontainers.elasticsearch import ElasticSearchContainer + + >>> with ElasticSearchContainer(f'elasticsearch:8.3.3') as es: + ... resp = urllib.request.urlopen(es.get_url()) + ... json.loads(resp.read().decode())['version']['number'] + '8.3.3' """ def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs): diff --git a/testcontainers/kafka.py b/testcontainers/kafka.py index 6cb1b8cdd..9344c4351 100644 --- a/testcontainers/kafka.py +++ b/testcontainers/kafka.py @@ -16,10 +16,12 @@ class KafkaContainer(DockerContainer): Example ------- - :: + .. doctest:: - with KafkaContainer() as kafka: - connection_url = kafka.get_bootstrap_server() + >>> from testcontainers.kafka import KafkaContainer + + >>> with KafkaContainer() as kafka: + ... connection = kafka.get_bootstrap_server() """ KAFKA_PORT = 9093 TC_START_SCRIPT = '/tc-start.sh' diff --git a/testcontainers/keycloak.py b/testcontainers/keycloak.py index 05e876d0d..b0ffa198b 100644 --- a/testcontainers/keycloak.py +++ b/testcontainers/keycloak.py @@ -25,10 +25,12 @@ class KeycloakContainer(DockerContainer): Example ------- - :: + .. doctest:: - with KeycloakContainer() as kc: - keycloak: KeycloakAdmin = kc.get_client() + >>> from testcontainers.keycloak import KeycloakContainer + + >>> with KeycloakContainer() as kc: + ... keycloak = kc.get_client() """ KEYCLOAK_USER = os.environ.get("KEYCLOAK_USER", "test") KEYCLOAK_PASSWORD = os.environ.get("KEYCLOAK_PASSWORD", "test") diff --git a/testcontainers/localstack.py b/testcontainers/localstack.py index 49cc0770a..105086bef 100644 --- a/testcontainers/localstack.py +++ b/testcontainers/localstack.py @@ -20,12 +20,18 @@ class LocalStackContainer(DockerContainer): Example ------- + .. doctest:: + + >>> from testcontainers.localstack import LocalStackContainer + + >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: + ... localstack.with_services("dynamodb", "lambda") + ... dynamo_endpoint = localstack.get_url() + + + The endpoint can be used to create a client with the boto3 library: :: - localstack = LocalStackContainer(image="localstack/localstack:0.11.4") - localstack.with_services("dynamodb", "lambda") - localstack.start() - dynamo_endpoint = localstack.get_url() dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) scan_result = dynamo_client.scan(TableName='foo') # Do something with the scan result diff --git a/testcontainers/mongodb.py b/testcontainers/mongodb.py index 0b278d405..eeaf8e0d7 100644 --- a/testcontainers/mongodb.py +++ b/testcontainers/mongodb.py @@ -22,29 +22,29 @@ class MongoDbContainer(DbContainer): Example ------- - :: + .. doctest:: - with MongoDbContainer("mongo:latest") as mongo: - db = mongo.get_connection_client().test - # Insert a database entry - result = db.restaurants.insert_one( - { - "address": { - "street": "2 Avenue", - "zipcode": "10075", - "building": "1480", - "coord": [-73.9557413, 40.7720266] - }, - "borough": "Manhattan", - "cuisine": "Italian", - "name": "Vella", - "restaurant_id": "41704620" - } - ) - # Find the restaurant document - cursor = db.restaurants.find({"borough": "Manhattan"}) - for document in cursor: - # Do something interesting with the document + >>> from testcontainers.mongodb import MongoDbContainer + + >>> with MongoDbContainer("mongo:latest") as mongo: + ... db = mongo.get_connection_client().test + ... # Insert a database entry + ... result = db.restaurants.insert_one( + ... { + ... "address": { + ... "street": "2 Avenue", + ... "zipcode": "10075", + ... "building": "1480", + ... "coord": [-73.9557413, 40.7720266] + ... }, + ... "borough": "Manhattan", + ... "cuisine": "Italian", + ... "name": "Vella", + ... "restaurant_id": "41704620" + ... } + ... ) + ... # Find the restaurant document + ... cursor = db.restaurants.find({"borough": "Manhattan"}) """ MONGO_INITDB_ROOT_USERNAME = os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") diff --git a/testcontainers/mssql.py b/testcontainers/mssql.py index e3a10276b..e3f9dea68 100644 --- a/testcontainers/mssql.py +++ b/testcontainers/mssql.py @@ -9,11 +9,14 @@ class SqlServerContainer(DbContainer): Example ------- - :: + .. doctest:: - with SqlServerContainer() as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute("select @@VERSION") + >>> import sqlalchemy + >>> from testcontainers.mssql import SqlServerContainer + + >>> with SqlServerContainer() as mssql: + ... e = sqlalchemy.create_engine(mssql.get_connection_url()) + ... result = e.execute("select @@VERSION") Notes ----- diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 814eee744..d2362084f 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -26,12 +26,15 @@ class MySqlContainer(DbContainer): in the constructor. Alternatively, you may use the :code:`get_connection_url()` method which returns a sqlalchemy-compatible url in format :code:`dialect+driver://username:password@host:port/database`. - :: + .. doctest:: - with MySqlContainer('mysql:5.7.17') as mysql: - e = sqlalchemy.create_engine(mysql.get_connection_url()) - result = e.execute("select version()") - version, = result.fetchone() + >>> import sqlalchemy + >>> from testcontainers.mysql import MySqlContainer + + >>> with MySqlContainer('mysql:5.7.17') as mysql: + ... e = sqlalchemy.create_engine(mysql.get_connection_url()) + ... result = e.execute("select version()") + ... version, = result.fetchone() """ def __init__(self, diff --git a/testcontainers/neo4j.py b/testcontainers/neo4j.py index 67a30b260..be99af450 100644 --- a/testcontainers/neo4j.py +++ b/testcontainers/neo4j.py @@ -25,13 +25,15 @@ class Neo4jContainer(DbContainer): Example ------- - :: - with Neo4jContainer() as neo4j: - with neo4j.get_driver() as driver: - with driver.session() as session: - result = session.run("MATCH (n) RETURN n LIMIT 1") - record = result.single() + .. doctest:: + >>> from testcontainers.neo4j import Neo4jContainer + + >>> with Neo4jContainer() as neo4j: + ... with neo4j.get_driver() as driver: + ... with driver.session() as session: + ... result = session.run("MATCH (n) RETURN n LIMIT 1") + ... record = result.single() """ # The official image requires a change of password on startup. diff --git a/testcontainers/oracle.py b/testcontainers/oracle.py index 341aaa108..4a528e04d 100644 --- a/testcontainers/oracle.py +++ b/testcontainers/oracle.py @@ -9,9 +9,12 @@ class OracleDbContainer(DbContainer): ------- :: - with OracleDbContainer() as oracle: - e = sqlalchemy.create_engine(oracle.get_connection_url()) - result = e.execute("select 1 from dual") + >>> import sqlalchemy + >>> from testcontainers.oracle import OracleDbContainer + + >>> with OracleDbContainer() as oracle: + ... e = sqlalchemy.create_engine(oracle.get_connection_url()) + ... result = e.execute("select * from V$VERSION") """ def __init__(self, image="wnameless/oracle-xe-11g-r2:latest", **kwargs): diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index 0081c4a9a..d32da472a 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -22,11 +22,18 @@ class PostgresContainer(DbContainer): Example ------- The example spins up a Postgres database and connects to it using the :code:`psycopg` driver. - :: + .. doctest:: - with PostgresContainer("postgres:9.5") as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - result = e.execute("select version()") + >>> from testcontainers.postgres import PostgresContainer + >>> import sqlalchemy + + >>> postgres_container = PostgresContainer("postgres:9.5") + >>> with postgres_container as postgres: + ... e = sqlalchemy.create_engine(postgres.get_connection_url()) + ... result = e.execute("select version()") + ... version, = result.fetchone() + >>> version + 'PostgreSQL 9.5...' """ POSTGRES_USER = os.environ.get("POSTGRES_USER", "test") POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "test") diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py index 81a1b99ba..c39dc4166 100644 --- a/testcontainers/rabbitmq.py +++ b/testcontainers/rabbitmq.py @@ -13,15 +13,14 @@ class RabbitMqContainer(DockerContainer): Example ------- - :: + .. doctest:: - from testcontainer.rabbitmq import RabbitMqContainer - import pika + >>> import pika + >>> from testcontainers.rabbitmq import RabbitMqContainer - with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq: - - connection = pika.BlockingConnection(rabbitmq.get_connection_params()) - channel = connection.channel() + >>> with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq: + ... connection = pika.BlockingConnection(rabbitmq.get_connection_params()) + ... channel = connection.channel() """ RABBITMQ_NODE_PORT = os.environ.get("RABBITMQ_NODE_PORT", 5672) diff --git a/testcontainers/redis.py b/testcontainers/redis.py index f866ea9e2..b7da14407 100644 --- a/testcontainers/redis.py +++ b/testcontainers/redis.py @@ -22,10 +22,12 @@ class RedisContainer(DockerContainer): Example ------- - :: + .. doctest:: - with RedisContainer() as redis: - redis_client = redis.get_client() + >>> from testcontainers.redis import RedisContainer + + >>> with RedisContainer() as redis_container: + ... redis_client = redis_container.get_client() """ def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs): super(RedisContainer, self).__init__(image, **kwargs) diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index 9e98538f4..597abca28 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -38,14 +38,13 @@ class BrowserWebDriverContainer(DockerContainer): Example ------- - :: + .. doctest:: - from selenium.webdriver import DesiredCapabilities + >>> from testcontainers.selenium import BrowserWebDriverContainer + >>> from selenium.webdriver import DesiredCapabilities - with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as chrome: - webdriver = chrome.get_driver() - webdriver.get("http://google.com") - webdriver.find_element("name", "q").send_keys("Hello") + >>> with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as chrome: + ... webdriver = chrome.get_driver() You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ From 8e1010e38369d4efc6e91383891c91b1818e06aa Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Thu, 1 Dec 2022 23:09:37 +0100 Subject: [PATCH 082/425] Updated GitHub workflow to do less doctests --- .github/workflows/main.yml | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 147e24d5f..28c9c8472 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,38 @@ on: branches: [ master ] jobs: + sphinx: + strategy: + fail-fast: false + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Cache Python dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles(format('requirements/{0}.txt', matrix.python-version)) }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install wheel + pip install -r requirements/${{ matrix.python-version }}.txt + - name: Run doctests + run: sphinx-build -b doctest docs docs/_build/html + build: strategy: fail-fast: false @@ -64,8 +96,6 @@ jobs: docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - name: Build documentation run: sphinx-build -nW docs docs/_build/html - - name: Run doctests - run: sphinx-build -b doctest docs docs/_build/html - name: Lint the code run: flake8 - name: Run tests From 370454752e4a1fa068b634e99178f7af886f832d Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Fri, 2 Dec 2022 09:45:01 +0100 Subject: [PATCH 083/425] Moved sphinx build documentation step to sphinx job --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 28c9c8472..c2ed7a4ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,8 @@ jobs: python -m pip install --upgrade pip pip install wheel pip install -r requirements/${{ matrix.python-version }}.txt + - name: Build documentation + run: sphinx-build -nW docs docs/_build/html - name: Run doctests run: sphinx-build -b doctest docs docs/_build/html @@ -94,8 +96,6 @@ jobs: docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=bridge testcontainers-python python diagnostics.py echo "Container diagnostics with host network" docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - - name: Build documentation - run: sphinx-build -nW docs docs/_build/html - name: Lint the code run: flake8 - name: Run tests From a1b5d4c61a4c7c11beadd653651ceec843e448f7 Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Fri, 2 Dec 2022 18:53:33 +0100 Subject: [PATCH 084/425] updated typo at rabbitmq.py Co-authored-by: Till Hoffmann --- testcontainers/rabbitmq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/rabbitmq.py b/testcontainers/rabbitmq.py index c39dc4166..402404bca 100644 --- a/testcontainers/rabbitmq.py +++ b/testcontainers/rabbitmq.py @@ -9,7 +9,7 @@ class RabbitMqContainer(DockerContainer): """ Test container for RabbitMQ. The example below spins up a RabbitMQ broker and uses the - `pika` client library (https://pypi.org/project/pika/) establish a connection to the broker. + `pika` client library (https://pypi.org/project/pika/) to establish a connection to the broker. Example ------- From 7d1e42c7094d3d75ac9d3d2ea51d27d2a12a3049 Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Fri, 2 Dec 2022 18:53:57 +0100 Subject: [PATCH 085/425] Update docs structure at testcontainers/azurite.py Co-authored-by: Till Hoffmann --- testcontainers/azurite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/azurite.py b/testcontainers/azurite.py index fbb013c4a..7020fdc09 100644 --- a/testcontainers/azurite.py +++ b/testcontainers/azurite.py @@ -21,7 +21,7 @@ class AzuriteContainer(DockerContainer): """ The example below spins up an Azurite container and shows an example to create a Blob service client with the container. The method - get_connection_string can be used to create a client for Blob service, Queue service + :code:`get_connection_string` can be used to create a client for Blob service, Queue service and Table service. Example From 6c4ab682f7589459516adb9b48a4c5c196ada834 Mon Sep 17 00:00:00 2001 From: Pepijn Fijt Date: Fri, 2 Dec 2022 18:56:46 +0100 Subject: [PATCH 086/425] fixed indentation neo4j example --- testcontainers/neo4j.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testcontainers/neo4j.py b/testcontainers/neo4j.py index be99af450..36e9732e7 100644 --- a/testcontainers/neo4j.py +++ b/testcontainers/neo4j.py @@ -29,11 +29,11 @@ class Neo4jContainer(DbContainer): >>> from testcontainers.neo4j import Neo4jContainer - >>> with Neo4jContainer() as neo4j: - ... with neo4j.get_driver() as driver: - ... with driver.session() as session: - ... result = session.run("MATCH (n) RETURN n LIMIT 1") - ... record = result.single() + >>> with Neo4jContainer() as neo4j, \ + neo4j.get_driver() as driver, \ + driver.session() as session: + ... result = session.run("MATCH (n) RETURN n LIMIT 1") + ... record = result.single() """ # The official image requires a change of password on startup. From 61e25721977b8c5d16a8ad1dbb4912aed53a34ae Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Sat, 3 Dec 2022 18:01:55 +0100 Subject: [PATCH 087/425] add minio container --- .github/workflows/main.yml | 1 + requirements.in | 2 +- requirements/3.10.txt | 33 +++++++++-------- requirements/3.7.txt | 33 +++++++++-------- requirements/3.8.txt | 33 +++++++++-------- requirements/3.9.txt | 33 +++++++++-------- setup.py | 3 ++ testcontainers/minio.py | 75 ++++++++++++++++++++++++++++++++++++++ tests/test_minio.py | 37 +++++++++++++++++++ 9 files changed, 189 insertions(+), 61 deletions(-) create mode 100644 testcontainers/minio.py create mode 100644 tests/test_minio.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 147e24d5f..02aab82a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,6 +22,7 @@ jobs: - google.py - kafka.py - localstack.py + - minio.py - mongodb.py - neo4j.py - nginx.py diff --git a/requirements.in b/requirements.in index cd3bc8b87..f41b1b145 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,rabbitmq,clickhouse,keycloak,arangodb,azurite] +-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,minio,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite] codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. diff --git a/requirements/3.10.txt b/requirements/3.10.txt index a35dc9ee3..bd62b9486 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: # # pip-compile --output-file=requirements/3.10.txt --resolver=backtracking requirements.in # @@ -38,6 +38,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.9.24 # via + # minio # msrest # requests # selenium @@ -47,7 +48,7 @@ cffi==1.15.1 # pynacl charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.4 +clickhouse-driver==0.2.5 # via testcontainers codecov==2.1.12 # via -r requirements.in @@ -95,9 +96,9 @@ exceptiongroup==1.0.4 # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.2 +google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.14.1 +google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -110,7 +111,7 @@ greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.50.0 +grpcio==1.51.1 # via # google-api-core # googleapis-common-protos @@ -126,7 +127,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.0.0 +importlib-metadata==5.1.0 # via # keyring # twine @@ -152,11 +153,13 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +minio==7.1.12 + # via testcontainers more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.2.1 +neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib @@ -175,7 +178,7 @@ pg8000==1.29.3 # via -r requirements.in pika==1.3.1 # via testcontainers -pkginfo==1.8.3 +pkginfo==1.9.2 # via twine pluggy==1.0.0 # via pytest @@ -227,7 +230,7 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.2 +python-arango==7.5.3 # via testcontainers python-dateutil==2.8.2 # via pg8000 @@ -283,7 +286,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.6.0 +selenium==4.7.2 # via testcontainers six==1.16.0 # via @@ -292,7 +295,6 @@ six==1.16.0 # dockerpty # ecdsa # google-auth - # grpcio # isodate # jsonschema # paramiko @@ -332,17 +334,18 @@ trio==0.22.0 # trio-websocket trio-websocket==0.9.2 # via selenium -twine==4.0.1 +twine==4.0.2 # via -r requirements.in typing-extensions==4.4.0 # via azure-core -tzdata==2022.6 +tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver urllib3[socks]==1.26.13 # via # docker + # minio # python-arango # python-keycloak # requests @@ -358,7 +361,7 @@ wrapt==1.14.1 # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.10.0 +zipp==3.11.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 6252d8354..3db50f23f 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.7 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: # # pip-compile --output-file=requirements/3.7.txt --resolver=backtracking requirements.in # @@ -44,6 +44,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.9.24 # via + # minio # msrest # requests # selenium @@ -53,7 +54,7 @@ cffi==1.15.1 # pynacl charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.4 +clickhouse-driver==0.2.5 # via testcontainers codecov==2.1.12 # via -r requirements.in @@ -101,9 +102,9 @@ exceptiongroup==1.0.4 # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.2 +google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.14.1 +google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -116,7 +117,7 @@ greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.50.0 +grpcio==1.51.1 # via # google-api-core # googleapis-common-protos @@ -132,7 +133,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.0.0 +importlib-metadata==5.1.0 # via # jsonschema # keyring @@ -166,11 +167,13 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +minio==7.1.12 + # via testcontainers more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.2.1 +neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib @@ -189,7 +192,7 @@ pg8000==1.29.3 # via -r requirements.in pika==1.3.1 # via testcontainers -pkginfo==1.8.3 +pkginfo==1.9.2 # via twine pluggy==1.0.0 # via pytest @@ -241,7 +244,7 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.2 +python-arango==7.5.3 # via testcontainers python-dateutil==2.8.2 # via pg8000 @@ -297,7 +300,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.6.0 +selenium==4.7.2 # via testcontainers six==1.16.0 # via @@ -306,7 +309,6 @@ six==1.16.0 # dockerpty # ecdsa # google-auth - # grpcio # isodate # jsonschema # paramiko @@ -346,7 +348,7 @@ trio==0.22.0 # trio-websocket trio-websocket==0.9.2 # via selenium -twine==4.0.1 +twine==4.0.2 # via -r requirements.in typing-extensions==4.4.0 # via @@ -356,13 +358,14 @@ typing-extensions==4.4.0 # importlib-metadata # redis # rich -tzdata==2022.6 +tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver urllib3[socks]==1.26.13 # via # docker + # minio # python-arango # python-keycloak # requests @@ -378,7 +381,7 @@ wrapt==1.14.1 # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.10.0 +zipp==3.11.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.8.txt b/requirements/3.8.txt index adc6ffac1..3b2b5c5bd 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # # pip-compile --output-file=requirements/3.8.txt --resolver=backtracking requirements.in # @@ -42,6 +42,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.9.24 # via + # minio # msrest # requests # selenium @@ -51,7 +52,7 @@ cffi==1.15.1 # pynacl charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.4 +clickhouse-driver==0.2.5 # via testcontainers codecov==2.1.12 # via -r requirements.in @@ -99,9 +100,9 @@ exceptiongroup==1.0.4 # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.2 +google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.14.1 +google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -114,7 +115,7 @@ greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.50.0 +grpcio==1.51.1 # via # google-api-core # googleapis-common-protos @@ -130,7 +131,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.0.0 +importlib-metadata==5.1.0 # via # keyring # sphinx @@ -157,11 +158,13 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +minio==7.1.12 + # via testcontainers more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.2.1 +neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib @@ -180,7 +183,7 @@ pg8000==1.29.3 # via -r requirements.in pika==1.3.1 # via testcontainers -pkginfo==1.8.3 +pkginfo==1.9.2 # via twine pluggy==1.0.0 # via pytest @@ -232,7 +235,7 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.2 +python-arango==7.5.3 # via testcontainers python-dateutil==2.8.2 # via pg8000 @@ -288,7 +291,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.6.0 +selenium==4.7.2 # via testcontainers six==1.16.0 # via @@ -297,7 +300,6 @@ six==1.16.0 # dockerpty # ecdsa # google-auth - # grpcio # isodate # jsonschema # paramiko @@ -337,19 +339,20 @@ trio==0.22.0 # trio-websocket trio-websocket==0.9.2 # via selenium -twine==4.0.1 +twine==4.0.2 # via -r requirements.in typing-extensions==4.4.0 # via # azure-core # rich -tzdata==2022.6 +tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver urllib3[socks]==1.26.13 # via # docker + # minio # python-arango # python-keycloak # requests @@ -365,7 +368,7 @@ wrapt==1.14.1 # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.10.0 +zipp==3.11.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.9.txt b/requirements/3.9.txt index e43224747..84d1a9ce3 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: # # pip-compile --output-file=requirements/3.9.txt --resolver=backtracking requirements.in # @@ -38,6 +38,7 @@ cachetools==5.2.0 # via google-auth certifi==2022.9.24 # via + # minio # msrest # requests # selenium @@ -47,7 +48,7 @@ cffi==1.15.1 # pynacl charset-normalizer==2.1.1 # via requests -clickhouse-driver==0.2.4 +clickhouse-driver==0.2.5 # via testcontainers codecov==2.1.12 # via -r requirements.in @@ -95,9 +96,9 @@ exceptiongroup==1.0.4 # trio flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.2 +google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.14.1 +google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -110,7 +111,7 @@ greenlet==2.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.50.0 +grpcio==1.51.1 # via # google-api-core # googleapis-common-protos @@ -126,7 +127,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.0.0 +importlib-metadata==5.1.0 # via # keyring # sphinx @@ -153,11 +154,13 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +minio==7.1.12 + # via testcontainers more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.2.1 +neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib @@ -176,7 +179,7 @@ pg8000==1.29.3 # via -r requirements.in pika==1.3.1 # via testcontainers -pkginfo==1.8.3 +pkginfo==1.9.2 # via twine pluggy==1.0.0 # via pytest @@ -228,7 +231,7 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.2 +python-arango==7.5.3 # via testcontainers python-dateutil==2.8.2 # via pg8000 @@ -284,7 +287,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.6.0 +selenium==4.7.2 # via testcontainers six==1.16.0 # via @@ -293,7 +296,6 @@ six==1.16.0 # dockerpty # ecdsa # google-auth - # grpcio # isodate # jsonschema # paramiko @@ -333,17 +335,18 @@ trio==0.22.0 # trio-websocket trio-websocket==0.9.2 # via selenium -twine==4.0.1 +twine==4.0.2 # via -r requirements.in typing-extensions==4.4.0 # via azure-core -tzdata==2022.6 +tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver urllib3[socks]==1.26.13 # via # docker + # minio # python-arango # python-keycloak # requests @@ -359,7 +362,7 @@ wrapt==1.14.1 # via testcontainers wsproto==1.2.0 # via trio-websocket -zipp==3.10.0 +zipp==3.11.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/setup.py b/setup.py index 706333a2e..82bfe42d9 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,8 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Software Development :: Libraries :: Python Modules', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', @@ -59,6 +61,7 @@ 'postgresql': ['sqlalchemy', 'psycopg2-binary'], 'selenium': ['selenium'], 'google-cloud-pubsub': ['google-cloud-pubsub < 2'], + 'minio': ['minio'], 'mongo': ['pymongo'], 'redis': ['redis'], 'mssqlserver': ['pymssql'], diff --git a/testcontainers/minio.py b/testcontainers/minio.py new file mode 100644 index 000000000..1e60f5e34 --- /dev/null +++ b/testcontainers/minio.py @@ -0,0 +1,75 @@ +from typing import TypedDict + +from minio import Minio +from requests import ConnectionError, Response, get + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class MinioConfig(TypedDict): + endpoint: str + console_address: str + access_key: str + secret_key: str + + +class MinioContainer(DockerContainer): + def __init__( + self, + image="minio/minio:RELEASE.2022-12-02T19-19-22Z", + port_to_expose=9000, + access_key="minioadmin", + secret_key="minioadmin", + **kwargs, + ): + super(MinioContainer, self).__init__(image, **kwargs) + self.port_to_expose = port_to_expose + self.console_port = port_to_expose + 1 + self.access_key = access_key + self.secret_key = secret_key + + self.with_exposed_ports(self.port_to_expose, self.console_port) + self.with_env("MINIO_ACCESS_KEY", self.access_key) + self.with_env("MINIO_SECRET_KEY", self.secret_key) + self.with_command( + f"server /data --address :{self.port_to_expose} --console-address :{self.console_port}" + ) + + def get_client(self, **kwargs) -> Minio: + """Returns a Minio client to connect to the container. + + Returns: + Minio: Python Minio Client according to https://min.io/docs/minio/linux/developers/python/API.html + """ + return Minio( + f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port_to_expose)}", + access_key=self.access_key, + secret_key=self.secret_key, + secure=False, + **kwargs, + ) + + def get_config(self) -> MinioConfig: + """Returns the configuration of the Minio container. + + Returns: + MinioConfig: Dictionary with the endpoint, access_key and secret_key. + """ + return { + "endpoint": f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port_to_expose)}", + "console_address": f"http://{self.get_container_host_ip()}:{self.console_port}", + "access_key": self.access_key, + "secret_key": self.secret_key, + } + + @wait_container_is_ready(ConnectionError) + def _healthcheck(self): + url = f"http://{self.get_config()['endpoint']}/minio/health/live" + response: Response = get(url) + response.raise_for_status() + + def start(self): + super().start() + self._healthcheck() + return self diff --git a/tests/test_minio.py b/tests/test_minio.py new file mode 100644 index 000000000..8fb62d9f2 --- /dev/null +++ b/tests/test_minio.py @@ -0,0 +1,37 @@ +import io +import socket +from contextlib import closing + +from pytest import fixture + +from testcontainers.minio import MinioContainer + + +@fixture +def port_to_expose(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(("localhost", 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + + +def test_docker_run_minio(port_to_expose): + config = MinioContainer( + port_to_expose=port_to_expose, + access_key="test-access", + secret_key="test-secret", + ) + with config as minio: + client = minio.get_client() + client.make_bucket("test") + test_content = b"Hello World" + client.put_object( + "test", + "testfile.txt", + io.BytesIO(test_content), + length=len(test_content), + ) + + assert client.get_object("test", "testfile.txt").data == test_content + assert minio.get_config()["access_key"] == config.access_key + assert minio.get_config()["secret_key"] == config.secret_key From d87820cc8c8014919b06125205cbb61a7cf3afda Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Sun, 4 Dec 2022 09:53:51 +0100 Subject: [PATCH 088/425] make everything headless --- testcontainers/minio.py | 8 ++------ tests/test_minio.py | 20 ++------------------ 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 1e60f5e34..6a33d29b4 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -25,16 +25,13 @@ def __init__( ): super(MinioContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose - self.console_port = port_to_expose + 1 self.access_key = access_key self.secret_key = secret_key - self.with_exposed_ports(self.port_to_expose, self.console_port) + self.with_exposed_ports(self.port_to_expose) self.with_env("MINIO_ACCESS_KEY", self.access_key) self.with_env("MINIO_SECRET_KEY", self.secret_key) - self.with_command( - f"server /data --address :{self.port_to_expose} --console-address :{self.console_port}" - ) + self.with_command(f"server /data --address :{self.port_to_expose}") def get_client(self, **kwargs) -> Minio: """Returns a Minio client to connect to the container. @@ -58,7 +55,6 @@ def get_config(self) -> MinioConfig: """ return { "endpoint": f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port_to_expose)}", - "console_address": f"http://{self.get_container_host_ip()}:{self.console_port}", "access_key": self.access_key, "secret_key": self.secret_key, } diff --git a/tests/test_minio.py b/tests/test_minio.py index 8fb62d9f2..99eed4380 100644 --- a/tests/test_minio.py +++ b/tests/test_minio.py @@ -1,26 +1,10 @@ import io -import socket -from contextlib import closing - -from pytest import fixture from testcontainers.minio import MinioContainer -@fixture -def port_to_expose(): - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: - s.bind(("localhost", 0)) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - return s.getsockname()[1] - - -def test_docker_run_minio(port_to_expose): - config = MinioContainer( - port_to_expose=port_to_expose, - access_key="test-access", - secret_key="test-secret", - ) +def test_docker_run_minio(): + config = MinioContainer(access_key="test-access", secret_key="test-secret") with config as minio: client = minio.get_client() client.make_bucket("test") From 6af0465ae39fdb22a17eca7ff6e6e02f949beee3 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Sun, 4 Dec 2022 10:11:05 +0100 Subject: [PATCH 089/425] removed console_address from MinioConfig --- testcontainers/minio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 6a33d29b4..954907c48 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -9,7 +9,6 @@ class MinioConfig(TypedDict): endpoint: str - console_address: str access_key: str secret_key: str From 59cca7011538397ce95a5faa06b6847625eef45e Mon Sep 17 00:00:00 2001 From: Malte Hedderich <12952192+maltehedderich@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:42:38 +0100 Subject: [PATCH 090/425] Reduce line length to <=100 --- testcontainers/minio.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 954907c48..32ec64d5e 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -36,7 +36,8 @@ def get_client(self, **kwargs) -> Minio: """Returns a Minio client to connect to the container. Returns: - Minio: Python Minio Client according to https://min.io/docs/minio/linux/developers/python/API.html + Minio: Python Minio Client according to + https://min.io/docs/minio/linux/developers/python/API.html """ return Minio( f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port_to_expose)}", @@ -53,7 +54,8 @@ def get_config(self) -> MinioConfig: MinioConfig: Dictionary with the endpoint, access_key and secret_key. """ return { - "endpoint": f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port_to_expose)}", + "endpoint": f"{self.get_container_host_ip()}" + + f":{self.get_exposed_port(self.port_to_expose)}", "access_key": self.access_key, "secret_key": self.secret_key, } From f5b07c15916576ede241e232dce470f00a9a7e53 Mon Sep 17 00:00:00 2001 From: Malte Hedderich <12952192+maltehedderich@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:02:37 +0100 Subject: [PATCH 091/425] Fix flake8 W291 and E131 --- testcontainers/minio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 32ec64d5e..385795c82 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -36,7 +36,7 @@ def get_client(self, **kwargs) -> Minio: """Returns a Minio client to connect to the container. Returns: - Minio: Python Minio Client according to + Minio: Python Minio Client according to https://min.io/docs/minio/linux/developers/python/API.html """ return Minio( @@ -55,7 +55,7 @@ def get_config(self) -> MinioConfig: """ return { "endpoint": f"{self.get_container_host_ip()}" + - f":{self.get_exposed_port(self.port_to_expose)}", + f":{self.get_exposed_port(self.port_to_expose)}", "access_key": self.access_key, "secret_key": self.secret_key, } From e5fb71793f92a0c53c7a4e9d02045dea51a72104 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 6 Dec 2022 10:26:18 -0500 Subject: [PATCH 092/425] Remove additional trailing line. --- docs/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 7201a495c..8cdd90ce8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,4 +23,3 @@ Usage modes RabbitMQ container Redis container - From dcce762ae58a398fd29832591109435c0e55440f Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 6 Dec 2022 10:27:51 -0500 Subject: [PATCH 093/425] Update indent in doctest example. --- testcontainers/neo4j.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcontainers/neo4j.py b/testcontainers/neo4j.py index 36e9732e7..daa127ec3 100644 --- a/testcontainers/neo4j.py +++ b/testcontainers/neo4j.py @@ -30,8 +30,8 @@ class Neo4jContainer(DbContainer): >>> from testcontainers.neo4j import Neo4jContainer >>> with Neo4jContainer() as neo4j, \ - neo4j.get_driver() as driver, \ - driver.session() as session: + neo4j.get_driver() as driver, \ + driver.session() as session: ... result = session.run("MATCH (n) RETURN n LIMIT 1") ... record = result.single() """ From a50f4d9987317ab223a721d5f90902451d8d8321 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 20:50:22 +0100 Subject: [PATCH 094/425] remove TypedDict for py3.7 compatibility --- testcontainers/minio.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 385795c82..21f7b25cd 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -1,5 +1,3 @@ -from typing import TypedDict - from minio import Minio from requests import ConnectionError, Response, get @@ -7,12 +5,6 @@ from testcontainers.core.waiting_utils import wait_container_is_ready -class MinioConfig(TypedDict): - endpoint: str - access_key: str - secret_key: str - - class MinioContainer(DockerContainer): def __init__( self, @@ -37,25 +29,28 @@ def get_client(self, **kwargs) -> Minio: Returns: Minio: Python Minio Client according to - https://min.io/docs/minio/linux/developers/python/API.html + https://min.io/docs/minio/linux/developers/python/API.html """ + host_ip = self.get_container_host_ip() + exposed_port = self.get_exposed_port(self.port_to_expose) return Minio( - f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port_to_expose)}", + f"{host_ip}:{exposed_port}", access_key=self.access_key, secret_key=self.secret_key, secure=False, **kwargs, ) - def get_config(self) -> MinioConfig: + def get_config(self) -> dict: """Returns the configuration of the Minio container. Returns: MinioConfig: Dictionary with the endpoint, access_key and secret_key. """ + host_ip = self.get_container_host_ip() + exposed_port = self.get_exposed_port(self.port_to_expose) return { - "endpoint": f"{self.get_container_host_ip()}" + - f":{self.get_exposed_port(self.port_to_expose)}", + "endpoint": f"{host_ip}:{exposed_port}", "access_key": self.access_key, "secret_key": self.secret_key, } From 31fd13721482dd1a0b1914084dba6df11bcecda8 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 21:07:00 +0100 Subject: [PATCH 095/425] improved docstring for getconfig --- testcontainers/minio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 21f7b25cd..8da3b8b5e 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -45,7 +45,7 @@ def get_config(self) -> dict: """Returns the configuration of the Minio container. Returns: - MinioConfig: Dictionary with the endpoint, access_key and secret_key. + dict: {`endpoint`: str, `access_key`: str, `secret_key`: str} """ host_ip = self.get_container_host_ip() exposed_port = self.get_exposed_port(self.port_to_expose) From 16cbdcdc57a3ebb379af7549809ff81444d44863 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 22:21:11 +0100 Subject: [PATCH 096/425] Add documentation and doctest --- testcontainers/minio.py | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 8da3b8b5e..0f7990305 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -6,6 +6,32 @@ class MinioContainer(DockerContainer): + """ + The example below spins up an Minio container and creates a new bucket in it. + Furthermore, it demonstrates how an object is written to this bucket and then subsequently retrieved. + The method :code:`get_client` can be used to create a client for the Minio Python API. + The method :code:`get_config` can be used to retrieve the endpoint, access key + and secret key of the container. + + Example + ------- + .. doctest:: + + >>> from testcontainers.minio import MinioContainer + + >>> with MinioContainer() as minio: + ... client = minio.get_client() + ... client.make_bucket("test") + ... test_content = b"Hello World" + ... client.put_object( + ... "test", + ... "testfile.txt", + ... io.BytesIO(test_content), + ... length=len(test_content), + ... ) + ... retrieved_content = client.get_object("test", "testfile.txt").data + """ + def __init__( self, image="minio/minio:RELEASE.2022-12-02T19-19-22Z", @@ -14,6 +40,14 @@ def __init__( secret_key="minioadmin", **kwargs, ): + """ + Args: + image (str, optional): The Docker image to use for the Minio container. + Defaults to "minio/minio:RELEASE.2022-12-02T19-19-22Z". + port_to_expose (int, optional): The port to expose on the container. Defaults to 9000. + access_key (str, optional): The access key for client connections. Defaults to "minioadmin". + secret_key (str, optional): The secret key for client connections. Defaults to "minioadmin". + """ super(MinioContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.access_key = access_key @@ -42,7 +76,8 @@ def get_client(self, **kwargs) -> Minio: ) def get_config(self) -> dict: - """Returns the configuration of the Minio container. + """This method returns the configuration of the Minio container, + including the endpoint, access key, and secret key. Returns: dict: {`endpoint`: str, `access_key`: str, `secret_key`: str} @@ -57,11 +92,15 @@ def get_config(self) -> dict: @wait_container_is_ready(ConnectionError) def _healthcheck(self): + """This is an internal method used to check if the Minio container + is healthy and ready to receive requests.""" url = f"http://{self.get_config()['endpoint']}/minio/health/live" response: Response = get(url) response.raise_for_status() def start(self): + """This method starts the Minio container and runs the healthcheck + to verify that the container is ready to use.""" super().start() self._healthcheck() return self From 50f95a3bf3a6294bad350018c8c8a28ff80e375e Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 22:28:57 +0100 Subject: [PATCH 097/425] Line length <100 --- testcontainers/minio.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 0f7990305..28ae55c75 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -8,7 +8,8 @@ class MinioContainer(DockerContainer): """ The example below spins up an Minio container and creates a new bucket in it. - Furthermore, it demonstrates how an object is written to this bucket and then subsequently retrieved. + Furthermore, it demonstrates how an object is written to this bucket and + then subsequently retrieved. The method :code:`get_client` can be used to create a client for the Minio Python API. The method :code:`get_config` can be used to retrieve the endpoint, access key and secret key of the container. @@ -44,9 +45,12 @@ def __init__( Args: image (str, optional): The Docker image to use for the Minio container. Defaults to "minio/minio:RELEASE.2022-12-02T19-19-22Z". - port_to_expose (int, optional): The port to expose on the container. Defaults to 9000. - access_key (str, optional): The access key for client connections. Defaults to "minioadmin". - secret_key (str, optional): The secret key for client connections. Defaults to "minioadmin". + port_to_expose (int, optional): The port to expose on the container. + Defaults to 9000. + access_key (str, optional): The access key for client connections. + Defaults to "minioadmin". + secret_key (str, optional): The secret key for client connections. + Defaults to "minioadmin". """ super(MinioContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose From 77242ae74c4417ebdc112eaddafb2fe13b5f3de8 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 22:44:58 +0100 Subject: [PATCH 098/425] add reference in docs --- docs/index.rst | 1 + docs/minio.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/minio.rst diff --git a/docs/index.rst b/docs/index.rst index 8cdd90ce8..8404eb6da 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,4 +22,5 @@ Usage modes Keycloak container RabbitMQ container Redis container + Minio container diff --git a/docs/minio.rst b/docs/minio.rst new file mode 100644 index 000000000..d11916a3d --- /dev/null +++ b/docs/minio.rst @@ -0,0 +1,6 @@ +Minio +=================== + +Allows to spin up Minio Container. + +.. autoclass:: testcontainers.minio.MinioContainer From c79c2dc8bd84fff7fb68b9fa8aa6a589ceaa04d6 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 22:50:17 +0100 Subject: [PATCH 099/425] added minio container to readme --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index c8a7aa3e9..488291985 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,7 @@ Currently available features: * RabbitMQ * Keycloak * Azurite container +* Minio container Installation ------------ From c7e8a6f6f4fb5363df8b92360393cd413448b8e7 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 22:51:41 +0100 Subject: [PATCH 100/425] fixed missing io import in doctest --- testcontainers/minio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index 28ae55c75..ab2bde3ae 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -18,6 +18,7 @@ class MinioContainer(DockerContainer): ------- .. doctest:: + >>> import io >>> from testcontainers.minio import MinioContainer >>> with MinioContainer() as minio: From cc6cf7b1e3b79fc39bed07d16a2eace71a292ece Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Tue, 6 Dec 2022 23:04:18 +0100 Subject: [PATCH 101/425] stored ObjectWriteResult --- testcontainers/minio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/minio.py b/testcontainers/minio.py index ab2bde3ae..039cff647 100644 --- a/testcontainers/minio.py +++ b/testcontainers/minio.py @@ -25,7 +25,7 @@ class MinioContainer(DockerContainer): ... client = minio.get_client() ... client.make_bucket("test") ... test_content = b"Hello World" - ... client.put_object( + ... write_result = client.put_object( ... "test", ... "testfile.txt", ... io.BytesIO(test_content), From a3a3d2f0cb7bf0b83c8b6a6bcf99d3da0ea9f4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 8 Dec 2022 22:24:41 -0600 Subject: [PATCH 102/425] Update cloud-sdk image emulators tags is light compared to latest --- testcontainers/google/pubsub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcontainers/google/pubsub.py b/testcontainers/google/pubsub.py index e4074dccd..26d5b71fa 100644 --- a/testcontainers/google/pubsub.py +++ b/testcontainers/google/pubsub.py @@ -27,14 +27,14 @@ class PubSubContainer(DockerContainer): :: def test_docker_run_pubsub(): - config = PubSubContainer('google/cloud-sdk:latest') + config = PubSubContainer('google/cloud-sdk:emulators') with config as pubsub: publisher = pubsub.get_publisher() topic_path = publisher.topic_path(pubsub.project, "my-topic") topic = publisher.create_topic(topic_path) """ - def __init__(self, image="google/cloud-sdk:latest", + def __init__(self, image="google/cloud-sdk:emulators", project="test-project", port=8432, **kwargs): super(PubSubContainer, self).__init__(image=image, **kwargs) self.project = project From 2215ab9a76f873491125802f597167d98eabdeae Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 9 Dec 2022 08:46:00 -0500 Subject: [PATCH 103/425] Update dependencies. --- requirements/3.10.txt | 9 +++------ requirements/3.7.txt | 9 +++------ requirements/3.8.txt | 9 +++------ requirements/3.9.txt | 9 +++------ 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/requirements/3.10.txt b/requirements/3.10.txt index bd62b9486..3c6ab5c12 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -36,7 +36,7 @@ bleach==5.0.1 # via readme-renderer cachetools==5.2.0 # via google-auth -certifi==2022.9.24 +certifi==2022.12.7 # via # minio # msrest @@ -165,12 +165,11 @@ oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio -packaging==21.3 +packaging==22.0 # via # deprecation # docker # pytest - # redis # sphinx paramiko==2.12.0 # via docker @@ -218,8 +217,6 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyparsing==3.0.9 - # via packaging pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 @@ -251,7 +248,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.3.5 +redis==4.4.0 # via testcontainers requests==2.28.1 # via diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 3db50f23f..d3d946913 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -42,7 +42,7 @@ cached-property==1.5.2 # via docker-compose cachetools==5.2.0 # via google-auth -certifi==2022.9.24 +certifi==2022.12.7 # via # minio # msrest @@ -179,12 +179,11 @@ oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio -packaging==21.3 +packaging==22.0 # via # deprecation # docker # pytest - # redis # sphinx paramiko==2.12.0 # via docker @@ -232,8 +231,6 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyparsing==3.0.9 - # via packaging pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 @@ -265,7 +262,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.3.5 +redis==4.4.0 # via testcontainers requests==2.28.1 # via diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 3b2b5c5bd..15e458420 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -40,7 +40,7 @@ bleach==5.0.1 # via readme-renderer cachetools==5.2.0 # via google-auth -certifi==2022.9.24 +certifi==2022.12.7 # via # minio # msrest @@ -170,12 +170,11 @@ oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio -packaging==21.3 +packaging==22.0 # via # deprecation # docker # pytest - # redis # sphinx paramiko==2.12.0 # via docker @@ -223,8 +222,6 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyparsing==3.0.9 - # via packaging pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 @@ -256,7 +253,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.3.5 +redis==4.4.0 # via testcontainers requests==2.28.1 # via diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 84d1a9ce3..668cb04d4 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -36,7 +36,7 @@ bleach==5.0.1 # via readme-renderer cachetools==5.2.0 # via google-auth -certifi==2022.9.24 +certifi==2022.12.7 # via # minio # msrest @@ -166,12 +166,11 @@ oauthlib==3.2.2 # via requests-oauthlib outcome==1.2.0 # via trio -packaging==21.3 +packaging==22.0 # via # deprecation # docker # pytest - # redis # sphinx paramiko==2.12.0 # via docker @@ -219,8 +218,6 @@ pymysql==1.0.2 # via testcontainers pynacl==1.5.0 # via paramiko -pyparsing==3.0.9 - # via packaging pyrsistent==0.19.2 # via jsonschema pysocks==1.7.1 @@ -252,7 +249,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.3.5 +redis==4.4.0 # via testcontainers requests==2.28.1 # via From b28ef0991354d1bafa57ef18ea0ad79ab5eaf367 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Fri, 9 Dec 2022 20:12:14 +0100 Subject: [PATCH 104/425] add .python-version file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d6f542436..f9ad98e59 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,4 @@ venv .vscode/ .DS_Store +.python-version \ No newline at end of file From c2e2fccd0373ef5b46b21862fd4d3a978712e455 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Fri, 9 Dec 2022 23:26:51 +0100 Subject: [PATCH 105/425] Add OpenSearchContainer --- .github/workflows/main.yml | 1 + README.rst | 1 + docs/index.rst | 1 + docs/opensearch.rst | 6 ++ requirements.in | 2 +- requirements/3.10.txt | 5 ++ requirements/3.7.txt | 5 ++ requirements/3.8.txt | 5 ++ requirements/3.9.txt | 5 ++ setup.py | 1 + testcontainers/opensearch.py | 105 +++++++++++++++++++++++++++++++++++ tests/test_opensearch.py | 36 ++++++++++++ 12 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 docs/opensearch.rst create mode 100644 testcontainers/opensearch.py create mode 100644 tests/test_opensearch.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a23509d70..b8b50edbc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,6 +67,7 @@ jobs: - keycloak.py - arangodb.py - azurite.py + - opensearch.py runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/README.rst b/README.rst index 488291985..cdc447d15 100644 --- a/README.rst +++ b/README.rst @@ -28,6 +28,7 @@ Currently available features: * Keycloak * Azurite container * Minio container +* OpeanSearch container Installation ------------ diff --git a/docs/index.rst b/docs/index.rst index 8404eb6da..f0348a72d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,4 +23,5 @@ Usage modes RabbitMQ container Redis container Minio container + OpeanSearch container diff --git a/docs/opensearch.rst b/docs/opensearch.rst new file mode 100644 index 000000000..44af25aba --- /dev/null +++ b/docs/opensearch.rst @@ -0,0 +1,6 @@ +OpeanSearch +=================== + +Allows to spin up OpeanSearch Container. + +.. autoclass:: testcontainers.opensearch.OpenSearchContainer diff --git a/requirements.in b/requirements.in index f41b1b145..79995814c 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,4 @@ --e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,minio,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite] +-e file:.[docker-compose,mysql,oracle,opensearch,postgresql,selenium,google-cloud-pubsub,minio,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite] codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 3c6ab5c12..916a831e8 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -40,6 +40,7 @@ certifi==2022.12.7 # via # minio # msrest + # opensearch-py # requests # selenium cffi==1.15.1 @@ -163,6 +164,8 @@ neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib +opensearch-py==2.0.1 + # via testcontainers outcome==1.2.0 # via trio packaging==22.0 @@ -258,6 +261,7 @@ requests==2.28.1 # docker-compose # google-api-core # msrest + # opensearch-py # python-arango # python-keycloak # requests-oauthlib @@ -343,6 +347,7 @@ urllib3[socks]==1.26.13 # via # docker # minio + # opensearch-py # python-arango # python-keycloak # requests diff --git a/requirements/3.7.txt b/requirements/3.7.txt index d3d946913..3550c0bcd 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -46,6 +46,7 @@ certifi==2022.12.7 # via # minio # msrest + # opensearch-py # requests # selenium cffi==1.15.1 @@ -177,6 +178,8 @@ neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib +opensearch-py==2.0.1 + # via testcontainers outcome==1.2.0 # via trio packaging==22.0 @@ -272,6 +275,7 @@ requests==2.28.1 # docker-compose # google-api-core # msrest + # opensearch-py # python-arango # python-keycloak # requests-oauthlib @@ -363,6 +367,7 @@ urllib3[socks]==1.26.13 # via # docker # minio + # opensearch-py # python-arango # python-keycloak # requests diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 15e458420..88fa3c371 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -44,6 +44,7 @@ certifi==2022.12.7 # via # minio # msrest + # opensearch-py # requests # selenium cffi==1.15.1 @@ -168,6 +169,8 @@ neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib +opensearch-py==2.0.1 + # via testcontainers outcome==1.2.0 # via trio packaging==22.0 @@ -263,6 +266,7 @@ requests==2.28.1 # docker-compose # google-api-core # msrest + # opensearch-py # python-arango # python-keycloak # requests-oauthlib @@ -350,6 +354,7 @@ urllib3[socks]==1.26.13 # via # docker # minio + # opensearch-py # python-arango # python-keycloak # requests diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 668cb04d4..ca4f23c45 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -40,6 +40,7 @@ certifi==2022.12.7 # via # minio # msrest + # opensearch-py # requests # selenium cffi==1.15.1 @@ -164,6 +165,8 @@ neo4j==5.3.0 # via testcontainers oauthlib==3.2.2 # via requests-oauthlib +opensearch-py==2.0.1 + # via testcontainers outcome==1.2.0 # via trio packaging==22.0 @@ -259,6 +262,7 @@ requests==2.28.1 # docker-compose # google-api-core # msrest + # opensearch-py # python-arango # python-keycloak # requests-oauthlib @@ -344,6 +348,7 @@ urllib3[socks]==1.26.13 # via # docker # minio + # opensearch-py # python-arango # python-keycloak # requests diff --git a/setup.py b/setup.py index 82bfe42d9..b56dd4a81 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,7 @@ 'keycloak': ['python-keycloak'], 'arangodb': ['python-arango'], 'azurite': ['azure-storage-blob'], + 'opensearch': ['opensearch-py'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/opensearch.py b/testcontainers/opensearch.py new file mode 100644 index 000000000..973d24b1e --- /dev/null +++ b/testcontainers/opensearch.py @@ -0,0 +1,105 @@ +from opensearchpy import OpenSearch +from opensearchpy.exceptions import ConnectionError, TransportError + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class OpenSearchContainer(DockerContainer): + """ + The following example demonstrates how to create a new index in an OpenSearch container + and add a document to it. It also shows how to search within the created index. The refresh + step in between makes sure that the newly created document is available for search. + + The method :code:`get_client` can be used to create a OpenSearch Python Client. + The method :code:`get_config` can be used to retrieve the host, port, user + and password of the container. + + Example + ------- + .. doctest:: + + >>> from testcontainers.opensearch import OpenSearchContainer + + >>> with OpenSearchContainer() as opensearch: + ... client = opensearch.get_client() + ... creation_result = client.index(index="test", body={"test": "test"}) + ... refresh_result = client.indices.refresh(index="test") + ... search_result = client.search(index="test", body={"query": {"match_all": {}}}) + """ + + def __init__( + self, + image="opensearchproject/opensearch:2.4.0", + port_to_expose=9200, + security_disabled=True, + **kwargs, + ): + """ + Args: + image (str, optional): The Docker image to use for the container. + Defaults to "opensearchproject/opensearch:2.4.0". + port_to_expose (int, optional): The port to expose on the container. + Defaults to 9200. + security_disabled (bool, optional): `True` disables the security plugin in OpenSearch. + Defaults to True. + """ + super(OpenSearchContainer, self).__init__(image, **kwargs) + self.port_to_expose = port_to_expose + self.security_disabled = security_disabled + + self.with_exposed_ports(self.port_to_expose) + self.with_env("discovery.type", "single-node") + self.with_env("plugins.security.disabled", f"{'true' if security_disabled else 'false'}") + if not security_disabled: + self.with_env("plugins.security.allow_default_init_securityindex", "true") + + def get_config(self): + """This method returns the configuration of the OpenSearch container, + including the host, port, user, and password. + + Returns: + dict: {`host`: str, `port`: str, `user`: str, `password`: str} + """ + + return { + "host": self.get_container_host_ip(), + "port": self.get_exposed_port(self.port_to_expose), + "user": "admin", + "password": "admin", + } + + def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: + """Returns a OpenSearch client to connect to the container. + + Returns: + OpenSearch: Python OpenSearch Client according to + https://opensearch.org/docs/latest/clients/python/ + """ + config = self.get_config() + return OpenSearch( + hosts=[ + { + "host": config["host"], + "port": config["port"], + } + ], + http_auth=(config["user"], config["password"]), + use_ssl=not self.security_disabled, + verify_certs=verify_certs, + **kwargs, + ) + + @wait_container_is_ready(ConnectionError, TransportError) + def _healthcheck(self): + """This is an internal method used to check if the OpenSearch container + is healthy and ready to receive requests.""" + client: OpenSearchContainer = self.get_client() + client.cluster.health(wait_for_status="green") + + def start(self): + """This method starts the OpenSearch container and runs the healthcheck + to verify that the container is ready to use.""" + super().start() + self._healthcheck() + return self diff --git a/tests/test_opensearch.py b/tests/test_opensearch.py new file mode 100644 index 000000000..f3923e84f --- /dev/null +++ b/tests/test_opensearch.py @@ -0,0 +1,36 @@ +from testcontainers.opensearch import OpenSearchContainer + + +def test_docker_run_opensearch(): + with OpenSearchContainer() as opensearch: + client = opensearch.get_client() + assert client.cluster.health()["status"] == "green" + + +def test_docker_run_opensearch_with_security(): + with OpenSearchContainer(security_disabled=False) as opensearch: + client = opensearch.get_client() + assert client.cluster.health()["status"] == "green" + + +def test_docker_run_opensearch_v1(): + with OpenSearchContainer(image="opensearchproject/opensearch:1.3.6") as opensearch: + client = opensearch.get_client() + assert client.cluster.health()["status"] == "green" + + +def test_docker_run_opensearch_v1_with_security(): + with OpenSearchContainer( + image="opensearchproject/opensearch:1.3.6", security_disabled=False + ) as opensearch: + client = opensearch.get_client() + assert client.cluster.health()["status"] == "green" + + +def test_search(): + with OpenSearchContainer() as opensearch: + client = opensearch.get_client() + client.index(index="test", body={"test": "test"}) + client.indices.refresh(index="test") + result = client.search(index="test", body={"query": {"match_all": {}}}) + assert result["hits"]["total"]["value"] == 1 From d488cb943e9b2d0d8f5969361d18ab0bf3256773 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Fri, 9 Dec 2022 23:29:17 +0100 Subject: [PATCH 106/425] add new line at the end --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f9ad98e59..bd980f755 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,4 @@ venv .vscode/ .DS_Store -.python-version \ No newline at end of file +.python-version From 4441c14908fe81beca0c86be5b17f85a8c86a5da Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Sat, 10 Dec 2022 00:23:05 +0100 Subject: [PATCH 107/425] Add missing and reorder containers --- README.rst | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index cdc447d15..141241194 100644 --- a/README.rst +++ b/README.rst @@ -12,23 +12,28 @@ Python port for testcontainers-java that allows using docker containers for func Currently available features: -* Selenium Grid containers -* Selenium Standalone containers -* MySql Db container -* MariaDb container -* Neo4j container -* OracleDb container -* PostgreSQL Db container -* ClickHouse container -* Microsoft SQL Server container -* Generic docker containers +* Generic docker container * ArangoDB container -* LocalStack -* RabbitMQ -* Keycloak * Azurite container +* ClickHouse container +* ElasticSearch container +* Kafka container +* Keycloak container +* LocalStack container +* MariaDb container * Minio container +* MongoDB container +* Microsoft SQL Server container +* MySql Db container +* Neo4j container +* NGINX container * OpeanSearch container +* OracleDb container +* PostgreSQL Db container +* RabbitMQ container +* Redis container +* Selenium Grid container +* Selenium Standalone container Installation ------------ From 326918e3ebb8c54885c25780c350055928c4065f Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Mon, 19 Dec 2022 15:55:22 +0000 Subject: [PATCH 108/425] Update docs/index.rst --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index f0348a72d..47773a299 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,5 +23,5 @@ Usage modes RabbitMQ container Redis container Minio container - OpeanSearch container + OpenSearch container From 38be1d46c7496d237e34970da57e21d96b9663a2 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Mon, 19 Dec 2022 15:55:55 +0000 Subject: [PATCH 109/425] Update docs/opensearch.rst --- docs/opensearch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/opensearch.rst b/docs/opensearch.rst index 44af25aba..68a23ba52 100644 --- a/docs/opensearch.rst +++ b/docs/opensearch.rst @@ -1,4 +1,4 @@ -OpeanSearch +OpenSearch =================== Allows to spin up OpeanSearch Container. From 0e4180a241aab40aa2091caebc3cb5a023842206 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Mon, 19 Dec 2022 15:56:54 +0000 Subject: [PATCH 110/425] Apply suggestions from code review --- README.rst | 2 +- docs/opensearch.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 141241194..ec948b6fa 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ Currently available features: * MySql Db container * Neo4j container * NGINX container -* OpeanSearch container +* OpenSearch container * OracleDb container * PostgreSQL Db container * RabbitMQ container diff --git a/docs/opensearch.rst b/docs/opensearch.rst index 68a23ba52..4e464217a 100644 --- a/docs/opensearch.rst +++ b/docs/opensearch.rst @@ -1,6 +1,6 @@ OpenSearch =================== -Allows to spin up OpeanSearch Container. +Allows to spin up OpenSearch Container. .. autoclass:: testcontainers.opensearch.OpenSearchContainer From cdb4b6d31a9af4e21aa07aef6ccc8aef603a4626 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Mon, 19 Dec 2022 19:55:51 +0100 Subject: [PATCH 111/425] security_enabled instead of security_disabled --- README.rst | 2 +- testcontainers/opensearch.py | 20 ++++++++++---------- tests/test_opensearch.py | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index ec948b6fa..b5f83416b 100644 --- a/README.rst +++ b/README.rst @@ -21,9 +21,9 @@ Currently available features: * Keycloak container * LocalStack container * MariaDb container +* Microsoft SQL Server container * Minio container * MongoDB container -* Microsoft SQL Server container * MySql Db container * Neo4j container * NGINX container diff --git a/testcontainers/opensearch.py b/testcontainers/opensearch.py index 973d24b1e..22c147e7a 100644 --- a/testcontainers/opensearch.py +++ b/testcontainers/opensearch.py @@ -32,7 +32,7 @@ def __init__( self, image="opensearchproject/opensearch:2.4.0", port_to_expose=9200, - security_disabled=True, + security_enabled=False, **kwargs, ): """ @@ -41,17 +41,17 @@ def __init__( Defaults to "opensearchproject/opensearch:2.4.0". port_to_expose (int, optional): The port to expose on the container. Defaults to 9200. - security_disabled (bool, optional): `True` disables the security plugin in OpenSearch. - Defaults to True. + security_enabled (bool, optional): `False` disables the security plugin in OpenSearch. + Defaults to False. """ super(OpenSearchContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose - self.security_disabled = security_disabled + self.security_enabled = security_enabled self.with_exposed_ports(self.port_to_expose) self.with_env("discovery.type", "single-node") - self.with_env("plugins.security.disabled", f"{'true' if security_disabled else 'false'}") - if not security_disabled: + self.with_env("plugins.security.disabled", f"{'false' if security_enabled else 'true'}") + if security_enabled: self.with_env("plugins.security.allow_default_init_securityindex", "true") def get_config(self): @@ -78,15 +78,15 @@ def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: """ config = self.get_config() return OpenSearch( - hosts=[ + hosts = [ { "host": config["host"], "port": config["port"], } ], - http_auth=(config["user"], config["password"]), - use_ssl=not self.security_disabled, - verify_certs=verify_certs, + http_auth = (config["user"], config["password"]), + use_ssl = self.security_enabled, + verify_certs = verify_certs, **kwargs, ) diff --git a/tests/test_opensearch.py b/tests/test_opensearch.py index f3923e84f..f5fb411e1 100644 --- a/tests/test_opensearch.py +++ b/tests/test_opensearch.py @@ -8,7 +8,7 @@ def test_docker_run_opensearch(): def test_docker_run_opensearch_with_security(): - with OpenSearchContainer(security_disabled=False) as opensearch: + with OpenSearchContainer(security_enabled=True) as opensearch: client = opensearch.get_client() assert client.cluster.health()["status"] == "green" @@ -21,7 +21,7 @@ def test_docker_run_opensearch_v1(): def test_docker_run_opensearch_v1_with_security(): with OpenSearchContainer( - image="opensearchproject/opensearch:1.3.6", security_disabled=False + image="opensearchproject/opensearch:1.3.6", security_enabled=True ) as opensearch: client = opensearch.get_client() assert client.cluster.health()["status"] == "green" From d6830cf4b2f11ee14a1ef1caabffe5d2c6281c37 Mon Sep 17 00:00:00 2001 From: Malte Hedderich Date: Mon, 19 Dec 2022 19:58:50 +0100 Subject: [PATCH 112/425] Flake8 fixes --- testcontainers/opensearch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testcontainers/opensearch.py b/testcontainers/opensearch.py index 22c147e7a..206dec056 100644 --- a/testcontainers/opensearch.py +++ b/testcontainers/opensearch.py @@ -78,15 +78,15 @@ def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: """ config = self.get_config() return OpenSearch( - hosts = [ + hosts=[ { "host": config["host"], "port": config["port"], } ], - http_auth = (config["user"], config["password"]), - use_ssl = self.security_enabled, - verify_certs = verify_certs, + http_auth=(config["user"], config["password"]), + use_ssl=self.security_enabled, + verify_certs=verify_certs, **kwargs, ) From ba179a3d0b11e4bdd28ab23009bef62ac8011eaf Mon Sep 17 00:00:00 2001 From: spicy-sauce Date: Wed, 28 Dec 2022 13:10:34 +0200 Subject: [PATCH 113/425] expose the ports in the constructor --- testcontainers/mssql.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testcontainers/mssql.py b/testcontainers/mssql.py index e3f9dea68..19874b26c 100644 --- a/testcontainers/mssql.py +++ b/testcontainers/mssql.py @@ -30,12 +30,13 @@ def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA" self.SQLSERVER_PASSWORD = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") self.port_to_expose = port + self.with_exposed_ports(self.port_to_expose) + self.SQLSERVER_USER = user self.SQLSERVER_DBNAME = dbname self.dialect = dialect def _configure(self): - self.with_exposed_ports(self.port_to_expose) self.with_env("SA_PASSWORD", self.SQLSERVER_PASSWORD) self.with_env("SQLSERVER_USER", self.SQLSERVER_USER) self.with_env("SQLSERVER_DBNAME", self.SQLSERVER_DBNAME) From e49f6e5fb2b17773b9e82db76c136e281a8d811e Mon Sep 17 00:00:00 2001 From: spicy-sauce Date: Wed, 28 Dec 2022 13:30:27 +0200 Subject: [PATCH 114/425] styling --- testcontainers/mssql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/mssql.py b/testcontainers/mssql.py index 19874b26c..6dd623181 100644 --- a/testcontainers/mssql.py +++ b/testcontainers/mssql.py @@ -28,10 +28,10 @@ def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA" port=1433, dbname="tempdb", dialect='mssql+pymssql', **kwargs): super(SqlServerContainer, self).__init__(image, **kwargs) - self.SQLSERVER_PASSWORD = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) + self.SQLSERVER_PASSWORD = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") self.SQLSERVER_USER = user self.SQLSERVER_DBNAME = dbname self.dialect = dialect From 9920b93642838d4f2e13b6cccbf04d73ce1ad10b Mon Sep 17 00:00:00 2001 From: Malte Hedderich <12952192+maltehedderich@users.noreply.github.com> Date: Thu, 29 Dec 2022 15:20:28 +0100 Subject: [PATCH 115/425] Update testcontainers/opensearch.py Co-authored-by: Till Hoffmann --- testcontainers/opensearch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers/opensearch.py b/testcontainers/opensearch.py index 206dec056..36d6addcb 100644 --- a/testcontainers/opensearch.py +++ b/testcontainers/opensearch.py @@ -50,7 +50,7 @@ def __init__( self.with_exposed_ports(self.port_to_expose) self.with_env("discovery.type", "single-node") - self.with_env("plugins.security.disabled", f"{'false' if security_enabled else 'true'}") + self.with_env("plugins.security.disabled", "false" if security_enabled else "true") if security_enabled: self.with_env("plugins.security.allow_default_init_securityindex", "true") From bec7e20bf781e05cc0f9d20b5012425389e203b2 Mon Sep 17 00:00:00 2001 From: Ilia Kravets Date: Mon, 26 Dec 2022 15:09:24 +0200 Subject: [PATCH 116/425] make dev version PEP 440 compatible Some package managers (e.g. poetry) may enforce PEP 440 even when installing development-only version. This change supports such a use case. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b56dd4a81..9b355e51c 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ with open('VERSION') as fp: version = fp.read().strip() except FileNotFoundError: - version = 'dev' + version = '0.dev0' setuptools.setup( name='testcontainers', From b67b590568532720e8e7b9f8bda6dee74a0833ca Mon Sep 17 00:00:00 2001 From: Mark Andreev Date: Sun, 24 Apr 2022 14:35:13 +0400 Subject: [PATCH 117/425] Add ResourceCleaner for stop containers after exit (#104) --- testcontainers/core/cleaner.py | 36 ++++++++++++++++++++++++++++ testcontainers/core/docker_client.py | 22 ++++++++++------- 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 testcontainers/core/cleaner.py diff --git a/testcontainers/core/cleaner.py b/testcontainers/core/cleaner.py new file mode 100644 index 000000000..8b248fdf9 --- /dev/null +++ b/testcontainers/core/cleaner.py @@ -0,0 +1,36 @@ +import atexit + +from docker.errors import NotFound + +from testcontainers.core.utils import setup_logger + +logger = setup_logger(__name__) + + +class ResourceCleaner: + __INSTANCE = None + + def __init__(self): + self.containers = [] + + def attach(self, container): + self.containers.append(container) + + return container + + def clean(self): + for c in self.containers: + try: + c.stop() + except NotFound: + pass + except Exception as e: + logger.exception(e) + + @classmethod + def instance(cls): + if cls.__INSTANCE is None: + cls.__INSTANCE = ResourceCleaner() + atexit.register(cls.__INSTANCE.clean) + + return cls.__INSTANCE diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index e10537aa5..c40c0aab3 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -14,6 +14,8 @@ import urllib import docker from docker.models.containers import Container + +from testcontainers.core.cleaner import ResourceCleaner from testcontainers.core.utils import inside_container from testcontainers.core.utils import default_gateway_ip @@ -30,15 +32,17 @@ def run(self, image: str, stdout: bool = True, stderr: bool = False, remove: bool = False, **kwargs) -> Container: - return self.client.containers.run(image, - command=command, - stdout=stdout, - stderr=stderr, - remove=remove, - detach=detach, - environment=environment, - ports=ports, - **kwargs) + return ResourceCleaner.instance().attach( + self.client.containers.run(image, + command=command, + stdout=stdout, + stderr=stderr, + remove=remove, + detach=detach, + environment=environment, + ports=ports, + **kwargs) + ) def port(self, container_id, port): port_mappings = self.client.api.port(container_id, port) From 1774c55322d92197520eecabc6ce6d919fa1d5de Mon Sep 17 00:00:00 2001 From: Mark Andreev Date: Tue, 26 Apr 2022 13:55:14 +0400 Subject: [PATCH 118/425] Add stop_silent for stop containers after exit (#104) --- testcontainers/core/cleaner.py | 38 +++++++--------------------- testcontainers/core/docker_client.py | 10 +++++--- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/testcontainers/core/cleaner.py b/testcontainers/core/cleaner.py index 8b248fdf9..f7359af64 100644 --- a/testcontainers/core/cleaner.py +++ b/testcontainers/core/cleaner.py @@ -1,5 +1,3 @@ -import atexit - from docker.errors import NotFound from testcontainers.core.utils import setup_logger @@ -7,30 +5,12 @@ logger = setup_logger(__name__) -class ResourceCleaner: - __INSTANCE = None - - def __init__(self): - self.containers = [] - - def attach(self, container): - self.containers.append(container) - - return container - - def clean(self): - for c in self.containers: - try: - c.stop() - except NotFound: - pass - except Exception as e: - logger.exception(e) - - @classmethod - def instance(cls): - if cls.__INSTANCE is None: - cls.__INSTANCE = ResourceCleaner() - atexit.register(cls.__INSTANCE.clean) - - return cls.__INSTANCE +def stop_silent(container): + def wrapper(): + try: + container.stop() + except NotFound: + pass + except Exception as e: + logger.exception(e) + return wrapper diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index c40c0aab3..9a80903d8 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -10,12 +10,13 @@ # 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 atexit import os import urllib import docker from docker.models.containers import Container -from testcontainers.core.cleaner import ResourceCleaner +from testcontainers.core.cleaner import stop_silent from testcontainers.core.utils import inside_container from testcontainers.core.utils import default_gateway_ip @@ -32,8 +33,7 @@ def run(self, image: str, stdout: bool = True, stderr: bool = False, remove: bool = False, **kwargs) -> Container: - return ResourceCleaner.instance().attach( - self.client.containers.run(image, + container = self.client.containers.run(image, command=command, stdout=stdout, stderr=stderr, @@ -42,7 +42,9 @@ def run(self, image: str, environment=environment, ports=ports, **kwargs) - ) + atexit.register(stop_silent(container)) + + return container def port(self, container_id, port): port_mappings = self.client.api.port(container_id, port) From faf48cc98c25a10212095ea6683d488bad3abddb Mon Sep 17 00:00:00 2001 From: Mark Andreev Date: Wed, 27 Apr 2022 22:54:14 +0400 Subject: [PATCH 119/425] Add stop_silent for stop containers after exit (#104) --- testcontainers/core/cleaner.py | 14 ++++++-------- testcontainers/core/docker_client.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/testcontainers/core/cleaner.py b/testcontainers/core/cleaner.py index f7359af64..db4b7c4fa 100644 --- a/testcontainers/core/cleaner.py +++ b/testcontainers/core/cleaner.py @@ -6,11 +6,9 @@ def stop_silent(container): - def wrapper(): - try: - container.stop() - except NotFound: - pass - except Exception as e: - logger.exception(e) - return wrapper + try: + container.stop() + except NotFound: + pass + except Exception as e: + logger.exception(e) diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index 9a80903d8..84da9dbfa 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -42,7 +42,7 @@ def run(self, image: str, environment=environment, ports=ports, **kwargs) - atexit.register(stop_silent(container)) + atexit.register(stop_silent, container) return container From d32e089918e9f381685da03f69eb9522d5af89dc Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 10:50:41 -0500 Subject: [PATCH 120/425] Fix linting errors and add logging. --- testcontainers/core/cleaner.py | 14 ------------ testcontainers/core/docker_client.py | 34 ++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 24 deletions(-) delete mode 100644 testcontainers/core/cleaner.py diff --git a/testcontainers/core/cleaner.py b/testcontainers/core/cleaner.py deleted file mode 100644 index db4b7c4fa..000000000 --- a/testcontainers/core/cleaner.py +++ /dev/null @@ -1,14 +0,0 @@ -from docker.errors import NotFound - -from testcontainers.core.utils import setup_logger - -logger = setup_logger(__name__) - - -def stop_silent(container): - try: - container.stop() - except NotFound: - pass - except Exception as e: - logger.exception(e) diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index 84da9dbfa..2af50f716 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -14,11 +14,25 @@ import os import urllib import docker +from docker.errors import NotFound from docker.models.containers import Container -from testcontainers.core.cleaner import stop_silent from testcontainers.core.utils import inside_container from testcontainers.core.utils import default_gateway_ip +from testcontainers.core.utils import setup_logger + + +LOGGER = setup_logger(__name__) + + +def _stop_container(container): + try: + container.stop() + except NotFound: + pass + except Exception as ex: + LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, + container.image, ex) class DockerClient(object): @@ -34,15 +48,15 @@ def run(self, image: str, stderr: bool = False, remove: bool = False, **kwargs) -> Container: container = self.client.containers.run(image, - command=command, - stdout=stdout, - stderr=stderr, - remove=remove, - detach=detach, - environment=environment, - ports=ports, - **kwargs) - atexit.register(stop_silent, container) + command=command, + stdout=stdout, + stderr=stderr, + remove=remove, + detach=detach, + environment=environment, + ports=ports, + **kwargs) + atexit.register(_stop_container, container) return container From c20cacd2354ecfd1b255ef2128375118d96d3b71 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 16:41:56 -0500 Subject: [PATCH 121/425] Split testcontainers into namespace packages. --- Makefile | 2 +- arangodb/setup.py | 14 ++ .../testcontainers/arangodb/__init__.py | 0 azurite/setup.py | 14 ++ .../testcontainers/azurite/__init__.py | 0 clickhouse/setup.py | 14 ++ .../testcontainers/clickhouse/__init__.py | 0 compose/setup.py | 14 ++ .../testcontainers/compose/__init__.py | 0 core/setup.py | 15 ++ .../testcontainers/core}/__init__.py | 0 .../testcontainers}/core/config.py | 0 .../testcontainers}/core/container.py | 0 .../testcontainers}/core/docker_client.py | 0 .../testcontainers}/core/exceptions.py | 0 .../testcontainers}/core/generic.py | 0 .../testcontainers}/core/utils.py | 0 .../testcontainers}/core/waiting_utils.py | 0 elasticsearch/setup.py | 13 ++ .../testcontainers/elasticsearch/__init__.py | 0 google/setup.py | 14 ++ .../testcontainers}/google/__init__.py | 0 .../testcontainers}/google/pubsub.py | 2 +- kafka/setup.py | 14 ++ .../testcontainers/kafka/__init__.py | 0 keycloak/setup.py | 14 ++ .../testcontainers/keycloak/__init__.py | 0 localstack/setup.py | 13 ++ .../testcontainers/localstack/__init__.py | 0 minio/setup.py | 14 ++ .../testcontainers/minio/__init__.py | 0 mongodb/setup.py | 14 ++ .../testcontainers/mongodb/__init__.py | 0 mssql/setup.py | 14 ++ .../testcontainers/mssql/__init__.py | 0 mysql/setup.py | 15 ++ .../testcontainers/mysql/__init__.py | 0 neo4j/setup.py | 14 ++ .../testcontainers/neo4j/__init__.py | 0 nginx/setup.py | 13 ++ .../testcontainers/nginx/__init__.py | 0 opensearch/setup.py | 14 ++ .../testcontainers/opensearch/__init__.py | 0 oracle/setup.py | 15 ++ .../testcontainers/oracle/__init__.py | 0 postgres/setup.py | 15 ++ .../testcontainers/postgres/__init__.py | 0 rabbitmq/setup.py | 14 ++ .../testcontainers/rabbitmq/__init__.py | 0 redis/setup.py | 14 ++ .../testcontainers/redis/__init__.py | 0 requirements.in | 23 ++- requirements/3.10.txt | 147 ++++++++++++----- requirements/3.7.txt | 151 ++++++++++++----- requirements/3.8.txt | 153 +++++++++++++----- requirements/3.9.txt | 147 ++++++++++++----- selenium/setup.py | 14 ++ .../testcontainers/selenium/__init__.py | 0 setup.py | 45 +++--- testcontainers/core/__init__.py | 0 testcontainers/general.py | 23 --- 61 files changed, 789 insertions(+), 213 deletions(-) create mode 100644 arangodb/setup.py rename testcontainers/arangodb.py => arangodb/testcontainers/arangodb/__init__.py (100%) create mode 100644 azurite/setup.py rename testcontainers/azurite.py => azurite/testcontainers/azurite/__init__.py (100%) create mode 100644 clickhouse/setup.py rename testcontainers/clickhouse.py => clickhouse/testcontainers/clickhouse/__init__.py (100%) create mode 100644 compose/setup.py rename testcontainers/compose.py => compose/testcontainers/compose/__init__.py (100%) create mode 100644 core/setup.py rename {testcontainers => core/testcontainers/core}/__init__.py (100%) rename {testcontainers => core/testcontainers}/core/config.py (100%) rename {testcontainers => core/testcontainers}/core/container.py (100%) rename {testcontainers => core/testcontainers}/core/docker_client.py (100%) rename {testcontainers => core/testcontainers}/core/exceptions.py (100%) rename {testcontainers => core/testcontainers}/core/generic.py (100%) rename {testcontainers => core/testcontainers}/core/utils.py (100%) rename {testcontainers => core/testcontainers}/core/waiting_utils.py (100%) create mode 100644 elasticsearch/setup.py rename testcontainers/elasticsearch.py => elasticsearch/testcontainers/elasticsearch/__init__.py (100%) create mode 100644 google/setup.py rename {testcontainers => google/testcontainers}/google/__init__.py (100%) rename {testcontainers => google/testcontainers}/google/pubsub.py (97%) create mode 100644 kafka/setup.py rename testcontainers/kafka.py => kafka/testcontainers/kafka/__init__.py (100%) create mode 100644 keycloak/setup.py rename testcontainers/keycloak.py => keycloak/testcontainers/keycloak/__init__.py (100%) create mode 100644 localstack/setup.py rename testcontainers/localstack.py => localstack/testcontainers/localstack/__init__.py (100%) create mode 100644 minio/setup.py rename testcontainers/minio.py => minio/testcontainers/minio/__init__.py (100%) create mode 100644 mongodb/setup.py rename testcontainers/mongodb.py => mongodb/testcontainers/mongodb/__init__.py (100%) create mode 100644 mssql/setup.py rename testcontainers/mssql.py => mssql/testcontainers/mssql/__init__.py (100%) create mode 100644 mysql/setup.py rename testcontainers/mysql.py => mysql/testcontainers/mysql/__init__.py (100%) create mode 100644 neo4j/setup.py rename testcontainers/neo4j.py => neo4j/testcontainers/neo4j/__init__.py (100%) create mode 100644 nginx/setup.py rename testcontainers/nginx.py => nginx/testcontainers/nginx/__init__.py (100%) create mode 100644 opensearch/setup.py rename testcontainers/opensearch.py => opensearch/testcontainers/opensearch/__init__.py (100%) create mode 100644 oracle/setup.py rename testcontainers/oracle.py => oracle/testcontainers/oracle/__init__.py (100%) create mode 100644 postgres/setup.py rename testcontainers/postgres.py => postgres/testcontainers/postgres/__init__.py (100%) create mode 100644 rabbitmq/setup.py rename testcontainers/rabbitmq.py => rabbitmq/testcontainers/rabbitmq/__init__.py (100%) create mode 100644 redis/setup.py rename testcontainers/redis.py => redis/testcontainers/redis/__init__.py (100%) create mode 100644 selenium/setup.py rename testcontainers/selenium.py => selenium/testcontainers/selenium/__init__.py (100%) delete mode 100644 testcontainers/core/__init__.py delete mode 100644 testcontainers/general.py diff --git a/Makefile b/Makefile index f7e2981d0..1eb787fbe 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ default : tests/3.8 requirements : ${REQUIREMENTS} -${REQUIREMENTS} : requirements/%.txt : requirements.in setup.py +${REQUIREMENTS} : requirements/%.txt : requirements.in */setup.py mkdir -p $(dir $@) ${RUN} -w /workspace -v `pwd`:/workspace --platform=linux/amd64 python:$* bash -c \ "pip install pip-tools && pip-compile --resolver=backtracking -v --upgrade -o $@ $<" diff --git a/arangodb/setup.py b/arangodb/setup.py new file mode 100644 index 000000000..4952cd39e --- /dev/null +++ b/arangodb/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-arangodb", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Arango DB component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "python-arango", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/arangodb.py b/arangodb/testcontainers/arangodb/__init__.py similarity index 100% rename from testcontainers/arangodb.py rename to arangodb/testcontainers/arangodb/__init__.py diff --git a/azurite/setup.py b/azurite/setup.py new file mode 100644 index 000000000..37bbc396e --- /dev/null +++ b/azurite/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-azurite", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Core component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "azure-storage-blob", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/azurite.py b/azurite/testcontainers/azurite/__init__.py similarity index 100% rename from testcontainers/azurite.py rename to azurite/testcontainers/azurite/__init__.py diff --git a/clickhouse/setup.py b/clickhouse/setup.py new file mode 100644 index 000000000..16250494d --- /dev/null +++ b/clickhouse/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-clickhouse", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Clickhouse component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "clickhouse-driver", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/clickhouse.py b/clickhouse/testcontainers/clickhouse/__init__.py similarity index 100% rename from testcontainers/clickhouse.py rename to clickhouse/testcontainers/clickhouse/__init__.py diff --git a/compose/setup.py b/compose/setup.py new file mode 100644 index 000000000..a3643e68d --- /dev/null +++ b/compose/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-compose", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Docker Compose component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "docker-compose", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/compose.py b/compose/testcontainers/compose/__init__.py similarity index 100% rename from testcontainers/compose.py rename to compose/testcontainers/compose/__init__.py diff --git a/core/setup.py b/core/setup.py new file mode 100644 index 000000000..a469b4a8a --- /dev/null +++ b/core/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-core", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Core component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "docker>=4.0.0", + "wrapt", + "deprecation", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/__init__.py b/core/testcontainers/core/__init__.py similarity index 100% rename from testcontainers/__init__.py rename to core/testcontainers/core/__init__.py diff --git a/testcontainers/core/config.py b/core/testcontainers/core/config.py similarity index 100% rename from testcontainers/core/config.py rename to core/testcontainers/core/config.py diff --git a/testcontainers/core/container.py b/core/testcontainers/core/container.py similarity index 100% rename from testcontainers/core/container.py rename to core/testcontainers/core/container.py diff --git a/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py similarity index 100% rename from testcontainers/core/docker_client.py rename to core/testcontainers/core/docker_client.py diff --git a/testcontainers/core/exceptions.py b/core/testcontainers/core/exceptions.py similarity index 100% rename from testcontainers/core/exceptions.py rename to core/testcontainers/core/exceptions.py diff --git a/testcontainers/core/generic.py b/core/testcontainers/core/generic.py similarity index 100% rename from testcontainers/core/generic.py rename to core/testcontainers/core/generic.py diff --git a/testcontainers/core/utils.py b/core/testcontainers/core/utils.py similarity index 100% rename from testcontainers/core/utils.py rename to core/testcontainers/core/utils.py diff --git a/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py similarity index 100% rename from testcontainers/core/waiting_utils.py rename to core/testcontainers/core/waiting_utils.py diff --git a/elasticsearch/setup.py b/elasticsearch/setup.py new file mode 100644 index 000000000..6954a62f7 --- /dev/null +++ b/elasticsearch/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-elasticsearch", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Elasticsearch component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/elasticsearch.py b/elasticsearch/testcontainers/elasticsearch/__init__.py similarity index 100% rename from testcontainers/elasticsearch.py rename to elasticsearch/testcontainers/elasticsearch/__init__.py diff --git a/google/setup.py b/google/setup.py new file mode 100644 index 000000000..b1ba077d6 --- /dev/null +++ b/google/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-gcp", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Google Cloud Platform component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "google-cloud-pubsub < 2", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/google/__init__.py b/google/testcontainers/google/__init__.py similarity index 100% rename from testcontainers/google/__init__.py rename to google/testcontainers/google/__init__.py diff --git a/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py similarity index 97% rename from testcontainers/google/pubsub.py rename to google/testcontainers/google/pubsub.py index 26d5b71fa..b15f4f854 100644 --- a/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -from ..core.container import DockerContainer +from testcontainers.core.container import DockerContainer class PubSubContainer(DockerContainer): diff --git a/kafka/setup.py b/kafka/setup.py new file mode 100644 index 000000000..f8798dc93 --- /dev/null +++ b/kafka/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-kafka", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Kafka component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "kafka-python", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/kafka.py b/kafka/testcontainers/kafka/__init__.py similarity index 100% rename from testcontainers/kafka.py rename to kafka/testcontainers/kafka/__init__.py diff --git a/keycloak/setup.py b/keycloak/setup.py new file mode 100644 index 000000000..091624743 --- /dev/null +++ b/keycloak/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-keycloak", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Keycloak component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "python-keycloak", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/keycloak.py b/keycloak/testcontainers/keycloak/__init__.py similarity index 100% rename from testcontainers/keycloak.py rename to keycloak/testcontainers/keycloak/__init__.py diff --git a/localstack/setup.py b/localstack/setup.py new file mode 100644 index 000000000..3ede68214 --- /dev/null +++ b/localstack/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-localstack", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="LocalStack component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/localstack.py b/localstack/testcontainers/localstack/__init__.py similarity index 100% rename from testcontainers/localstack.py rename to localstack/testcontainers/localstack/__init__.py diff --git a/minio/setup.py b/minio/setup.py new file mode 100644 index 000000000..1ac782d41 --- /dev/null +++ b/minio/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-minio", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="MinIO component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "minio", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/minio.py b/minio/testcontainers/minio/__init__.py similarity index 100% rename from testcontainers/minio.py rename to minio/testcontainers/minio/__init__.py diff --git a/mongodb/setup.py b/mongodb/setup.py new file mode 100644 index 000000000..e219f3c05 --- /dev/null +++ b/mongodb/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-mongodb", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="MongoDB component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "pymongo", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/mongodb.py b/mongodb/testcontainers/mongodb/__init__.py similarity index 100% rename from testcontainers/mongodb.py rename to mongodb/testcontainers/mongodb/__init__.py diff --git a/mssql/setup.py b/mssql/setup.py new file mode 100644 index 000000000..5fb9da6ae --- /dev/null +++ b/mssql/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-mssql", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Microsoft SQL Server component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "pymssql", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/mssql.py b/mssql/testcontainers/mssql/__init__.py similarity index 100% rename from testcontainers/mssql.py rename to mssql/testcontainers/mssql/__init__.py diff --git a/mysql/setup.py b/mysql/setup.py new file mode 100644 index 000000000..59d324ba2 --- /dev/null +++ b/mysql/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-mysql", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="MySQL component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "sqlalchemy", + "pymysql" + ], + python_requires=">=3.7", +) diff --git a/testcontainers/mysql.py b/mysql/testcontainers/mysql/__init__.py similarity index 100% rename from testcontainers/mysql.py rename to mysql/testcontainers/mysql/__init__.py diff --git a/neo4j/setup.py b/neo4j/setup.py new file mode 100644 index 000000000..154b0beb4 --- /dev/null +++ b/neo4j/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-neo4j", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Neo4j component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "neo4j", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/neo4j.py b/neo4j/testcontainers/neo4j/__init__.py similarity index 100% rename from testcontainers/neo4j.py rename to neo4j/testcontainers/neo4j/__init__.py diff --git a/nginx/setup.py b/nginx/setup.py new file mode 100644 index 000000000..28eb17010 --- /dev/null +++ b/nginx/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-nginx", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="NGINX component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/nginx.py b/nginx/testcontainers/nginx/__init__.py similarity index 100% rename from testcontainers/nginx.py rename to nginx/testcontainers/nginx/__init__.py diff --git a/opensearch/setup.py b/opensearch/setup.py new file mode 100644 index 000000000..674c7b028 --- /dev/null +++ b/opensearch/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-opensearch", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="OpenSearch component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "opensearch-py", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/opensearch.py b/opensearch/testcontainers/opensearch/__init__.py similarity index 100% rename from testcontainers/opensearch.py rename to opensearch/testcontainers/opensearch/__init__.py diff --git a/oracle/setup.py b/oracle/setup.py new file mode 100644 index 000000000..0416937ae --- /dev/null +++ b/oracle/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-oracle", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Oracle component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "sqlalchemy", + "cx_Oracle", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/oracle.py b/oracle/testcontainers/oracle/__init__.py similarity index 100% rename from testcontainers/oracle.py rename to oracle/testcontainers/oracle/__init__.py diff --git a/postgres/setup.py b/postgres/setup.py new file mode 100644 index 000000000..516f4fd0e --- /dev/null +++ b/postgres/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-postgres", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="PostgreSQL component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "sqlalchemy", + "psycopg2-binary", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/postgres.py b/postgres/testcontainers/postgres/__init__.py similarity index 100% rename from testcontainers/postgres.py rename to postgres/testcontainers/postgres/__init__.py diff --git a/rabbitmq/setup.py b/rabbitmq/setup.py new file mode 100644 index 000000000..a19897edf --- /dev/null +++ b/rabbitmq/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-rabbitmq", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="RabbitMQ component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "pika", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/rabbitmq.py b/rabbitmq/testcontainers/rabbitmq/__init__.py similarity index 100% rename from testcontainers/rabbitmq.py rename to rabbitmq/testcontainers/rabbitmq/__init__.py diff --git a/redis/setup.py b/redis/setup.py new file mode 100644 index 000000000..fe713341d --- /dev/null +++ b/redis/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-redis", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Redis component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "redis", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/redis.py b/redis/testcontainers/redis/__init__.py similarity index 100% rename from testcontainers/redis.py rename to redis/testcontainers/redis/__init__.py diff --git a/requirements.in b/requirements.in index 79995814c..eab7d18bf 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,25 @@ --e file:.[docker-compose,mysql,oracle,opensearch,postgresql,selenium,google-cloud-pubsub,minio,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite] +-e file:core +-e file:arangodb +-e file:azurite +-e file:clickhouse +-e file:compose +-e file:elasticsearch +-e file:google +-e file:kafka +-e file:keycloak +-e file:localstack +-e file:minio +-e file:mongodb +-e file:mssql +-e file:mysql +-e file:neo4j +-e file:nginx +-e file:opensearch +-e file:oracle +-e file:postgres +-e file:rabbitmq +-e file:redis +-e file:selenium codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 916a831e8..8533a5cad 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -4,7 +4,71 @@ # # pip-compile --output-file=requirements/3.10.txt --resolver=backtracking requirements.in # --e file:. +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium # via -r requirements.in alabaster==0.7.12 # via sphinx @@ -16,18 +80,18 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==22.1.0 +attrs==22.2.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.26.1 +azure-core==1.26.2 # via # azure-storage-blob # msrest azure-storage-blob==12.14.1 - # via testcontainers + # via testcontainers-azurite babel==2.11.0 # via sphinx bcrypt==4.0.1 @@ -50,12 +114,12 @@ cffi==1.15.1 charset-normalizer==2.1.1 # via requests clickhouse-driver==0.2.5 - # via testcontainers + # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in commonmark==0.9.1 # via rich -coverage[toml]==6.5.0 +coverage[toml]==7.0.3 # via # codecov # pytest-cov @@ -66,9 +130,9 @@ cryptography==36.0.2 # paramiko # secretstorage cx-oracle==8.3.0 - # via testcontainers + # via testcontainers-oracle deprecation==2.1.0 - # via testcontainers + # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -76,9 +140,9 @@ dnspython==2.2.1 docker[ssh]==6.0.1 # via # docker-compose - # testcontainers + # testcontainers-core docker-compose==1.29.2 - # via testcontainers + # via testcontainers-compose dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -91,7 +155,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # pytest # trio @@ -102,8 +166,8 @@ google-api-core[grpc]==2.11.0 google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 - # via testcontainers -googleapis-common-protos[grpc]==1.57.0 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.57.1 # via # google-api-core # grpc-google-iam-v1 @@ -128,7 +192,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.1.0 +importlib-metadata==6.0.0 # via # keyring # twine @@ -147,25 +211,25 @@ jinja2==3.1.2 jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 - # via testcontainers -keyring==23.11.0 + # via testcontainers-kafka +keyring==23.13.1 # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 minio==7.1.12 - # via testcontainers + # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob neo4j==5.3.0 - # via testcontainers + # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib opensearch-py==2.0.1 - # via testcontainers + # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==22.0 @@ -176,11 +240,11 @@ packaging==22.0 # sphinx paramiko==2.12.0 # via docker -pg8000==1.29.3 +pg8000==1.29.4 # via -r requirements.in pika==1.3.1 - # via testcontainers -pkginfo==1.9.2 + # via testcontainers-rabbitmq +pkginfo==1.9.4 # via twine pluggy==1.0.0 # via pytest @@ -191,7 +255,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.5 - # via testcontainers + # via testcontainers-postgres pyasn1==0.4.8 # via # pyasn1-modules @@ -205,7 +269,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.13.0 +pygments==2.14.0 # via # readme-renderer # rich @@ -213,14 +277,14 @@ pygments==2.13.0 pyjwt==2.6.0 # via python-arango pymongo==4.3.3 - # via testcontainers + # via testcontainers-mongodb pymssql==2.2.7 - # via testcontainers + # via testcontainers-mssql pymysql==1.0.2 - # via testcontainers + # via testcontainers-mysql pynacl==1.5.0 # via paramiko -pyrsistent==0.19.2 +pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 @@ -230,17 +294,17 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.3 - # via testcontainers +python-arango==7.5.4 + # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.6.0 - # via testcontainers -pytz==2022.6 +python-keycloak==2.8.0 + # via testcontainers-keycloak +pytz==2022.7 # via # babel # clickhouse-driver @@ -252,7 +316,7 @@ pyyaml==5.4.1 readme-renderer==37.3 # via twine redis==4.4.0 - # via testcontainers + # via testcontainers-redis requests==2.28.1 # via # azure-core @@ -277,7 +341,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==12.6.0 +rich==13.0.0 # via twine rsa==4.9 # via @@ -288,7 +352,7 @@ scramp==1.4.4 secretstorage==3.3.3 # via keyring selenium==4.7.2 - # via testcontainers + # via testcontainers-selenium six==1.16.0 # via # azure-core @@ -307,7 +371,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.3.0 +sphinx==6.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -321,8 +385,11 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.44 - # via testcontainers +sqlalchemy==1.4.46 + # via + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres texttable==1.6.7 # via docker-compose tomli==2.0.1 @@ -360,7 +427,7 @@ websocket-client==0.59.0 # docker # docker-compose wrapt==1.14.1 - # via testcontainers + # via testcontainers-core wsproto==1.2.0 # via trio-websocket zipp==3.11.0 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 3550c0bcd..bf83cf3d2 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -4,7 +4,71 @@ # # pip-compile --output-file=requirements/3.7.txt --resolver=backtracking requirements.in # --e file:. +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium # via -r requirements.in alabaster==0.7.12 # via sphinx @@ -16,18 +80,18 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==22.1.0 +attrs==22.2.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.26.1 +azure-core==1.26.2 # via # azure-storage-blob # msrest azure-storage-blob==12.14.1 - # via testcontainers + # via testcontainers-azurite babel==2.11.0 # via sphinx backports-zoneinfo==0.2.1 @@ -56,12 +120,12 @@ cffi==1.15.1 charset-normalizer==2.1.1 # via requests clickhouse-driver==0.2.5 - # via testcontainers + # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in commonmark==0.9.1 # via rich -coverage[toml]==6.5.0 +coverage[toml]==7.0.3 # via # codecov # pytest-cov @@ -72,9 +136,9 @@ cryptography==36.0.2 # paramiko # secretstorage cx-oracle==8.3.0 - # via testcontainers + # via testcontainers-oracle deprecation==2.1.0 - # via testcontainers + # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -82,9 +146,9 @@ dnspython==2.2.1 docker[ssh]==6.0.1 # via # docker-compose - # testcontainers + # testcontainers-core docker-compose==1.29.2 - # via testcontainers + # via testcontainers-compose dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -97,7 +161,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # pytest # trio @@ -108,8 +172,8 @@ google-api-core[grpc]==2.11.0 google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 - # via testcontainers -googleapis-common-protos[grpc]==1.57.0 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.57.1 # via # google-api-core # grpc-google-iam-v1 @@ -134,7 +198,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.1.0 +importlib-metadata==6.0.0 # via # jsonschema # keyring @@ -146,6 +210,8 @@ importlib-metadata==5.1.0 # sphinx # sqlalchemy # twine +importlib-resources==5.10.2 + # via keyring iniconfig==1.1.1 # via pytest isodate==0.6.1 @@ -161,25 +227,25 @@ jinja2==3.1.2 jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 - # via testcontainers -keyring==23.11.0 + # via testcontainers-kafka +keyring==23.13.1 # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 minio==7.1.12 - # via testcontainers + # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob neo4j==5.3.0 - # via testcontainers + # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib opensearch-py==2.0.1 - # via testcontainers + # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==22.0 @@ -190,11 +256,11 @@ packaging==22.0 # sphinx paramiko==2.12.0 # via docker -pg8000==1.29.3 +pg8000==1.29.4 # via -r requirements.in pika==1.3.1 - # via testcontainers -pkginfo==1.9.2 + # via testcontainers-rabbitmq +pkginfo==1.9.4 # via twine pluggy==1.0.0 # via pytest @@ -205,7 +271,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.5 - # via testcontainers + # via testcontainers-postgres pyasn1==0.4.8 # via # pyasn1-modules @@ -219,7 +285,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.13.0 +pygments==2.14.0 # via # readme-renderer # rich @@ -227,14 +293,14 @@ pygments==2.13.0 pyjwt==2.6.0 # via python-arango pymongo==4.3.3 - # via testcontainers + # via testcontainers-mongodb pymssql==2.2.7 - # via testcontainers + # via testcontainers-mssql pymysql==1.0.2 - # via testcontainers + # via testcontainers-mysql pynacl==1.5.0 # via paramiko -pyrsistent==0.19.2 +pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 @@ -244,17 +310,17 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.3 - # via testcontainers +python-arango==7.5.4 + # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.6.0 - # via testcontainers -pytz==2022.6 +python-keycloak==2.8.0 + # via testcontainers-keycloak +pytz==2022.7 # via # babel # clickhouse-driver @@ -266,7 +332,7 @@ pyyaml==5.4.1 readme-renderer==37.3 # via twine redis==4.4.0 - # via testcontainers + # via testcontainers-redis requests==2.28.1 # via # azure-core @@ -291,7 +357,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==12.6.0 +rich==13.0.0 # via twine rsa==4.9 # via @@ -302,7 +368,7 @@ scramp==1.4.4 secretstorage==3.3.3 # via keyring selenium==4.7.2 - # via testcontainers + # via testcontainers-selenium six==1.16.0 # via # azure-core @@ -335,8 +401,11 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.44 - # via testcontainers +sqlalchemy==1.4.46 + # via + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres texttable==1.6.7 # via docker-compose tomli==2.0.1 @@ -380,11 +449,13 @@ websocket-client==0.59.0 # docker # docker-compose wrapt==1.14.1 - # via testcontainers + # via testcontainers-core wsproto==1.2.0 # via trio-websocket zipp==3.11.0 - # via importlib-metadata + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 88fa3c371..b31174517 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -4,7 +4,71 @@ # # pip-compile --output-file=requirements/3.8.txt --resolver=backtracking requirements.in # --e file:. +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium # via -r requirements.in alabaster==0.7.12 # via sphinx @@ -16,18 +80,18 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==22.1.0 +attrs==22.2.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.26.1 +azure-core==1.26.2 # via # azure-storage-blob # msrest azure-storage-blob==12.14.1 - # via testcontainers + # via testcontainers-azurite babel==2.11.0 # via sphinx backports-zoneinfo==0.2.1 @@ -54,12 +118,12 @@ cffi==1.15.1 charset-normalizer==2.1.1 # via requests clickhouse-driver==0.2.5 - # via testcontainers + # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in commonmark==0.9.1 # via rich -coverage[toml]==6.5.0 +coverage[toml]==7.0.3 # via # codecov # pytest-cov @@ -70,9 +134,9 @@ cryptography==36.0.2 # paramiko # secretstorage cx-oracle==8.3.0 - # via testcontainers + # via testcontainers-oracle deprecation==2.1.0 - # via testcontainers + # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -80,9 +144,9 @@ dnspython==2.2.1 docker[ssh]==6.0.1 # via # docker-compose - # testcontainers + # testcontainers-core docker-compose==1.29.2 - # via testcontainers + # via testcontainers-compose dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -95,7 +159,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # pytest # trio @@ -106,8 +170,8 @@ google-api-core[grpc]==2.11.0 google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 - # via testcontainers -googleapis-common-protos[grpc]==1.57.0 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.57.1 # via # google-api-core # grpc-google-iam-v1 @@ -132,11 +196,13 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.1.0 +importlib-metadata==6.0.0 # via # keyring # sphinx # twine +importlib-resources==5.10.2 + # via keyring iniconfig==1.1.1 # via pytest isodate==0.6.1 @@ -152,25 +218,25 @@ jinja2==3.1.2 jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 - # via testcontainers -keyring==23.11.0 + # via testcontainers-kafka +keyring==23.13.1 # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 minio==7.1.12 - # via testcontainers + # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob neo4j==5.3.0 - # via testcontainers + # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib opensearch-py==2.0.1 - # via testcontainers + # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==22.0 @@ -181,11 +247,11 @@ packaging==22.0 # sphinx paramiko==2.12.0 # via docker -pg8000==1.29.3 +pg8000==1.29.4 # via -r requirements.in pika==1.3.1 - # via testcontainers -pkginfo==1.9.2 + # via testcontainers-rabbitmq +pkginfo==1.9.4 # via twine pluggy==1.0.0 # via pytest @@ -196,7 +262,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.5 - # via testcontainers + # via testcontainers-postgres pyasn1==0.4.8 # via # pyasn1-modules @@ -210,7 +276,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.13.0 +pygments==2.14.0 # via # readme-renderer # rich @@ -218,14 +284,14 @@ pygments==2.13.0 pyjwt==2.6.0 # via python-arango pymongo==4.3.3 - # via testcontainers + # via testcontainers-mongodb pymssql==2.2.7 - # via testcontainers + # via testcontainers-mssql pymysql==1.0.2 - # via testcontainers + # via testcontainers-mysql pynacl==1.5.0 # via paramiko -pyrsistent==0.19.2 +pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 @@ -235,17 +301,17 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.3 - # via testcontainers +python-arango==7.5.4 + # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.6.0 - # via testcontainers -pytz==2022.6 +python-keycloak==2.8.0 + # via testcontainers-keycloak +pytz==2022.7 # via # babel # clickhouse-driver @@ -257,7 +323,7 @@ pyyaml==5.4.1 readme-renderer==37.3 # via twine redis==4.4.0 - # via testcontainers + # via testcontainers-redis requests==2.28.1 # via # azure-core @@ -282,7 +348,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==12.6.0 +rich==13.0.0 # via twine rsa==4.9 # via @@ -293,7 +359,7 @@ scramp==1.4.4 secretstorage==3.3.3 # via keyring selenium==4.7.2 - # via testcontainers + # via testcontainers-selenium six==1.16.0 # via # azure-core @@ -312,7 +378,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.3.0 +sphinx==6.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -326,8 +392,11 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.44 - # via testcontainers +sqlalchemy==1.4.46 + # via + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres texttable==1.6.7 # via docker-compose tomli==2.0.1 @@ -367,11 +436,13 @@ websocket-client==0.59.0 # docker # docker-compose wrapt==1.14.1 - # via testcontainers + # via testcontainers-core wsproto==1.2.0 # via trio-websocket zipp==3.11.0 - # via importlib-metadata + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/3.9.txt b/requirements/3.9.txt index ca4f23c45..80140fbe0 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -4,7 +4,71 @@ # # pip-compile --output-file=requirements/3.9.txt --resolver=backtracking requirements.in # --e file:. +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium # via -r requirements.in alabaster==0.7.12 # via sphinx @@ -16,18 +80,18 @@ async-generator==1.10 # trio-websocket async-timeout==4.0.2 # via redis -attrs==22.1.0 +attrs==22.2.0 # via # jsonschema # outcome # pytest # trio -azure-core==1.26.1 +azure-core==1.26.2 # via # azure-storage-blob # msrest azure-storage-blob==12.14.1 - # via testcontainers + # via testcontainers-azurite babel==2.11.0 # via sphinx bcrypt==4.0.1 @@ -50,12 +114,12 @@ cffi==1.15.1 charset-normalizer==2.1.1 # via requests clickhouse-driver==0.2.5 - # via testcontainers + # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in commonmark==0.9.1 # via rich -coverage[toml]==6.5.0 +coverage[toml]==7.0.3 # via # codecov # pytest-cov @@ -66,9 +130,9 @@ cryptography==36.0.2 # paramiko # secretstorage cx-oracle==8.3.0 - # via testcontainers + # via testcontainers-oracle deprecation==2.1.0 - # via testcontainers + # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -76,9 +140,9 @@ dnspython==2.2.1 docker[ssh]==6.0.1 # via # docker-compose - # testcontainers + # testcontainers-core docker-compose==1.29.2 - # via testcontainers + # via testcontainers-compose dockerpty==0.4.1 # via docker-compose docopt==0.6.2 @@ -91,7 +155,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # pytest # trio @@ -102,8 +166,8 @@ google-api-core[grpc]==2.11.0 google-auth==2.15.0 # via google-api-core google-cloud-pubsub==1.7.2 - # via testcontainers -googleapis-common-protos[grpc]==1.57.0 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.57.1 # via # google-api-core # grpc-google-iam-v1 @@ -128,7 +192,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==5.1.0 +importlib-metadata==6.0.0 # via # keyring # sphinx @@ -148,25 +212,25 @@ jinja2==3.1.2 jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 - # via testcontainers -keyring==23.11.0 + # via testcontainers-kafka +keyring==23.13.1 # via twine markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 minio==7.1.12 - # via testcontainers + # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob neo4j==5.3.0 - # via testcontainers + # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib opensearch-py==2.0.1 - # via testcontainers + # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==22.0 @@ -177,11 +241,11 @@ packaging==22.0 # sphinx paramiko==2.12.0 # via docker -pg8000==1.29.3 +pg8000==1.29.4 # via -r requirements.in pika==1.3.1 - # via testcontainers -pkginfo==1.9.2 + # via testcontainers-rabbitmq +pkginfo==1.9.4 # via twine pluggy==1.0.0 # via pytest @@ -192,7 +256,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpcio-status psycopg2-binary==2.9.5 - # via testcontainers + # via testcontainers-postgres pyasn1==0.4.8 # via # pyasn1-modules @@ -206,7 +270,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.13.0 +pygments==2.14.0 # via # readme-renderer # rich @@ -214,14 +278,14 @@ pygments==2.13.0 pyjwt==2.6.0 # via python-arango pymongo==4.3.3 - # via testcontainers + # via testcontainers-mongodb pymssql==2.2.7 - # via testcontainers + # via testcontainers-mssql pymysql==1.0.2 - # via testcontainers + # via testcontainers-mysql pynacl==1.5.0 # via paramiko -pyrsistent==0.19.2 +pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 @@ -231,17 +295,17 @@ pytest==7.2.0 # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.3 - # via testcontainers +python-arango==7.5.4 + # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 python-dotenv==0.21.0 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.6.0 - # via testcontainers -pytz==2022.6 +python-keycloak==2.8.0 + # via testcontainers-keycloak +pytz==2022.7 # via # babel # clickhouse-driver @@ -253,7 +317,7 @@ pyyaml==5.4.1 readme-renderer==37.3 # via twine redis==4.4.0 - # via testcontainers + # via testcontainers-redis requests==2.28.1 # via # azure-core @@ -278,7 +342,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==12.6.0 +rich==13.0.0 # via twine rsa==4.9 # via @@ -289,7 +353,7 @@ scramp==1.4.4 secretstorage==3.3.3 # via keyring selenium==4.7.2 - # via testcontainers + # via testcontainers-selenium six==1.16.0 # via # azure-core @@ -308,7 +372,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.3.0 +sphinx==6.1.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -322,8 +386,11 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.44 - # via testcontainers +sqlalchemy==1.4.46 + # via + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres texttable==1.6.7 # via docker-compose tomli==2.0.1 @@ -361,7 +428,7 @@ websocket-client==0.59.0 # docker # docker-compose wrapt==1.14.1 - # via testcontainers + # via testcontainers-core wsproto==1.2.0 # via trio-websocket zipp==3.11.0 diff --git a/selenium/setup.py b/selenium/setup.py new file mode 100644 index 000000000..bd8bab53c --- /dev/null +++ b/selenium/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="testcontainers-selenium", + version="0.0.1rc1", + packages=find_namespace_packages(), + description="Selenium component of testcontainers-python.", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "selenium", + ], + python_requires=">=3.7", +) diff --git a/testcontainers/selenium.py b/selenium/testcontainers/selenium/__init__.py similarity index 100% rename from testcontainers/selenium.py rename to selenium/testcontainers/selenium/__init__.py diff --git a/setup.py b/setup.py index 9b355e51c..c3a91f6f2 100644 --- a/setup.py +++ b/setup.py @@ -17,17 +17,10 @@ long_description = fp.read() long_description = long_description.replace(".. doctest::", ".. code-block::") -# Load the version number -try: - with open('VERSION') as fp: - version = fp.read().strip() -except FileNotFoundError: - version = '0.dev0' setuptools.setup( name='testcontainers', - packages=setuptools.find_packages(exclude=['tests']), - version=version, + version='4.0.0rc1', description='Library provides lightweight, throwaway instances of common databases, Selenium ' 'web browsers, or anything else that can run in a Docker container', author='Sergey Pirogov', @@ -55,24 +48,24 @@ 'deprecation', ], extras_require={ - 'docker-compose': ['docker-compose'], - 'mysql': ['sqlalchemy', 'pymysql'], - 'oracle': ['sqlalchemy', 'cx_Oracle'], - 'postgresql': ['sqlalchemy', 'psycopg2-binary'], - 'selenium': ['selenium'], - 'google-cloud-pubsub': ['google-cloud-pubsub < 2'], - 'minio': ['minio'], - 'mongo': ['pymongo'], - 'redis': ['redis'], - 'mssqlserver': ['pymssql'], - 'neo4j': ['neo4j'], - 'kafka': ['kafka-python'], - 'rabbitmq': ['pika'], - 'clickhouse': ['clickhouse-driver'], - 'keycloak': ['python-keycloak'], - 'arangodb': ['python-arango'], - 'azurite': ['azure-storage-blob'], - 'opensearch': ['opensearch-py'], + 'arangodb': ['testcontainers-arangodb'], + 'azurite': ['testcontainers-azurite'], + 'clickhouse': ['testcontainers-clickhouse'], + 'docker-compose': ['testcontainers-compose'], + 'google-cloud-pubsub': ['testcontainers-gcp'], + 'kafka': ['testcontainers-kafka'], + 'keycloak': ['testcontainers-keycloak'], + 'minio': ['testcontainers-minio'], + 'mongo': ['testcontainers-mongo'], + 'mssqlserver': ['testcontainers-mssql'], + 'mysql': ['testcontainers-mysql'], + 'neo4j': ['testcontainers-neo4j'], + 'opensearch': ['testcontainers-opensearch'], + 'oracle': ['testcontainers-oracle'], + 'postgresql': ['testcontainers-postgres'], + 'rabbitmq': ['testcontainers-rabbitmq'], + 'redis': ['testcontainers-redis'], + 'selenium': ['testcontainers-selenium'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/core/__init__.py b/testcontainers/core/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/testcontainers/general.py b/testcontainers/general.py deleted file mode 100644 index 04452655f..000000000 --- a/testcontainers/general.py +++ /dev/null @@ -1,23 +0,0 @@ -# -# 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. -from deprecation import deprecated -from testcontainers.core.container import DockerContainer - - -class TestContainer(DockerContainer): - @deprecated(details="Use `DockerContainer`.") - def __init__(self, image, port_to_expose=None): - super(TestContainer, self).__init__(image) - if port_to_expose: - self.port_to_expose = port_to_expose - self.with_exposed_ports(self.port_to_expose) From cc0622178aa8957820604b0fae7d915dda80dcf4 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 16:54:27 -0500 Subject: [PATCH 122/425] Update `Dockerfile.diagnostics` to install `core` package. --- Dockerfile.diagnostics | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile.diagnostics b/Dockerfile.diagnostics index 83bc6621c..9d1bba2cd 100644 --- a/Dockerfile.diagnostics +++ b/Dockerfile.diagnostics @@ -2,6 +2,6 @@ ARG version=3.8 FROM python:${version} WORKDIR /workspace -COPY setup.py README.rst ./ -RUN pip install -e . -COPY . . +COPY core core +RUN pip install --no-cache-dir -e core +COPY diagnostics.py . From 0f7abe9969def08480b6fb1cb2a8d77985b3e300 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 17:15:06 -0500 Subject: [PATCH 123/425] Restructure tests and temporarily disable packaging. --- .github/workflows/main.yml | 46 ++++++------ {tests => arangodb/tests}/test_arangodb.py | 0 {tests => azurite/tests}/test_azurite.py | 0 .../tests}/test_clickhouse.py | 0 {tests/test_core => compose/tests}/.env.test | 0 .../tests}/docker-compose-2.yml | 0 .../tests}/docker-compose-3.yml | 0 .../tests}/docker-compose-4.yml | 0 .../tests}/docker-compose.yml | 0 .../tests}/test_docker_compose.py | 0 {tests/test_core => core/tests}/Dockerfile | 0 {tests/test_core => core/tests}/test_core.py | 0 {tests => core/tests}/test_docker_client.py | 0 .../tests}/test_docker_in_docker.py | 0 .../tests}/test_new_docker_api.py | 0 .../tests}/test_elasticsearch.py | 0 {tests => google/tests}/test_google.py | 0 {tests => kafka/tests}/test_kafka.py | 0 {tests => keycloak/tests}/test_keycloak.py | 0 .../tests}/test_localstack.py | 0 {tests => minio/tests}/test_minio.py | 0 {tests => mongodb/tests}/test_mongodb.py | 0 mssql/tests/test_mssql.py | 18 +++++ mysql/tests/test_mysql.py | 22 ++++++ {tests => neo4j/tests}/test_neo4j.py | 0 {tests => nginx/tests}/test_nginx.py | 0 .../tests}/test_opensearch.py | 0 oracle/tests/test_oracle.py | 16 +++++ postgres/tests/test_postgres.py | 18 +++++ {tests => rabbitmq/tests}/test_rabbitmq.py | 0 {tests => redis/tests}/test_redis.py | 0 .../tests/test_selenium.py | 7 ++ tests/__init__.py | 0 tests/test_core/__init__.py | 0 tests/test_core/test_db_containers.py | 70 ------------------- tests/test_selenium.py | 9 --- 36 files changed, 103 insertions(+), 103 deletions(-) rename {tests => arangodb/tests}/test_arangodb.py (100%) rename {tests => azurite/tests}/test_azurite.py (100%) rename {tests => clickhouse/tests}/test_clickhouse.py (100%) rename {tests/test_core => compose/tests}/.env.test (100%) rename {tests/test_core => compose/tests}/docker-compose-2.yml (100%) rename {tests/test_core => compose/tests}/docker-compose-3.yml (100%) rename {tests/test_core => compose/tests}/docker-compose-4.yml (100%) rename {tests/test_core => compose/tests}/docker-compose.yml (100%) rename {tests/test_core => compose/tests}/test_docker_compose.py (100%) rename {tests/test_core => core/tests}/Dockerfile (100%) rename {tests/test_core => core/tests}/test_core.py (100%) rename {tests => core/tests}/test_docker_client.py (100%) rename {tests/test_core => core/tests}/test_docker_in_docker.py (100%) rename {tests/test_core => core/tests}/test_new_docker_api.py (100%) rename {tests => elasticsearch/tests}/test_elasticsearch.py (100%) rename {tests => google/tests}/test_google.py (100%) rename {tests => kafka/tests}/test_kafka.py (100%) rename {tests => keycloak/tests}/test_keycloak.py (100%) rename {tests => localstack/tests}/test_localstack.py (100%) rename {tests => minio/tests}/test_minio.py (100%) rename {tests => mongodb/tests}/test_mongodb.py (100%) create mode 100644 mssql/tests/test_mssql.py create mode 100644 mysql/tests/test_mysql.py rename {tests => neo4j/tests}/test_neo4j.py (100%) rename {tests => nginx/tests}/test_nginx.py (100%) rename {tests => opensearch/tests}/test_opensearch.py (100%) create mode 100644 oracle/tests/test_oracle.py create mode 100644 postgres/tests/test_postgres.py rename {tests => rabbitmq/tests}/test_rabbitmq.py (100%) rename {tests => redis/tests}/test_redis.py (100%) rename tests/test_webdriver.py => selenium/tests/test_selenium.py (65%) delete mode 100644 tests/__init__.py delete mode 100644 tests/test_core/__init__.py delete mode 100644 tests/test_core/test_db_containers.py delete mode 100644 tests/test_selenium.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8b50edbc..5c6b61ec0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,24 +50,28 @@ jobs: - "3.9" - "3.10" test-component: + - arangodb + - azurite + - clickhouse + - compose - core - - clickhouse.py - - elasticsearch.py - - google.py - - kafka.py - - localstack.py - - minio.py - - mongodb.py - - neo4j.py - - nginx.py - - rabbitmq.py - - redis.py - - selenium.py - - webdriver.py - - keycloak.py - - arangodb.py - - azurite.py - - opensearch.py + - elasticsearch + - google + - kafka + - keycloak + - localstack + - minio + - mongodb + - mssql + - mysql + - neo4j + - nginx + - opensearch + - oracle + - postgres + - rabbitmq + - redis + - selenium runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -101,10 +105,4 @@ jobs: - name: Lint the code run: flake8 - name: Run tests - run: | - py.test -svx --cov-config .coveragerc --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short tests/test_${{ matrix.test-component }} - codecov - - name: Build package and check it - run: | - python setup.py bdist_wheel - twine check dist/* + run: pytest -svx --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short ${{ matrix.test-component }}/tests diff --git a/tests/test_arangodb.py b/arangodb/tests/test_arangodb.py similarity index 100% rename from tests/test_arangodb.py rename to arangodb/tests/test_arangodb.py diff --git a/tests/test_azurite.py b/azurite/tests/test_azurite.py similarity index 100% rename from tests/test_azurite.py rename to azurite/tests/test_azurite.py diff --git a/tests/test_clickhouse.py b/clickhouse/tests/test_clickhouse.py similarity index 100% rename from tests/test_clickhouse.py rename to clickhouse/tests/test_clickhouse.py diff --git a/tests/test_core/.env.test b/compose/tests/.env.test similarity index 100% rename from tests/test_core/.env.test rename to compose/tests/.env.test diff --git a/tests/test_core/docker-compose-2.yml b/compose/tests/docker-compose-2.yml similarity index 100% rename from tests/test_core/docker-compose-2.yml rename to compose/tests/docker-compose-2.yml diff --git a/tests/test_core/docker-compose-3.yml b/compose/tests/docker-compose-3.yml similarity index 100% rename from tests/test_core/docker-compose-3.yml rename to compose/tests/docker-compose-3.yml diff --git a/tests/test_core/docker-compose-4.yml b/compose/tests/docker-compose-4.yml similarity index 100% rename from tests/test_core/docker-compose-4.yml rename to compose/tests/docker-compose-4.yml diff --git a/tests/test_core/docker-compose.yml b/compose/tests/docker-compose.yml similarity index 100% rename from tests/test_core/docker-compose.yml rename to compose/tests/docker-compose.yml diff --git a/tests/test_core/test_docker_compose.py b/compose/tests/test_docker_compose.py similarity index 100% rename from tests/test_core/test_docker_compose.py rename to compose/tests/test_docker_compose.py diff --git a/tests/test_core/Dockerfile b/core/tests/Dockerfile similarity index 100% rename from tests/test_core/Dockerfile rename to core/tests/Dockerfile diff --git a/tests/test_core/test_core.py b/core/tests/test_core.py similarity index 100% rename from tests/test_core/test_core.py rename to core/tests/test_core.py diff --git a/tests/test_docker_client.py b/core/tests/test_docker_client.py similarity index 100% rename from tests/test_docker_client.py rename to core/tests/test_docker_client.py diff --git a/tests/test_core/test_docker_in_docker.py b/core/tests/test_docker_in_docker.py similarity index 100% rename from tests/test_core/test_docker_in_docker.py rename to core/tests/test_docker_in_docker.py diff --git a/tests/test_core/test_new_docker_api.py b/core/tests/test_new_docker_api.py similarity index 100% rename from tests/test_core/test_new_docker_api.py rename to core/tests/test_new_docker_api.py diff --git a/tests/test_elasticsearch.py b/elasticsearch/tests/test_elasticsearch.py similarity index 100% rename from tests/test_elasticsearch.py rename to elasticsearch/tests/test_elasticsearch.py diff --git a/tests/test_google.py b/google/tests/test_google.py similarity index 100% rename from tests/test_google.py rename to google/tests/test_google.py diff --git a/tests/test_kafka.py b/kafka/tests/test_kafka.py similarity index 100% rename from tests/test_kafka.py rename to kafka/tests/test_kafka.py diff --git a/tests/test_keycloak.py b/keycloak/tests/test_keycloak.py similarity index 100% rename from tests/test_keycloak.py rename to keycloak/tests/test_keycloak.py diff --git a/tests/test_localstack.py b/localstack/tests/test_localstack.py similarity index 100% rename from tests/test_localstack.py rename to localstack/tests/test_localstack.py diff --git a/tests/test_minio.py b/minio/tests/test_minio.py similarity index 100% rename from tests/test_minio.py rename to minio/tests/test_minio.py diff --git a/tests/test_mongodb.py b/mongodb/tests/test_mongodb.py similarity index 100% rename from tests/test_mongodb.py rename to mongodb/tests/test_mongodb.py diff --git a/mssql/tests/test_mssql.py b/mssql/tests/test_mssql.py new file mode 100644 index 000000000..63b0a0135 --- /dev/null +++ b/mssql/tests/test_mssql.py @@ -0,0 +1,18 @@ +import sqlalchemy +from testcontainers.mssql import SqlServerContainer + + +def test_docker_run_mssql(): + image = 'mcr.microsoft.com/azure-sql-edge' + dialect = 'mssql+pymssql' + with SqlServerContainer(image, dialect=dialect) as mssql: + e = sqlalchemy.create_engine(mssql.get_connection_url()) + result = e.execute('select @@servicename') + for row in result: + assert row[0] == 'MSSQLSERVER' + + with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: + e = sqlalchemy.create_engine(mssql.get_connection_url()) + result = e.execute('select @@servicename') + for row in result: + assert row[0] == 'MSSQLSERVER' diff --git a/mysql/tests/test_mysql.py b/mysql/tests/test_mysql.py new file mode 100644 index 000000000..b3b8c24c6 --- /dev/null +++ b/mysql/tests/test_mysql.py @@ -0,0 +1,22 @@ +import sqlalchemy +import pytest +from testcontainers.core.utils import is_arm +from testcontainers.mysql import MySqlContainer + + +@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') +def test_docker_run_mysql(): + config = MySqlContainer('mysql:5.7.17') + with config as mysql: + e = sqlalchemy.create_engine(mysql.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0].startswith('5.7.17') + + +def test_docker_run_mariadb(): + with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: + e = sqlalchemy.create_engine(mariadb.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0].startswith('10.6.5') diff --git a/tests/test_neo4j.py b/neo4j/tests/test_neo4j.py similarity index 100% rename from tests/test_neo4j.py rename to neo4j/tests/test_neo4j.py diff --git a/tests/test_nginx.py b/nginx/tests/test_nginx.py similarity index 100% rename from tests/test_nginx.py rename to nginx/tests/test_nginx.py diff --git a/tests/test_opensearch.py b/opensearch/tests/test_opensearch.py similarity index 100% rename from tests/test_opensearch.py rename to opensearch/tests/test_opensearch.py diff --git a/oracle/tests/test_oracle.py b/oracle/tests/test_oracle.py new file mode 100644 index 000000000..495de3c0d --- /dev/null +++ b/oracle/tests/test_oracle.py @@ -0,0 +1,16 @@ +import sqlalchemy +import pytest +from testcontainers.oracle import OracleDbContainer + + +@pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") +def test_docker_run_oracle(): + with OracleDbContainer() as oracledb: + e = sqlalchemy.create_engine(oracledb.get_connection_url()) + result = e.execute("select * from V$VERSION") + versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', + 'PL/SQL Release 11.2.0.2.0 - Production', + 'CORE\t11.2.0.2.0\tProduction', + 'TNS for Linux: Version 11.2.0.2.0 - Production', + 'NLSRTL Version 11.2.0.2.0 - Production'} + assert {row[0] for row in result} == versions diff --git a/postgres/tests/test_postgres.py b/postgres/tests/test_postgres.py new file mode 100644 index 000000000..7bad16f84 --- /dev/null +++ b/postgres/tests/test_postgres.py @@ -0,0 +1,18 @@ +import sqlalchemy +from testcontainers.postgres import PostgresContainer + + +def test_docker_run_postgres(): + postgres_container = PostgresContainer("postgres:9.5") + with postgres_container as postgres: + e = sqlalchemy.create_engine(postgres.get_connection_url()) + result = e.execute("select version()") + for row in result: + assert row[0].lower().startswith("postgresql 9.5") + + +def test_docker_run_postgres_with_driver_pg8000(): + postgres_container = PostgresContainer("postgres:9.5", driver="pg8000") + with postgres_container as postgres: + e = sqlalchemy.create_engine(postgres.get_connection_url()) + e.execute("select 1=1") diff --git a/tests/test_rabbitmq.py b/rabbitmq/tests/test_rabbitmq.py similarity index 100% rename from tests/test_rabbitmq.py rename to rabbitmq/tests/test_rabbitmq.py diff --git a/tests/test_redis.py b/redis/tests/test_redis.py similarity index 100% rename from tests/test_redis.py rename to redis/tests/test_redis.py diff --git a/tests/test_webdriver.py b/selenium/tests/test_selenium.py similarity index 65% rename from tests/test_webdriver.py rename to selenium/tests/test_selenium.py index 512af97f9..0b25cd5ec 100644 --- a/tests/test_webdriver.py +++ b/selenium/tests/test_selenium.py @@ -13,3 +13,10 @@ def test_webdriver_container_container(caps): webdriver = chrome.get_driver() webdriver.get("http://google.com") webdriver.find_element("name", "q").send_keys("Hello") + + +def test_selenium_custom_image(): + image = "selenium/standalone-chrome:latest" + chrome = BrowserWebDriverContainer(DesiredCapabilities.CHROME, image=image) + assert "image" in dir(chrome), "`image` attribute was not instantialized." + assert chrome.image == image, "`image` attribute was not set to the user provided value" diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_core/__init__.py b/tests/test_core/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_core/test_db_containers.py b/tests/test_core/test_db_containers.py deleted file mode 100644 index 0aa6640a7..000000000 --- a/tests/test_core/test_db_containers.py +++ /dev/null @@ -1,70 +0,0 @@ -import sqlalchemy -import pytest -from testcontainers.core.utils import is_arm -from testcontainers.mssql import SqlServerContainer -from testcontainers.mysql import MySqlContainer -from testcontainers.oracle import OracleDbContainer -from testcontainers.postgres import PostgresContainer - - -@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') -def test_docker_run_mysql(): - config = MySqlContainer('mysql:5.7.17') - with config as mysql: - e = sqlalchemy.create_engine(mysql.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].startswith('5.7.17') - - -def test_docker_run_postgres(): - postgres_container = PostgresContainer("postgres:9.5") - with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].lower().startswith("postgresql 9.5") - - -def test_docker_run_postgres_with_driver_pg8000(): - postgres_container = PostgresContainer("postgres:9.5", driver="pg8000") - with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - e.execute("select 1=1") - - -def test_docker_run_mariadb(): - with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: - e = sqlalchemy.create_engine(mariadb.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].startswith('10.6.5') - - -@pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") -def test_docker_run_oracle(): - with OracleDbContainer() as oracledb: - e = sqlalchemy.create_engine(oracledb.get_connection_url()) - result = e.execute("select * from V$VERSION") - versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', - 'PL/SQL Release 11.2.0.2.0 - Production', - 'CORE\t11.2.0.2.0\tProduction', - 'TNS for Linux: Version 11.2.0.2.0 - Production', - 'NLSRTL Version 11.2.0.2.0 - Production'} - assert {row[0] for row in result} == versions - - -def test_docker_run_mssql(): - image = 'mcr.microsoft.com/azure-sql-edge' - dialect = 'mssql+pymssql' - with SqlServerContainer(image, dialect=dialect) as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute('select @@servicename') - for row in result: - assert row[0] == 'MSSQLSERVER' - - with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute('select @@servicename') - for row in result: - assert row[0] == 'MSSQLSERVER' diff --git a/tests/test_selenium.py b/tests/test_selenium.py deleted file mode 100644 index 9958ecff4..000000000 --- a/tests/test_selenium.py +++ /dev/null @@ -1,9 +0,0 @@ - -def test_selenium_custom_image(): - from testcontainers.selenium import BrowserWebDriverContainer - from selenium.webdriver import DesiredCapabilities - - image = "selenium/standalone-chrome:latest" - chrome = BrowserWebDriverContainer(DesiredCapabilities.CHROME, image=image) - assert "image" in dir(chrome), "`image` attribute was not instantialized." - assert chrome.image == image, "`image` attribute was not set to the user provided value" From 26aa7bdbfcd497aa55869c4449b6eefcd94f7b69 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 17:19:48 -0500 Subject: [PATCH 124/425] Fix docker compose root directory in tests. --- compose/tests/test_docker_compose.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose/tests/test_docker_compose.py b/compose/tests/test_docker_compose.py index ce8ec02ce..00f231e68 100644 --- a/compose/tests/test_docker_compose.py +++ b/compose/tests/test_docker_compose.py @@ -1,3 +1,4 @@ +import os from unittest.mock import patch import pytest @@ -8,7 +9,7 @@ from testcontainers.core.waiting_utils import wait_for_logs -ROOT = "tests/test_core" +ROOT = os.path.dirname(__file__) def test_can_spawn_service_via_compose(): From 2be4b588619ad47286c866319e18d9761a6c3a2c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 17:32:30 -0500 Subject: [PATCH 125/425] Update coverage reporting for namespace packages. --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5c6b61ec0..bfd165a3c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -105,4 +105,6 @@ jobs: - name: Lint the code run: flake8 - name: Run tests - run: pytest -svx --cov-report html:skip-covered --cov-report term:skip-covered --cov=testcontainers --tb=short ${{ matrix.test-component }}/tests + run: > + pytest -svx --cov-report=term-missing --cov=testcontainers.${{ matrix.test-component }} + --tb=short ${{ matrix.test-component }}/tests From 9f0e96811aa58f0d3e144ab50d3043460d4f51bd Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 17:56:24 -0500 Subject: [PATCH 126/425] Reorganize documentation into namespace packages. --- .github/workflows/main.yml | 4 +- .readthedocs.yml | 14 ++----- README.rst | 25 +++++++++++ arangodb/README.rst | 1 + azurite/README.rst | 1 + clickhouse/README.rst | 1 + compose/README.rst | 1 + docs/conf.py => conf.py | 6 ++- core/README.rst | 4 ++ docs/Makefile | 20 --------- docs/aws.rst | 6 --- docs/azure.rst | 6 --- docs/compose.rst | 2 - docs/database.rst | 14 ------- docs/elasticsearch.rst | 6 --- docs/google-cloud-emulators.rst | 2 - docs/{index.rst => index.rst-ish} | 0 docs/kafka.rst | 6 --- docs/keycloak.rst | 6 --- docs/make.bat | 36 ---------------- docs/minio.rst | 6 --- docs/opensearch.rst | 6 --- docs/rabbitmq.rst | 6 --- docs/redis.rst | 6 --- docs/selenium.rst | 2 - elasticsearch/README.rst | 1 + google/README.rst | 1 + kafka/README.rst | 1 + keycloak/README.rst | 1 + localstack/README.rst | 1 + minio/README.rst | 1 + mongodb/README.rst | 1 + mssql/README.rst | 1 + mysql/README.rst | 1 + neo4j/README.rst | 1 + nginx/README.rst | 1 + opensearch/README.rst | 1 + oracle/README.rst | 1 + postgres/README.rst | 1 + rabbitmq/README.rst | 1 + redis/README.rst | 1 + release-process.rst | 69 ------------------------------- selenium/README.rst | 1 + 43 files changed, 61 insertions(+), 212 deletions(-) create mode 100644 arangodb/README.rst create mode 100644 azurite/README.rst create mode 100644 clickhouse/README.rst create mode 100644 compose/README.rst rename docs/conf.py => conf.py (96%) create mode 100644 core/README.rst delete mode 100644 docs/Makefile delete mode 100644 docs/aws.rst delete mode 100644 docs/azure.rst delete mode 100644 docs/compose.rst delete mode 100644 docs/database.rst delete mode 100644 docs/elasticsearch.rst delete mode 100644 docs/google-cloud-emulators.rst rename docs/{index.rst => index.rst-ish} (100%) delete mode 100644 docs/kafka.rst delete mode 100644 docs/keycloak.rst delete mode 100644 docs/make.bat delete mode 100644 docs/minio.rst delete mode 100644 docs/opensearch.rst delete mode 100644 docs/rabbitmq.rst delete mode 100644 docs/redis.rst delete mode 100644 docs/selenium.rst create mode 100644 elasticsearch/README.rst create mode 100644 google/README.rst create mode 100644 kafka/README.rst create mode 100644 keycloak/README.rst create mode 100644 localstack/README.rst create mode 100644 minio/README.rst create mode 100644 mongodb/README.rst create mode 100644 mssql/README.rst create mode 100644 mysql/README.rst create mode 100644 neo4j/README.rst create mode 100644 nginx/README.rst create mode 100644 opensearch/README.rst create mode 100644 oracle/README.rst create mode 100644 postgres/README.rst create mode 100644 rabbitmq/README.rst create mode 100644 redis/README.rst delete mode 100644 release-process.rst create mode 100644 selenium/README.rst diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bfd165a3c..f3b7d57b4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,9 +36,9 @@ jobs: pip install wheel pip install -r requirements/${{ matrix.python-version }}.txt - name: Build documentation - run: sphinx-build -nW docs docs/_build/html + run: sphinx-build -nW . docs/_build - name: Run doctests - run: sphinx-build -b doctest docs docs/_build/html + run: sphinx-build -b doctest . docs/_build build: strategy: diff --git a/.readthedocs.yml b/.readthedocs.yml index 4f1886a13..b3a90fc61 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,19 +1,13 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +# Read the Docs configuration file (see https://docs.readthedocs.io/en/stable/config-file/v2.html +# for details). # Required version: 2 -# Build documentation in the docs/ directory with Sphinx sphinx: - configuration: docs/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub + configuration: conf.py formats: all - -# Optionally set the version of Python and requirements required to build your docs python: - version: 3.7 + version: "3.7" install: - requirements: requirements/3.7.txt diff --git a/README.rst b/README.rst index b5f83416b..8fe92cf2c 100644 --- a/README.rst +++ b/README.rst @@ -100,3 +100,28 @@ You can contribute a new container in three steps: 1. Create a new module at :code:`testcontainers/[my fancy container].py` that implements the new functionality. 2. Create a new test module at :code:`tests/test_[my fancy container].py` that tests the new functionality. 3. Add :code:`[my fancy container]` to the list of test components in the GitHub Action configuration at :code:`.github/workflows/main.yml`. + +.. toctree:: + + core/README + arangodb/README + azurite/README + clickhouse/README + compose/README + elasticsearch/README + google/README + kafka/README + keycloak/README + localstack/README + minio/README + mongodb/README + mssql/README + mysql/README + neo4j/README + nginx/README + opensearch/README + oracle/README + postgres/README + rabbitmq/README + redis/README + selenium/README diff --git a/arangodb/README.rst b/arangodb/README.rst new file mode 100644 index 000000000..7f2837f2a --- /dev/null +++ b/arangodb/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.arangodb.ArangoDbContainer diff --git a/azurite/README.rst b/azurite/README.rst new file mode 100644 index 000000000..793b5124a --- /dev/null +++ b/azurite/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.azurite.AzuriteContainer diff --git a/clickhouse/README.rst b/clickhouse/README.rst new file mode 100644 index 000000000..9075cf628 --- /dev/null +++ b/clickhouse/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.clickhouse.ClickHouseContainer diff --git a/compose/README.rst b/compose/README.rst new file mode 100644 index 000000000..beafee0d5 --- /dev/null +++ b/compose/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.compose.DockerCompose diff --git a/docs/conf.py b/conf.py similarity index 96% rename from docs/conf.py rename to conf.py index 354df4d01..9f18309fa 100644 --- a/docs/conf.py +++ b/conf.py @@ -36,6 +36,10 @@ 'sphinx.ext.napoleon', ] +# Configure autodoc to avoid excessively long fully-qualified names. +add_module_names = False +autodoc_typehints_format = "short" + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -46,7 +50,7 @@ source_suffix = '.rst' # The master toctree document. -master_doc = 'index' +master_doc = 'README' # General information about the project. project = u'testcontainers' diff --git a/core/README.rst b/core/README.rst new file mode 100644 index 000000000..37c7c4779 --- /dev/null +++ b/core/README.rst @@ -0,0 +1,4 @@ +testcontainers-core +=================== + +:code:`testcontainers-core` is a utility package for spinning up Docker containers in testing environments. diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 8177779fc..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = testcontainers -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/aws.rst b/docs/aws.rst deleted file mode 100644 index 784afa649..000000000 --- a/docs/aws.rst +++ /dev/null @@ -1,6 +0,0 @@ -AWS Emulators -=================== - -Allows to spin up AWS emulators, such as the LocalStackContainer. - -.. autoclass:: testcontainers.localstack.LocalStackContainer diff --git a/docs/azure.rst b/docs/azure.rst deleted file mode 100644 index adb5446b3..000000000 --- a/docs/azure.rst +++ /dev/null @@ -1,6 +0,0 @@ -Azure Emulators -=================== - -Allows to spin up Azure emulators, such as the Azurite emulator. - -.. autoclass:: testcontainers.azurite.AzuriteContainer diff --git a/docs/compose.rst b/docs/compose.rst deleted file mode 100644 index 191fb3591..000000000 --- a/docs/compose.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. automodule:: testcontainers.compose - :members: DockerCompose diff --git a/docs/database.rst b/docs/database.rst deleted file mode 100644 index 869487433..000000000 --- a/docs/database.rst +++ /dev/null @@ -1,14 +0,0 @@ -Database containers -=================== - -Allows to spin up database images such as MySQL, PostgreSQL, MariaDB, Oracle XE, MongoDb, ClickHouse, Neo4j or ArangoDB - -.. autoclass:: testcontainers.mysql.MySqlContainer -.. autoclass:: testcontainers.mysql.MariaDbContainer -.. autoclass:: testcontainers.postgres.PostgresContainer -.. autoclass:: testcontainers.oracle.OracleDbContainer -.. autoclass:: testcontainers.mongodb.MongoDbContainer -.. autoclass:: testcontainers.mssql.SqlServerContainer -.. autoclass:: testcontainers.clickhouse.ClickHouseContainer -.. autoclass:: testcontainers.neo4j.Neo4jContainer -.. autoclass:: testcontainers.arangodb.ArangoDbContainer diff --git a/docs/elasticsearch.rst b/docs/elasticsearch.rst deleted file mode 100644 index 2a21aa068..000000000 --- a/docs/elasticsearch.rst +++ /dev/null @@ -1,6 +0,0 @@ -Elastic Search Container -=========================== - -Allows to spin up Elastic Search Container. - -.. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer diff --git a/docs/google-cloud-emulators.rst b/docs/google-cloud-emulators.rst deleted file mode 100644 index 15c134558..000000000 --- a/docs/google-cloud-emulators.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. automodule:: testcontainers.google - :members: PubSubContainer diff --git a/docs/index.rst b/docs/index.rst-ish similarity index 100% rename from docs/index.rst rename to docs/index.rst-ish diff --git a/docs/kafka.rst b/docs/kafka.rst deleted file mode 100644 index 1f80f4c30..000000000 --- a/docs/kafka.rst +++ /dev/null @@ -1,6 +0,0 @@ -Kafka Container -=================== - -Allows to spin up Kafka Container. - -.. autoclass:: testcontainers.kafka.KafkaContainer diff --git a/docs/keycloak.rst b/docs/keycloak.rst deleted file mode 100644 index 356b3df3e..000000000 --- a/docs/keycloak.rst +++ /dev/null @@ -1,6 +0,0 @@ -Keycloak Container -=================== - -Allows to spin up Keycloak container. - -.. autoclass:: testcontainers.keycloak.KeycloakContainer \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 17111716a..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=testcontainers - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/minio.rst b/docs/minio.rst deleted file mode 100644 index d11916a3d..000000000 --- a/docs/minio.rst +++ /dev/null @@ -1,6 +0,0 @@ -Minio -=================== - -Allows to spin up Minio Container. - -.. autoclass:: testcontainers.minio.MinioContainer diff --git a/docs/opensearch.rst b/docs/opensearch.rst deleted file mode 100644 index 4e464217a..000000000 --- a/docs/opensearch.rst +++ /dev/null @@ -1,6 +0,0 @@ -OpenSearch -=================== - -Allows to spin up OpenSearch Container. - -.. autoclass:: testcontainers.opensearch.OpenSearchContainer diff --git a/docs/rabbitmq.rst b/docs/rabbitmq.rst deleted file mode 100644 index b55c6615e..000000000 --- a/docs/rabbitmq.rst +++ /dev/null @@ -1,6 +0,0 @@ -RabbitMQ Container -=================== - -Allows to spin up RabbitMQ container. - -.. autoclass:: testcontainers.rabbitmq.RabbitMqContainer \ No newline at end of file diff --git a/docs/redis.rst b/docs/redis.rst deleted file mode 100644 index ce2c220c8..000000000 --- a/docs/redis.rst +++ /dev/null @@ -1,6 +0,0 @@ -Redis Container -=================== - -Allows to spin up Redis container. - -.. autoclass:: testcontainers.redis.RedisContainer \ No newline at end of file diff --git a/docs/selenium.rst b/docs/selenium.rst deleted file mode 100644 index ac368333b..000000000 --- a/docs/selenium.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. automodule:: testcontainers.selenium - :members: BrowserWebDriverContainer diff --git a/elasticsearch/README.rst b/elasticsearch/README.rst new file mode 100644 index 000000000..5c6555d32 --- /dev/null +++ b/elasticsearch/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer diff --git a/google/README.rst b/google/README.rst new file mode 100644 index 000000000..dc03a080e --- /dev/null +++ b/google/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.google.PubSubContainer diff --git a/kafka/README.rst b/kafka/README.rst new file mode 100644 index 000000000..a4846e335 --- /dev/null +++ b/kafka/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.kafka.KafkaContainer diff --git a/keycloak/README.rst b/keycloak/README.rst new file mode 100644 index 000000000..3cce2d062 --- /dev/null +++ b/keycloak/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.keycloak.KeycloakContainer diff --git a/localstack/README.rst b/localstack/README.rst new file mode 100644 index 000000000..05df74274 --- /dev/null +++ b/localstack/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.localstack.LocalStackContainer diff --git a/minio/README.rst b/minio/README.rst new file mode 100644 index 000000000..6be8abb79 --- /dev/null +++ b/minio/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.minio.MinioContainer diff --git a/mongodb/README.rst b/mongodb/README.rst new file mode 100644 index 000000000..d8f9cdf58 --- /dev/null +++ b/mongodb/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.mongodb.MongoDbContainer diff --git a/mssql/README.rst b/mssql/README.rst new file mode 100644 index 000000000..8a2f026d2 --- /dev/null +++ b/mssql/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.mssql.SqlServerContainer diff --git a/mysql/README.rst b/mysql/README.rst new file mode 100644 index 000000000..d5b52d1d5 --- /dev/null +++ b/mysql/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.mysql.MySqlContainer diff --git a/neo4j/README.rst b/neo4j/README.rst new file mode 100644 index 000000000..42691ba0e --- /dev/null +++ b/neo4j/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.neo4j.Neo4jContainer diff --git a/nginx/README.rst b/nginx/README.rst new file mode 100644 index 000000000..ff1504759 --- /dev/null +++ b/nginx/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.nginx.NginxContainer diff --git a/opensearch/README.rst b/opensearch/README.rst new file mode 100644 index 000000000..8848f0c98 --- /dev/null +++ b/opensearch/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.opensearch.OpenSearchContainer diff --git a/oracle/README.rst b/oracle/README.rst new file mode 100644 index 000000000..390c331a0 --- /dev/null +++ b/oracle/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.oracle.OracleDbContainer diff --git a/postgres/README.rst b/postgres/README.rst new file mode 100644 index 000000000..7dc4a8a90 --- /dev/null +++ b/postgres/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.postgres.PostgresContainer diff --git a/rabbitmq/README.rst b/rabbitmq/README.rst new file mode 100644 index 000000000..15c66224b --- /dev/null +++ b/rabbitmq/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.rabbitmq.RabbitMqContainer diff --git a/redis/README.rst b/redis/README.rst new file mode 100644 index 000000000..333cb246f --- /dev/null +++ b/redis/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.redis.RedisContainer diff --git a/release-process.rst b/release-process.rst deleted file mode 100644 index 8cfa7c6d0..000000000 --- a/release-process.rst +++ /dev/null @@ -1,69 +0,0 @@ -Release process -=============== - -Run tests on target branch --------------------------- - -Steps:: - - tox -epep8 - tox -esphinx-docs - - -Decleare package version ------------------------- - -In setup.py bump version to the next:: - - version='X.X.X' to version='X.X.Y' - -Cut off stable branch ---------------------- - -Steps:: - - git checkout -b vX.X.X-stable - git push origin vX.X.X-stable - - -Create GitHub tag ------------------ - -Steps:: - - Releases ---> Draft New Release - Name: python-testrelease version X.X.X stable release - - -Collect changes from previous version -------------------------------------- - -Steps:: - - git log --oneline --decorate - - -Build distribution package --------------------------- - -Steps:: - - python setup.py bdist_wheel - - -Check install capability for the whell --------------------------------------- - -Steps:: - - virtualenv .test_venv - source .test_venv/bin/activate - pip install dist/testcontainer-X.X.X-py2.py3-none-any.whl - - -Submit release to PYPI ----------------------- - -Steps:: - - twine dist/* upload diff --git a/selenium/README.rst b/selenium/README.rst new file mode 100644 index 000000000..9308568e9 --- /dev/null +++ b/selenium/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.selenium.BrowserWebDriverContainer From 79856b5f58139e4819aaa8d1397c14bbbb93089c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 18:41:41 -0500 Subject: [PATCH 127/425] Add outstanding doctests (except Oracle). --- compose/testcontainers/compose/__init__.py | 2 +- google/testcontainers/google/pubsub.py | 3 ++- localstack/testcontainers/localstack/__init__.py | 2 +- mysql/testcontainers/mysql/__init__.py | 2 +- oracle/testcontainers/oracle/__init__.py | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index b5961aa81..cefd0cc3f 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -31,7 +31,7 @@ class DockerCompose(object): Example ------- - :: + .. doctest:: with DockerCompose("/home/project", compose_file_name=["docker-compose-1.yml", "docker-compose-2.yml"], diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index b15f4f854..0dc1d9463 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -24,7 +24,8 @@ class PubSubContainer(DockerContainer): The :code:`pubsub` instance provides convenience methods :code:`get_publisher` and :code:`get_subscriber` to connect to the emulator without having to set the environment variable :code:`PUBSUB_EMULATOR_HOST`. - :: + + .. doctest:: def test_docker_run_pubsub(): config = PubSubContainer('google/cloud-sdk:emulators') diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index 105086bef..c76a5f2f0 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -30,7 +30,7 @@ class LocalStackContainer(DockerContainer): The endpoint can be used to create a client with the boto3 library: - :: + .. doctest:: dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) scan_result = dynamo_client.scan(TableName='foo') diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index d2362084f..c96814a79 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -77,7 +77,7 @@ class MariaDbContainer(MySqlContainer): Example ------- - :: + .. doctest:: with MariaDbContainer("mariadb:latest") as mariadb: e = sqlalchemy.create_engine(mariadb.get_connection_url()) diff --git a/oracle/testcontainers/oracle/__init__.py b/oracle/testcontainers/oracle/__init__.py index 4a528e04d..595bbe32e 100644 --- a/oracle/testcontainers/oracle/__init__.py +++ b/oracle/testcontainers/oracle/__init__.py @@ -7,7 +7,7 @@ class OracleDbContainer(DbContainer): Example ------- - :: + .. code-block:: >>> import sqlalchemy >>> from testcontainers.oracle import OracleDbContainer From 5125333bf60526813319ad99c2d77f5bc243d1bd Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 18:59:43 -0500 Subject: [PATCH 128/425] Build the package and publish on `master`. --- .github/workflows/main.yml | 15 +++++++++-- .github/workflows/pypi-release.yml | 42 ------------------------------ 2 files changed, 13 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/pypi-release.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f3b7d57b4..d20fa1606 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,9 +1,9 @@ name: testcontainers-python on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: sphinx: @@ -108,3 +108,14 @@ jobs: run: > pytest -svx --cov-report=term-missing --cov=testcontainers.${{ matrix.test-component }} --tb=short ${{ matrix.test-component }}/tests + - name: Build the package + working-directory: ${{ matrix.test-component }} + run: | + python setup.py bdist_wheel + twine check dist/* + - name: Publish the package to pypi + if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'testcontainers' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: twine upload ${{ matrix.test-component }}/dist/* diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml deleted file mode 100644 index 083bcc27f..000000000 --- a/.github/workflows/pypi-release.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Upload Python packages to PyPi -on: - push: - tags: - - 'v*' - -jobs: - build: - runs-on: ubuntu-latest - env: - python-version: 3.8 - steps: - - uses: actions/checkout@v2 - - - name: Setup python ${{ env.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ env.python-version }} - - - name: Cache Python dependencies - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles(format('requirements/{0}.txt', env.python-version)) }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - pip install -r requirements/${{ env.python-version }}.txt - - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python generate_version.py - python setup.py bdist_wheel - twine upload dist/* From ec1d326b6f9edbadf3f53588e4aa92fe4b351cb2 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 19:04:30 -0500 Subject: [PATCH 129/425] Build sphinx docs with python 3.10. --- .github/workflows/main.yml | 16 ++++------------ .readthedocs.yml | 12 ++++-------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d20fa1606..0f83dc063 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,26 +7,18 @@ on: jobs: sphinx: - strategy: - fail-fast: false - matrix: - python-version: - - "3.7" - - "3.8" - - "3.9" - - "3.10" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Setup python ${{ matrix.python-version }} + - name: Setup python 3.10 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: "3.10" - name: Cache Python dependencies uses: actions/cache@v2 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles(format('requirements/{0}.txt', matrix.python-version)) }} + key: ${{ runner.os }}-pip-${{ hashFiles('requirements/3.10.txt') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- @@ -34,7 +26,7 @@ jobs: run: | python -m pip install --upgrade pip pip install wheel - pip install -r requirements/${{ matrix.python-version }}.txt + pip install -r requirements/3.10.txt - name: Build documentation run: sphinx-build -nW . docs/_build - name: Run doctests diff --git a/.readthedocs.yml b/.readthedocs.yml index b3a90fc61..cc40a2e5d 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,13 +1,9 @@ # Read the Docs configuration file (see https://docs.readthedocs.io/en/stable/config-file/v2.html # for details). -# Required version: 2 -sphinx: - configuration: conf.py -formats: all -python: - version: "3.7" - install: - - requirements: requirements/3.7.txt +build: + os: ubuntu-22.04 + tools: + python: "3.10" From c04c27d60c7c032caffd839cb4ea886e7c0f4d78 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 19:35:43 -0500 Subject: [PATCH 130/425] Provide explicit `long_description` required by python 3.10. --- arangodb/setup.py | 6 +++++- azurite/setup.py | 6 +++++- clickhouse/setup.py | 6 +++++- compose/setup.py | 6 +++++- core/setup.py | 6 +++++- elasticsearch/setup.py | 6 +++++- google/setup.py | 6 +++++- kafka/setup.py | 6 +++++- keycloak/setup.py | 6 +++++- localstack/setup.py | 6 +++++- minio/setup.py | 6 +++++- mongodb/setup.py | 6 +++++- mssql/setup.py | 6 +++++- mysql/setup.py | 6 +++++- neo4j/setup.py | 6 +++++- nginx/setup.py | 6 +++++- opensearch/setup.py | 6 +++++- oracle/setup.py | 6 +++++- postgres/setup.py | 6 +++++- rabbitmq/setup.py | 6 +++++- redis/setup.py | 6 +++++- selenium/setup.py | 6 +++++- 22 files changed, 110 insertions(+), 22 deletions(-) diff --git a/arangodb/setup.py b/arangodb/setup.py index 4952cd39e..2309bca7f 100644 --- a/arangodb/setup.py +++ b/arangodb/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Arango DB component of testcontainers-python." + setup( name="testcontainers-arangodb", version="0.0.1rc1", packages=find_namespace_packages(), - description="Arango DB component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/azurite/setup.py b/azurite/setup.py index 37bbc396e..b003a39d6 100644 --- a/azurite/setup.py +++ b/azurite/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Core component of testcontainers-python." + setup( name="testcontainers-azurite", version="0.0.1rc1", packages=find_namespace_packages(), - description="Core component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/clickhouse/setup.py b/clickhouse/setup.py index 16250494d..2f7c9ebc3 100644 --- a/clickhouse/setup.py +++ b/clickhouse/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Clickhouse component of testcontainers-python." + setup( name="testcontainers-clickhouse", version="0.0.1rc1", packages=find_namespace_packages(), - description="Clickhouse component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/compose/setup.py b/compose/setup.py index a3643e68d..bfe128e22 100644 --- a/compose/setup.py +++ b/compose/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Docker Compose component of testcontainers-python." + setup( name="testcontainers-compose", version="0.0.1rc1", packages=find_namespace_packages(), - description="Docker Compose component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/core/setup.py b/core/setup.py index a469b4a8a..f857f7952 100644 --- a/core/setup.py +++ b/core/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Core component of testcontainers-python." + setup( name="testcontainers-core", version="0.0.1rc1", packages=find_namespace_packages(), - description="Core component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "docker>=4.0.0", diff --git a/elasticsearch/setup.py b/elasticsearch/setup.py index 6954a62f7..09d57cc6b 100644 --- a/elasticsearch/setup.py +++ b/elasticsearch/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Elasticsearch component of testcontainers-python." + setup( name="testcontainers-elasticsearch", version="0.0.1rc1", packages=find_namespace_packages(), - description="Elasticsearch component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/google/setup.py b/google/setup.py index b1ba077d6..a772c93a9 100644 --- a/google/setup.py +++ b/google/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Google Cloud Platform component of testcontainers-python." + setup( name="testcontainers-gcp", version="0.0.1rc1", packages=find_namespace_packages(), - description="Google Cloud Platform component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/kafka/setup.py b/kafka/setup.py index f8798dc93..ac9412f7b 100644 --- a/kafka/setup.py +++ b/kafka/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Kafka component of testcontainers-python." + setup( name="testcontainers-kafka", version="0.0.1rc1", packages=find_namespace_packages(), - description="Kafka component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/keycloak/setup.py b/keycloak/setup.py index 091624743..13236ea55 100644 --- a/keycloak/setup.py +++ b/keycloak/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Keycloak component of testcontainers-python." + setup( name="testcontainers-keycloak", version="0.0.1rc1", packages=find_namespace_packages(), - description="Keycloak component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/localstack/setup.py b/localstack/setup.py index 3ede68214..649104a71 100644 --- a/localstack/setup.py +++ b/localstack/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "LocalStack component of testcontainers-python." + setup( name="testcontainers-localstack", version="0.0.1rc1", packages=find_namespace_packages(), - description="LocalStack component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/minio/setup.py b/minio/setup.py index 1ac782d41..939257079 100644 --- a/minio/setup.py +++ b/minio/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "MinIO component of testcontainers-python." + setup( name="testcontainers-minio", version="0.0.1rc1", packages=find_namespace_packages(), - description="MinIO component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/mongodb/setup.py b/mongodb/setup.py index e219f3c05..0f8966c6a 100644 --- a/mongodb/setup.py +++ b/mongodb/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "MongoDB component of testcontainers-python." + setup( name="testcontainers-mongodb", version="0.0.1rc1", packages=find_namespace_packages(), - description="MongoDB component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/mssql/setup.py b/mssql/setup.py index 5fb9da6ae..082a3fc8d 100644 --- a/mssql/setup.py +++ b/mssql/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Microsoft SQL Server component of testcontainers-python." + setup( name="testcontainers-mssql", version="0.0.1rc1", packages=find_namespace_packages(), - description="Microsoft SQL Server component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/mysql/setup.py b/mysql/setup.py index 59d324ba2..4ca79d715 100644 --- a/mysql/setup.py +++ b/mysql/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "MySQL component of testcontainers-python." + setup( name="testcontainers-mysql", version="0.0.1rc1", packages=find_namespace_packages(), - description="MySQL component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/neo4j/setup.py b/neo4j/setup.py index 154b0beb4..ec2c30bb3 100644 --- a/neo4j/setup.py +++ b/neo4j/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Neo4j component of testcontainers-python." + setup( name="testcontainers-neo4j", version="0.0.1rc1", packages=find_namespace_packages(), - description="Neo4j component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/nginx/setup.py b/nginx/setup.py index 28eb17010..bb24ba3ce 100644 --- a/nginx/setup.py +++ b/nginx/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "NGINX component of testcontainers-python." + setup( name="testcontainers-nginx", version="0.0.1rc1", packages=find_namespace_packages(), - description="NGINX component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/opensearch/setup.py b/opensearch/setup.py index 674c7b028..1e3db8c76 100644 --- a/opensearch/setup.py +++ b/opensearch/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "OpenSearch component of testcontainers-python." + setup( name="testcontainers-opensearch", version="0.0.1rc1", packages=find_namespace_packages(), - description="OpenSearch component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/oracle/setup.py b/oracle/setup.py index 0416937ae..0a6fd4e26 100644 --- a/oracle/setup.py +++ b/oracle/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Oracle component of testcontainers-python." + setup( name="testcontainers-oracle", version="0.0.1rc1", packages=find_namespace_packages(), - description="Oracle component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/postgres/setup.py b/postgres/setup.py index 516f4fd0e..1d9abd351 100644 --- a/postgres/setup.py +++ b/postgres/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "PostgreSQL component of testcontainers-python." + setup( name="testcontainers-postgres", version="0.0.1rc1", packages=find_namespace_packages(), - description="PostgreSQL component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/rabbitmq/setup.py b/rabbitmq/setup.py index a19897edf..853887ead 100644 --- a/rabbitmq/setup.py +++ b/rabbitmq/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "RabbitMQ component of testcontainers-python." + setup( name="testcontainers-rabbitmq", version="0.0.1rc1", packages=find_namespace_packages(), - description="RabbitMQ component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/redis/setup.py b/redis/setup.py index fe713341d..2e1131e5a 100644 --- a/redis/setup.py +++ b/redis/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Redis component of testcontainers-python." + setup( name="testcontainers-redis", version="0.0.1rc1", packages=find_namespace_packages(), - description="Redis component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", diff --git a/selenium/setup.py b/selenium/setup.py index bd8bab53c..b2fff38f3 100644 --- a/selenium/setup.py +++ b/selenium/setup.py @@ -1,10 +1,14 @@ from setuptools import setup, find_namespace_packages +description = "Selenium component of testcontainers-python." + setup( name="testcontainers-selenium", version="0.0.1rc1", packages=find_namespace_packages(), - description="Selenium component of testcontainers-python.", + description=description, + long_description=description, + long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", From a38167bf7b6f5b749f423c5ff0f9953cb09e4b88 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 19:47:30 -0500 Subject: [PATCH 131/425] Update main `README.rst`. --- Makefile | 2 +- README.rst | 97 ++++++++++++++++++------------------------------------ 2 files changed, 33 insertions(+), 66 deletions(-) diff --git a/Makefile b/Makefile index 1eb787fbe..f5fb3801c 100644 --- a/Makefile +++ b/Makefile @@ -40,4 +40,4 @@ ${TESTS} : tests/% : image/% # Target to build the documentation docs : - sphinx-build -nW docs docs/_build/html + sphinx-build -nW . docs/_build diff --git a/README.rst b/README.rst index 8fe92cf2c..732445606 100644 --- a/README.rst +++ b/README.rst @@ -8,45 +8,32 @@ testcontainers-python .. image:: https://readthedocs.org/projects/testcontainers-python/badge/?version=latest :target: http://testcontainers-python.readthedocs.io/en/latest/?badge=latest -Python port for testcontainers-java that allows using docker containers for functional and integration testing. Testcontainers-python provides capabilities to spin up docker containers (such as a database, Selenium web browser, or any other container) for testing. - -Currently available features: - -* Generic docker container -* ArangoDB container -* Azurite container -* ClickHouse container -* ElasticSearch container -* Kafka container -* Keycloak container -* LocalStack container -* MariaDb container -* Microsoft SQL Server container -* Minio container -* MongoDB container -* MySql Db container -* Neo4j container -* NGINX container -* OpenSearch container -* OracleDb container -* PostgreSQL Db container -* RabbitMQ container -* Redis container -* Selenium Grid container -* Selenium Standalone container +testcontainers-python facilitates the use of Docker containers for functional and integration testing. The collection of packages currently supports the following features. -Installation ------------- - -The testcontainers package is available from `PyPI `_, and it can be installed using :code:`pip`. Depending on which containers are needed, you can specify additional dependencies as `extras `_: - -.. code-block:: bash +.. toctree:: - # Install without extras - pip install testcontainers - # Install with one or more extras - pip install testcontainers[mysql] - pip install testcontainers[mysql,oracle] + core/README + arangodb/README + azurite/README + clickhouse/README + compose/README + elasticsearch/README + google/README + kafka/README + keycloak/README + localstack/README + minio/README + mongodb/README + mssql/README + mysql/README + neo4j/README + nginx/README + opensearch/README + oracle/README + postgres/README + rabbitmq/README + redis/README + selenium/README Basic usage ----------- @@ -64,9 +51,15 @@ Basic usage >>> version 'PostgreSQL 9.5...' -The snippet above will spin up a Postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. +The snippet above will spin up a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. More extensive documentation can be found at `Read The Docs `_. + +Installation +------------ + +The suite of testcontainers packages is available on `PyPI `_, and individual packages can be installed using :code:`pip`. We recommend installing the package you need by running :code:`pip install testcontainers-`, e.g., :code:`pip install testcontainers-mysql`. + +For backwards compatibility, packages can also be installed by specifying `extras `_, e.g., :code:`pip install testcontainers[mysql]`. -More extensive documentation can be found at `Read The Docs `_. Usage within Docker (e.g., in a CI) ----------------------------------- @@ -76,7 +69,6 @@ When trying to launch a testcontainer from within a Docker container two things 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. - Setting up a development environment ------------------------------------ @@ -100,28 +92,3 @@ You can contribute a new container in three steps: 1. Create a new module at :code:`testcontainers/[my fancy container].py` that implements the new functionality. 2. Create a new test module at :code:`tests/test_[my fancy container].py` that tests the new functionality. 3. Add :code:`[my fancy container]` to the list of test components in the GitHub Action configuration at :code:`.github/workflows/main.yml`. - -.. toctree:: - - core/README - arangodb/README - azurite/README - clickhouse/README - compose/README - elasticsearch/README - google/README - kafka/README - keycloak/README - localstack/README - minio/README - mongodb/README - mssql/README - mysql/README - neo4j/README - nginx/README - opensearch/README - oracle/README - postgres/README - rabbitmq/README - redis/README - selenium/README From 159e95d9ee4fe3f9d356c1d68e06618727fbe2cf Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 20:08:46 -0500 Subject: [PATCH 132/425] Restrict pypi upload to python 3.10. --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0f83dc063..a397776ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -106,7 +106,11 @@ jobs: python setup.py bdist_wheel twine check dist/* - name: Publish the package to pypi - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'testcontainers' + if: > + github.event_name == 'push' + && github.ref == 'refs/heads/master' + && github.repository_owner == 'testcontainers' + && matrix.python-version == '3.10' env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} From 91f54d546702939bd142798e42ba63caf4d54e3d Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 5 Jan 2023 21:59:31 -0500 Subject: [PATCH 133/425] Add meta package for backwards compatibility. --- .github/workflows/docs.yml | 32 +++++++++++++++ .github/workflows/main.yml | 22 +++++------ Makefile | 79 ++++++++++++++++++++++++-------------- README.rst | 2 +- conf.py | 2 +- generate_version.py | 12 ------ meta/README.rst | 1 + meta/setup.py | 70 +++++++++++++++++++++++++++++++++ requirements.in | 4 +- requirements/3.10.txt | 5 +++ requirements/3.7.txt | 5 +++ requirements/3.8.txt | 5 +++ requirements/3.9.txt | 5 +++ setup.py | 73 ----------------------------------- 14 files changed, 189 insertions(+), 128 deletions(-) create mode 100644 .github/workflows/docs.yml delete mode 100644 generate_version.py create mode 100644 meta/README.rst create mode 100644 meta/setup.py delete mode 100644 setup.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..2cbc4d732 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,32 @@ +name: testcontainers documentation +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Cache Python dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements/3.10.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Python dependencies + run: | + pip install --upgrade pip + pip install -r requirements/3.10.txt + - name: Build documentation + run: make docs + - name: Run doctests + run: make doctests diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a397776ef..a3390dded 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: testcontainers-python +name: testcontainers packages on: push: branches: [master] @@ -52,6 +52,7 @@ jobs: - kafka - keycloak - localstack + - meta - minio - mongodb - mssql @@ -81,10 +82,10 @@ jobs: ${{ runner.os }}- - name: Install Python dependencies run: | - python -m pip install --upgrade pip - pip install wheel + pip install --upgrade pip pip install -r requirements/${{ matrix.python-version }}.txt - name: Run docker diagnostics + if: matrix.test-component == 'core' run: | echo "Build minimal container for docker-in-docker diagnostics" docker build -f Dockerfile.diagnostics -t testcontainers-python . @@ -95,16 +96,12 @@ jobs: echo "Container diagnostics with host network" docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - name: Lint the code - run: flake8 + run: make ${{ matrix.test-component }}/lint - name: Run tests - run: > - pytest -svx --cov-report=term-missing --cov=testcontainers.${{ matrix.test-component }} - --tb=short ${{ matrix.test-component }}/tests + if: matrix.test-component != 'meta' + run: make ${{ matrix.test-component }}/tests - name: Build the package - working-directory: ${{ matrix.test-component }} - run: | - python setup.py bdist_wheel - twine check dist/* + run: make ${{ matrix.test-component }}/dist - name: Publish the package to pypi if: > github.event_name == 'push' @@ -114,4 +111,5 @@ jobs: env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: twine upload ${{ matrix.test-component }}/dist/* + TWINE_REPOSITORY: pypi + run: make ${{ matrix.test-component }}/publish diff --git a/Makefile b/Makefile index f5fb3801c..b754a5898 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,66 @@ PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 +PYTHON_VERSION ?= 3.10 +IMAGE = testcontainers-python:${PYTHON_VERSION} REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) -TESTS = $(addprefix tests/,${PYTHON_VERSIONS}) -IMAGES = $(addprefix image/,${PYTHON_VERSIONS}) RUN = docker run --rm -it +TWINE_REPOSITORY ?= testpypi +# Get all directories that contain a setup.py and get the directory name. +PACKAGES = $(subst /,,$(dir $(wildcard */setup.py))) + +# All */dist folders for each of the packages. +DISTRIBUTIONS = $(addsuffix /dist,${PACKAGES}) +UPLOAD = $(addsuffix /upload,${PACKAGES}) +# All */tests folders for each of the test suites. +TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES})) +TESTS_DIND = $(addsuffix -dind,${TESTS}) +# All linting targets. +LINT = $(addsuffix /lint,${PACKAGES}) + +# Targets to build a distribution for each package. +dist: ${DISTRIBUTIONS} +${DISTRIBUTIONS} : %/dist : %/setup.py + cd $* \ + && python setup.py bdist_wheel \ + && twine check dist/* + +# Targets to run the test suite for each package. +tests : ${TESTS} +${TESTS} : %/tests : + pytest -svx --cov-report=term-missing --cov=testcontainers.$* --tb=short $*/tests -.PHONY : docs +# Targets to lint the code. +lint : ${LINT} +${LINT} : %/lint : + flake8 $* -# Default target +# Targets to publish packages. +${UPLOAD} : %/upload : + twine upload --non-interactive --repository=${TWINE_REPOSITORY} --skip-existing $*/dist/* -default : tests/3.8 +# Targets to build docker images +image: requirements/${PYTHON_VERSION}.txt + docker build --build-arg version=${PYTHON_VERSION} -t ${IMAGE} . +# Targets to run tests in docker containers +tests-dind : ${TESTS_DIND} -# Targets to build requirement files +${TESTS_DIND} : %/tests-dind : image + ${RUN} -v /var/run/docker.sock:/var/run/docker.sock ${IMAGE} \ + bash -c "make $*/lint $*/tests" -requirements : ${REQUIREMENTS} +# Target to build the documentation +docs : + sphinx-build -nW . docs/_build + +doctests : + sphinx-build -b doctest . docs/_build +# Targets to build requirement files +requirements : ${REQUIREMENTS} ${REQUIREMENTS} : requirements/%.txt : requirements.in */setup.py mkdir -p $(dir $@) ${RUN} -w /workspace -v `pwd`:/workspace --platform=linux/amd64 python:$* bash -c \ "pip install pip-tools && pip-compile --resolver=backtracking -v --upgrade -o $@ $<" - -# Targets to build docker images - -images : ${IMAGES} - -${IMAGES} : image/% : requirements/%.txt - docker build --build-arg version=$* -t testcontainers-python:$* . - - -# Targets to run tests in docker containers - -tests : ${TESTS} - -${TESTS} : tests/% : image/% - ${RUN} -v /var/run/docker.sock:/var/run/docker.sock testcontainers-python:$* \ - bash -c "flake8 && pytest -v ${ARGS}" - -# Target to build the documentation - -docs : - sphinx-build -nW . docs/_build +# Targets that do not generate file-level artifacts. +.PHONY : dists ${DISTRIBUTIONS} docs doctests image tests ${TESTS} diff --git a/README.rst b/README.rst index 732445606..92a300df6 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,7 @@ Installation The suite of testcontainers packages is available on `PyPI `_, and individual packages can be installed using :code:`pip`. We recommend installing the package you need by running :code:`pip install testcontainers-`, e.g., :code:`pip install testcontainers-mysql`. -For backwards compatibility, packages can also be installed by specifying `extras `_, e.g., :code:`pip install testcontainers[mysql]`. +For backwards compatibility, packages can also be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[mysql]`. Usage within Docker (e.g., in a CI) diff --git a/conf.py b/conf.py index 9f18309fa..efae1fe68 100644 --- a/conf.py +++ b/conf.py @@ -76,7 +76,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', "meta/README.rst"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/generate_version.py b/generate_version.py deleted file mode 100644 index b8e02c082..000000000 --- a/generate_version.py +++ /dev/null @@ -1,12 +0,0 @@ -import os - -# Automatically determine the version to push to pypi -github_ref = os.environ.get('GITHUB_REF', '') -prefix = 'refs/tags/v' -if github_ref.startswith(prefix): - version = github_ref[len(prefix):] - with open('VERSION', 'w') as fp: - fp.write(version) - print('Wrote version %s to VERSION file.' % version) -else: - raise ValueError('Could not identify version in %s.' % github_ref) diff --git a/meta/README.rst b/meta/README.rst new file mode 100644 index 000000000..cbd214fc1 --- /dev/null +++ b/meta/README.rst @@ -0,0 +1 @@ +The :code:`testcontainers` meta package facilitates the installation of the collection of namespace packages that make up the testcontainers ecosystem for python. It follows `Jupyter's approach `__ of installing a collection of packages. diff --git a/meta/setup.py b/meta/setup.py new file mode 100644 index 000000000..8ced316e1 --- /dev/null +++ b/meta/setup.py @@ -0,0 +1,70 @@ +# +# 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 setuptools + +description = "Python interface for throwaway instances of anything that can run in a Docker " \ + "container." +long_description = f"{description} See https://testcontainers-python.readthedocs.io/en/latest/ " \ + "for details." + +setuptools.setup( + name="testcontainers", + version="4.0.0rc1", + description=description, + long_description=long_description, + long_description_content_type="text/x-rst", + author="Sergey Pirogov", + author_email="automationremarks@gmail.com", + url="https://github.com/testcontainers/testcontainers-python", + keywords=["testing", "logging", "docker", "test automation"], + classifiers=[ + "License :: OSI Approved :: Apache Software License", + "Intended Audience :: Information Technology", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Software Development :: Libraries :: Python Modules", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + ], + install_requires=[ + "testcontainers-core", + ], + extras_require={ + "arangodb": ["testcontainers-arangodb"], + "azurite": ["testcontainers-azurite"], + "clickhouse": ["testcontainers-clickhouse"], + "docker-compose": ["testcontainers-compose"], + "google-cloud-pubsub": ["testcontainers-gcp"], + "kafka": ["testcontainers-kafka"], + "keycloak": ["testcontainers-keycloak"], + "minio": ["testcontainers-minio"], + "mongo": ["testcontainers-mongo"], + "mssqlserver": ["testcontainers-mssql"], + "mysql": ["testcontainers-mysql"], + "neo4j": ["testcontainers-neo4j"], + "opensearch": ["testcontainers-opensearch"], + "oracle": ["testcontainers-oracle"], + "postgresql": ["testcontainers-postgres"], + "rabbitmq": ["testcontainers-rabbitmq"], + "redis": ["testcontainers-redis"], + "selenium": ["testcontainers-selenium"], + }, + python_requires=">=3.7", +) diff --git a/requirements.in b/requirements.in index eab7d18bf..0204fdb8e 100644 --- a/requirements.in +++ b/requirements.in @@ -1,13 +1,14 @@ --e file:core -e file:arangodb -e file:azurite -e file:clickhouse +-e file:core -e file:compose -e file:elasticsearch -e file:google -e file:kafka -e file:keycloak -e file:localstack +-e file:meta -e file:minio -e file:mongodb -e file:mssql @@ -28,3 +29,4 @@ pytest pytest-cov sphinx twine +wheel diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 8533a5cad..a3dfc3319 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -4,6 +4,8 @@ # # pip-compile --output-file=requirements/3.10.txt --resolver=backtracking requirements.in # +-e file:meta + # via -r requirements.in -e file:arangodb # via -r requirements.in -e file:azurite @@ -15,6 +17,7 @@ -e file:core # via # -r requirements.in + # testcontainers # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse @@ -426,6 +429,8 @@ websocket-client==0.59.0 # via # docker # docker-compose +wheel==0.38.4 + # via -r requirements.in wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index bf83cf3d2..9e6f252c8 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -4,6 +4,8 @@ # # pip-compile --output-file=requirements/3.7.txt --resolver=backtracking requirements.in # +-e file:meta + # via -r requirements.in -e file:arangodb # via -r requirements.in -e file:azurite @@ -15,6 +17,7 @@ -e file:core # via # -r requirements.in + # testcontainers # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse @@ -448,6 +451,8 @@ websocket-client==0.59.0 # via # docker # docker-compose +wheel==0.38.4 + # via -r requirements.in wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 diff --git a/requirements/3.8.txt b/requirements/3.8.txt index b31174517..e3ae06b65 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -4,6 +4,8 @@ # # pip-compile --output-file=requirements/3.8.txt --resolver=backtracking requirements.in # +-e file:meta + # via -r requirements.in -e file:arangodb # via -r requirements.in -e file:azurite @@ -15,6 +17,7 @@ -e file:core # via # -r requirements.in + # testcontainers # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse @@ -435,6 +438,8 @@ websocket-client==0.59.0 # via # docker # docker-compose +wheel==0.38.4 + # via -r requirements.in wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 80140fbe0..b78eb42dd 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -4,6 +4,8 @@ # # pip-compile --output-file=requirements/3.9.txt --resolver=backtracking requirements.in # +-e file:meta + # via -r requirements.in -e file:arangodb # via -r requirements.in -e file:azurite @@ -15,6 +17,7 @@ -e file:core # via # -r requirements.in + # testcontainers # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse @@ -427,6 +430,8 @@ websocket-client==0.59.0 # via # docker # docker-compose +wheel==0.38.4 + # via -r requirements.in wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index c3a91f6f2..000000000 --- a/setup.py +++ /dev/null @@ -1,73 +0,0 @@ -# -# 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 setuptools - -with open('README.rst') as fp: - long_description = fp.read() -long_description = long_description.replace(".. doctest::", ".. code-block::") - - -setuptools.setup( - name='testcontainers', - version='4.0.0rc1', - description='Library provides lightweight, throwaway instances of common databases, Selenium ' - 'web browsers, or anything else that can run in a Docker container', - author='Sergey Pirogov', - author_email='automationremarks@gmail.com', - url='https://github.com/testcontainers/testcontainers-python', - keywords=['testing', 'logging', 'docker', 'test automation'], - classifiers=[ - 'License :: OSI Approved :: Apache Software License', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Operating System :: MacOS', - ], - install_requires=[ - 'docker>=4.0.0', - 'wrapt', - 'deprecation', - ], - extras_require={ - 'arangodb': ['testcontainers-arangodb'], - 'azurite': ['testcontainers-azurite'], - 'clickhouse': ['testcontainers-clickhouse'], - 'docker-compose': ['testcontainers-compose'], - 'google-cloud-pubsub': ['testcontainers-gcp'], - 'kafka': ['testcontainers-kafka'], - 'keycloak': ['testcontainers-keycloak'], - 'minio': ['testcontainers-minio'], - 'mongo': ['testcontainers-mongo'], - 'mssqlserver': ['testcontainers-mssql'], - 'mysql': ['testcontainers-mysql'], - 'neo4j': ['testcontainers-neo4j'], - 'opensearch': ['testcontainers-opensearch'], - 'oracle': ['testcontainers-oracle'], - 'postgresql': ['testcontainers-postgres'], - 'rabbitmq': ['testcontainers-rabbitmq'], - 'redis': ['testcontainers-redis'], - 'selenium': ['testcontainers-selenium'], - }, - long_description_content_type="text/x-rst", - long_description=long_description, - python_requires='>=3.7', -) From 2bfbdb91d732248946e7e596cb184e0ff05d1ebb Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 09:03:22 -0500 Subject: [PATCH 134/425] Skip `testpypi` upload for `meta` package. --- .github/workflows/main.yml | 43 +++++++------------------------------- .gitignore | 1 + Makefile | 8 +++++-- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3390dded..da95aeb10 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,32 +6,6 @@ on: branches: [master] jobs: - sphinx: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup python 3.10 - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Cache Python dependencies - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements/3.10.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install wheel - pip install -r requirements/3.10.txt - - name: Build documentation - run: sphinx-build -nW . docs/_build - - name: Run doctests - run: sphinx-build -b doctest . docs/_build - build: strategy: fail-fast: false @@ -41,7 +15,7 @@ jobs: - "3.8" - "3.9" - "3.10" - test-component: + component: - arangodb - azurite - clickhouse @@ -85,7 +59,7 @@ jobs: pip install --upgrade pip pip install -r requirements/${{ matrix.python-version }}.txt - name: Run docker diagnostics - if: matrix.test-component == 'core' + if: matrix.component == 'core' run: | echo "Build minimal container for docker-in-docker diagnostics" docker build -f Dockerfile.diagnostics -t testcontainers-python . @@ -96,13 +70,13 @@ jobs: echo "Container diagnostics with host network" docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - name: Lint the code - run: make ${{ matrix.test-component }}/lint + run: make ${{ matrix.component }}/lint - name: Run tests - if: matrix.test-component != 'meta' - run: make ${{ matrix.test-component }}/tests + if: matrix.component != 'meta' + run: make ${{ matrix.component }}/tests - name: Build the package - run: make ${{ matrix.test-component }}/dist - - name: Publish the package to pypi + run: make ${{ matrix.component }}/dist + - name: Upload the package to pypi if: > github.event_name == 'push' && github.ref == 'refs/heads/master' @@ -111,5 +85,4 @@ jobs: env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - TWINE_REPOSITORY: pypi - run: make ${{ matrix.test-component }}/publish + run: make ${{ matrix.component }}/upload diff --git a/.gitignore b/.gitignore index bd980f755..a2c626860 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ venv .DS_Store .python-version +.env diff --git a/Makefile b/Makefile index b754a5898..1551dedde 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) RUN = docker run --rm -it -TWINE_REPOSITORY ?= testpypi # Get all directories that contain a setup.py and get the directory name. PACKAGES = $(subst /,,$(dir $(wildcard */setup.py))) @@ -34,8 +33,13 @@ ${LINT} : %/lint : flake8 $* # Targets to publish packages. +upload : ${UPLOAD} ${UPLOAD} : %/upload : - twine upload --non-interactive --repository=${TWINE_REPOSITORY} --skip-existing $*/dist/* + if [ ${TWINE_REPOSITORY}-$* = testpypi-meta ]; then \ + echo "Cannot upload meta package to testpypi because of missing permissions."; \ + else \ + twine upload --non-interactive --skip-existing $*/dist/*; \ + fi # Targets to build docker images image: requirements/${PYTHON_VERSION}.txt From e04582fb95b9655a41179bad52743a9065ca3fe3 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 09:41:01 -0500 Subject: [PATCH 135/425] Update instructions for contributing new features. --- .../PULL_REQUEST_TEMPLATE/new_container.md | 15 ++-- README.rst | 76 ++++++++++++------- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index f89a8d6d2..dcdff17b4 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -1,9 +1,8 @@ -You have implemented a new container and would like to contribute it? Great! Here are the necessary steps: +You have implemented a new container and would like to contribute it? Great! Here are the necessary steps. -- [ ] You have added the new container as a module in the `testcontainers` directory (such as `testcontainers/my_fancy_container.py`). -- [ ] You have added any new python dependencies in the `extras_require` section of `setup.py`. -- [ ] You have added the `extra_requires` key to `requirements.in`. -- [ ] You have updated all python requirements by running `make requirements` from the root directory. -- [ ] You have added tests for the new container in the `tests` directory, e.g. `tests/test_my_fancy_container.py`. -- [ ] You have added the name of the container (such as `my_fancy_container`) to the `test-components` matrix in `.github/workflows/main.yml` to ensure the tests are run. -- [ ] You have rebased your development branch on `master` (or merged `master` into your development branch). +- [ ] Create a new feature directory and populate it with the package structure [described in the documentation](https://testcontainers-python.readthedocs.io/en/latest/#package-structure). Copying one of the existing features is likely the best way to get started. +- [ ] Implement the new feature (typically in `__init__.py`) and corresponding tests. +- [ ] Add a line `-e file:[feature name]` to `requirements.in` and run `make requirements`. This command will find any new requirements and generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Then run `pip install -r requirements/[your python version].txt` to install the new requirements. +- [ ] Update the feature `README.rst` and add it to the table of contents (`toctree` directive) in the top-level `README.rst`. +- [ ] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `master` branch. +- [ ] Rebase your development branch on `master` (or merge `master` into your development branch). diff --git a/README.rst b/README.rst index 92a300df6..633b35130 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ testcontainers-python .. image:: https://github.com/testcontainers/testcontainers-python/workflows/testcontainers-python/badge.svg :target: https://github.com/testcontainers/testcontainers-python/actions/workflows/main.yml -.. image:: https://img.shields.io/pypi/v/testcontainers.svg?style=flat-square +.. image:: https://img.shields.io/pypi/v/testcontainers.svg :target: https://pypi.python.org/pypi/testcontainers .. image:: https://readthedocs.org/projects/testcontainers-python/badge/?version=latest :target: http://testcontainers-python.readthedocs.io/en/latest/?badge=latest @@ -35,18 +35,17 @@ testcontainers-python facilitates the use of Docker containers for functional an redis/README selenium/README -Basic usage ------------ +Getting Started +--------------- .. doctest:: >>> from testcontainers.postgres import PostgresContainer >>> import sqlalchemy - >>> postgres_container = PostgresContainer("postgres:9.5") - >>> with postgres_container as postgres: - ... e = sqlalchemy.create_engine(postgres.get_connection_url()) - ... result = e.execute("select version()") + >>> with PostgresContainer("postgres:9.5") as postgres: + ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) + ... result = engine.execute("select version()") ... version, = result.fetchone() >>> version 'PostgreSQL 9.5...' @@ -56,39 +55,64 @@ The snippet above will spin up a postgres database in a container. The :code:`ge Installation ------------ -The suite of testcontainers packages is available on `PyPI `_, and individual packages can be installed using :code:`pip`. We recommend installing the package you need by running :code:`pip install testcontainers-`, e.g., :code:`pip install testcontainers-mysql`. +The suite of testcontainers packages is available on `PyPI `_, and individual packages can be installed using :code:`pip`. We recommend installing the package you need by running :code:`pip install testcontainers-`, e.g., :code:`pip install testcontainers-postgres`. -For backwards compatibility, packages can also be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[mysql]`. +.. note:: + For backwards compatibility, packages can also be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[postgres]`. -Usage within Docker (e.g., in a CI) ------------------------------------ -When trying to launch a testcontainer from within a Docker container two things have to be provided: +Docker in Docker (DinD) +----------------------- + +When trying to launch a testcontainer from within a Docker container, e.g., in continuous integration testing, two things have to be provided: 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. -Setting up a development environment ------------------------------------- +Development and Contributing +---------------------------- -We recommend you use a `virtual environment `_ for development. Note that a python version :code:`>=3.7` is required. After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. +We recommend you use a `virtual environment `_ for development (:code:`python>=3.7` is required). After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. .. code-block:: bash - pip install -r requirements/$(python -c 'import sys; print("%d.%d" % sys.version_info[:2])').txt + pip install -r requirements/[your python version].txt pytest -s -Adding requirements -^^^^^^^^^^^^^^^^^^^ - -We use :code:`pip-tools` to resolve and manage dependencies. If you need to add a dependency to testcontainers or one of the extras, modify the :code:`setup.py` as well as the :code:`requirements.in` accordingly and then run :code:`pip install pip-tools` followed by :code:`make requirements` to update the requirements files. +Package Structure +^^^^^^^^^^^^^^^^^ -Contributing a new container -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Testcontainers is a collection of `implicit namespace packages `__ to decouple the development of different extensions, e.g., :code:`testcontainers-mysql` and :code:`testcontainers-postgres` for MySQL and PostgreSQL database containers, respectively. The folder structure is as follows. -You can contribute a new container in three steps: +.. code-block:: bash -1. Create a new module at :code:`testcontainers/[my fancy container].py` that implements the new functionality. -2. Create a new test module at :code:`tests/test_[my fancy container].py` that tests the new functionality. -3. Add :code:`[my fancy container]` to the list of test components in the GitHub Action configuration at :code:`.github/workflows/main.yml`. + # One folder per feature. + [feature name] + # Folder without __init__.py for implicit namespace packages. + testcontainers + # Implementation as namespace package with __init__.py. + [feature name] + __init__.py + # Other files for this + ... + # Tests for the feature. + tests + test_[feature_name].py + ... + # README for this feature. + README.rst + # Setup script for this feature. + setup.py + +Contributing a New Feature +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You want to contribute a new feature or container? Great! You can do that in six steps. + +1. Create a new feature directory and populate it with the [package structure]_ as described above. Copying one of the existing features is likely the best way to get started. +2. Implement the new feature (typically in :code:`__init__.py`) and corresponding tests. +3. Add a line :code:`-e file:[feature name]` to :code:`requirements.in` and run :code:`make requirements`. This command will find any new requirements and generate lock files to ensure reproducible builds (see the `pip-tools `__ documentation for details). Then run :code:`pip install -r requirements/[your python version].txt` to install the new requirements. +4. Update the feature :code:`README.rst` and add it to the table of contents (:code:`toctree` directive) in the top-level :code:`README.rst`. +5. Add a line :code:`[feature name]` to the list of components in the GitHub Action workflow in :code:`.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the :code:`master` branch. +6. Rebase your development branch on :code:`master` (or merge :code:`master` into your development branch). From 9b865ac6b95edbced2447c83c11e12ef4ee24ab2 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 09:43:06 -0500 Subject: [PATCH 136/425] Add target to remove generated files. --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1551dedde..3e58fb4f4 100644 --- a/Makefile +++ b/Makefile @@ -66,5 +66,12 @@ ${REQUIREMENTS} : requirements/%.txt : requirements.in */setup.py ${RUN} -w /workspace -v `pwd`:/workspace --platform=linux/amd64 python:$* bash -c \ "pip install pip-tools && pip-compile --resolver=backtracking -v --upgrade -o $@ $<" +# Remove any generated files. +clean : + rm -rf docs/_build + rm -rf */build + rm -rf */dist + rm -rf */*.egg-info + # Targets that do not generate file-level artifacts. -.PHONY : dists ${DISTRIBUTIONS} docs doctests image tests ${TESTS} +.PHONY : clean dists ${DISTRIBUTIONS} docs doctests image tests ${TESTS} From 665f65ea119f5d378886ab0ce509216e7b3ef759 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 09:44:56 -0500 Subject: [PATCH 137/425] Drop self-reference in documentation. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 633b35130..081456879 100644 --- a/README.rst +++ b/README.rst @@ -50,7 +50,7 @@ Getting Started >>> version 'PostgreSQL 9.5...' -The snippet above will spin up a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. More extensive documentation can be found at `Read The Docs `_. +The snippet above will spin up a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. Installation ------------ From 87f7f5af49d9e58ef31be7a526336610bb9cc0eb Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 09:47:22 -0500 Subject: [PATCH 138/425] Drop old `index.rst` for sphinx docs. --- docs/index.rst-ish | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 docs/index.rst-ish diff --git a/docs/index.rst-ish b/docs/index.rst-ish deleted file mode 100644 index 47773a299..000000000 --- a/docs/index.rst-ish +++ /dev/null @@ -1,27 +0,0 @@ -.. python-testcontainers documentation master file, created by - sphinx-quickstart on Mon Aug 22 13:39:46 2016. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -.. include:: ../README.rst - -Usage modes ------------ - -.. toctree:: - :maxdepth: 2 - - Database containers - Selenium containers - Docker Compose - Google Cloud Emulators - Azure Emulators - AWS Emulators - Elastic Search - Kafka container - Keycloak container - RabbitMQ container - Redis container - Minio container - OpenSearch container - From 891f303f7380e10696cdd31d97080cd3a807ce35 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 10:12:27 -0500 Subject: [PATCH 139/425] Remove deprecated features. --- core/setup.py | 1 - core/testcontainers/core/container.py | 5 ----- core/testcontainers/core/generic.py | 8 +------- .../testcontainers/elasticsearch/__init__.py | 8 -------- mysql/testcontainers/mysql/__init__.py | 18 ------------------ nginx/testcontainers/nginx/__init__.py | 2 -- requirements/3.10.txt | 5 +---- requirements/3.7.txt | 5 +---- requirements/3.8.txt | 5 +---- requirements/3.9.txt | 5 +---- 10 files changed, 5 insertions(+), 57 deletions(-) diff --git a/core/setup.py b/core/setup.py index f857f7952..0270c6de8 100644 --- a/core/setup.py +++ b/core/setup.py @@ -13,7 +13,6 @@ install_requires=[ "docker>=4.0.0", "wrapt", - "deprecation", ], python_requires=">=3.7", ) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 695d05a68..d5c30948e 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,5 +1,4 @@ import os -from deprecation import deprecated from docker.models.containers import Container from testcontainers.core.waiting_utils import wait_container_is_ready @@ -36,10 +35,6 @@ def with_exposed_ports(self, *ports) -> 'DockerContainer': self.ports[port] = None return self - @deprecated(details='Use `with_kwargs`.') - def with_kargs(self, **kargs) -> 'DockerContainer': - return self.with_kwargs(**kargs) - def with_kwargs(self, **kwargs) -> 'DockerContainer': self._kwargs = kwargs return self diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 65c02f5fa..1b235dcf1 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -13,8 +13,8 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready -from deprecation import deprecated ADDITIONAL_TRANSIENT_ERRORS = [] + try: from sqlalchemy.exc import DBAPIError ADDITIONAL_TRANSIENT_ERRORS.append(DBAPIError) @@ -57,9 +57,3 @@ def start(self): def _configure(self): raise NotImplementedError - - -class GenericContainer(DockerContainer): - @deprecated(details="Use `DockerContainer`.") - def __init__(self, image): - super(GenericContainer, self).__init__(image) diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index 3a04bad70..aa21ffeda 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -15,8 +15,6 @@ import urllib from typing import Dict -from deprecation import deprecated - from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -100,9 +98,3 @@ def start(self): super().start() self._connect() return self - - -@deprecated(details='Use `ElasticSearchContainer` with a capital S instead ' - 'of `ElasticsearchContainer`.') -class ElasticsearchContainer(ElasticSearchContainer): - pass diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index c96814a79..0f5167072 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -10,7 +10,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from deprecation import deprecated from os import environ from testcontainers.core.generic import DbContainer @@ -69,20 +68,3 @@ def get_connection_url(self): password=self.MYSQL_PASSWORD, db_name=self.MYSQL_DATABASE, port=self.port_to_expose) - - -class MariaDbContainer(MySqlContainer): - """ - Maria database container, a commercially-supported fork of MySql. - - Example - ------- - .. doctest:: - - with MariaDbContainer("mariadb:latest") as mariadb: - e = sqlalchemy.create_engine(mariadb.get_connection_url()) - result = e.execute("select version()") - """ - @deprecated(details="Use `MySqlContainer` with 'mariadb:latest' image.") - def __init__(self, image="mariadb:latest", **kwargs): - super(MariaDbContainer, self).__init__(image, **kwargs) diff --git a/nginx/testcontainers/nginx/__init__.py b/nginx/testcontainers/nginx/__init__.py index 39cfe82cb..a9c336181 100644 --- a/nginx/testcontainers/nginx/__init__.py +++ b/nginx/testcontainers/nginx/__init__.py @@ -10,12 +10,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from deprecation import deprecated from testcontainers.core.container import DockerContainer class NginxContainer(DockerContainer): - @deprecated(details="Use `DockerContainer` with 'nginx:latest' image and expose port 80.") def __init__(self, image="nginx:latest", port_to_expose=80, **kwargs): super(NginxContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose diff --git a/requirements/3.10.txt b/requirements/3.10.txt index a3dfc3319..12d87ff45 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -134,8 +134,6 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle -deprecation==2.1.0 - # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -237,7 +235,6 @@ outcome==1.2.0 # via trio packaging==22.0 # via - # deprecation # docker # pytest # sphinx @@ -344,7 +341,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.0 +rich==13.0.1 # via twine rsa==4.9 # via diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 9e6f252c8..cb2fc471f 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -140,8 +140,6 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle -deprecation==2.1.0 - # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -253,7 +251,6 @@ outcome==1.2.0 # via trio packaging==22.0 # via - # deprecation # docker # pytest # sphinx @@ -360,7 +357,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.0 +rich==13.0.1 # via twine rsa==4.9 # via diff --git a/requirements/3.8.txt b/requirements/3.8.txt index e3ae06b65..bff674ccc 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -138,8 +138,6 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle -deprecation==2.1.0 - # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -244,7 +242,6 @@ outcome==1.2.0 # via trio packaging==22.0 # via - # deprecation # docker # pytest # sphinx @@ -351,7 +348,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.0 +rich==13.0.1 # via twine rsa==4.9 # via diff --git a/requirements/3.9.txt b/requirements/3.9.txt index b78eb42dd..cd8caea88 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -134,8 +134,6 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle -deprecation==2.1.0 - # via testcontainers-core distro==1.8.0 # via docker-compose dnspython==2.2.1 @@ -238,7 +236,6 @@ outcome==1.2.0 # via trio packaging==22.0 # via - # deprecation # docker # pytest # sphinx @@ -345,7 +342,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.0 +rich==13.0.1 # via twine rsa==4.9 # via From 01e96f3eb900f7cc69a059ba30a4d80e620e0bd3 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 10:41:37 -0500 Subject: [PATCH 140/425] Use integrated dependency caching. https://github.blog/changelog/2021-11-23-github-actions-setup-python-now-supports-dependency-caching/ --- .github/workflows/docs.yml | 10 ++-------- .github/workflows/main.yml | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2cbc4d732..5f2ddb1fa 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,14 +14,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: "3.10" - - name: Cache Python dependencies - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements/3.10.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- + cache: pip + cache-dependency-path: requirements/3.10.txt - name: Install Python dependencies run: | pip install --upgrade pip diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index da95aeb10..07002a7f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,14 +46,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Cache Python dependencies - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles(format('requirements/{0}.txt', matrix.python-version)) }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- + cache: pip + cache-dependency-path: ${{ format('requirements/{0}.txt', matrix.python-version) }} - name: Install Python dependencies run: | pip install --upgrade pip From 304c4c59766af8e407bf585dc6cd04dfcb9c79d3 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 11:01:37 -0500 Subject: [PATCH 141/425] Run doctests for each package. --- .github/workflows/docs.yml | 2 -- .github/workflows/main.yml | 3 +++ Makefile | 6 +++++- core/README.rst | 2 ++ core/testcontainers/core/container.py | 11 +++++++++++ doctests/conf.py | 5 +++++ 6 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 doctests/conf.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5f2ddb1fa..b74625373 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,5 +22,3 @@ jobs: pip install -r requirements/3.10.txt - name: Build documentation run: make docs - - name: Run doctests - run: make doctests diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07002a7f7..a90d61e60 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -68,6 +68,9 @@ jobs: - name: Run tests if: matrix.component != 'meta' run: make ${{ matrix.component }}/tests + - name: Run doctests + if: matrix.component != 'meta' + run: make ${{ matrix.component }}/doctest - name: Build the package run: make ${{ matrix.component }}/dist - name: Upload the package to pypi diff --git a/Makefile b/Makefile index 3e58fb4f4..bd49c0cea 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ UPLOAD = $(addsuffix /upload,${PACKAGES}) # All */tests folders for each of the test suites. TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES})) TESTS_DIND = $(addsuffix -dind,${TESTS}) +DOCTESTS = $(addsuffix /doctest,$(filter-out meta,${PACKAGES})) # All linting targets. LINT = $(addsuffix /lint,${PACKAGES}) @@ -56,9 +57,12 @@ ${TESTS_DIND} : %/tests-dind : image docs : sphinx-build -nW . docs/_build -doctests : +doctest : ${DOCTESTS} sphinx-build -b doctest . docs/_build +${DOCTESTS} : %/doctest : + sphinx-build -b doctest -c doctests $* docs/_build + # Targets to build requirement files requirements : ${REQUIREMENTS} ${REQUIREMENTS} : requirements/%.txt : requirements.in */setup.py diff --git a/core/README.rst b/core/README.rst index 37c7c4779..c5afdffc7 100644 --- a/core/README.rst +++ b/core/README.rst @@ -2,3 +2,5 @@ testcontainers-core =================== :code:`testcontainers-core` is a utility package for spinning up Docker containers in testing environments. + +.. autoclass:: testcontainers.core.container.DockerContainer diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index d5c30948e..78be81a72 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -10,6 +10,17 @@ class DockerContainer(object): + """ + Basic container object to spin up Docker instances. + + .. doctest:: + + >>> from testcontainers.core.container import DockerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + + >>> with DockerContainer("hello-world") as container: + ... delay = wait_for_logs(container, "Hello from Docker!") + """ def __init__(self, image, docker_client_kw: dict = None, **kwargs): self.env = {} self.ports = {} diff --git a/doctests/conf.py b/doctests/conf.py new file mode 100644 index 000000000..0822df226 --- /dev/null +++ b/doctests/conf.py @@ -0,0 +1,5 @@ +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", +] +master_doc = "README" From d23e1cf3d31ec6182afd57edff55c97129049bd1 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 11:15:21 -0500 Subject: [PATCH 142/425] Fix description in `setup.py`. --- azurite/setup.py | 2 +- clickhouse/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azurite/setup.py b/azurite/setup.py index b003a39d6..18b10858b 100644 --- a/azurite/setup.py +++ b/azurite/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_namespace_packages -description = "Core component of testcontainers-python." +description = "Azurite component of testcontainers-python." setup( name="testcontainers-azurite", diff --git a/clickhouse/setup.py b/clickhouse/setup.py index 2f7c9ebc3..004a151c7 100644 --- a/clickhouse/setup.py +++ b/clickhouse/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_namespace_packages -description = "Clickhouse component of testcontainers-python." +description = "ClickHouse component of testcontainers-python." setup( name="testcontainers-clickhouse", From aba500d2921c349f8c6a233a818aa3257f010e00 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 11:20:22 -0500 Subject: [PATCH 143/425] Add `sqlalchemy` requirement for `mssql` package. --- mssql/setup.py | 1 + requirements/3.10.txt | 1 + requirements/3.7.txt | 1 + requirements/3.8.txt | 1 + requirements/3.9.txt | 1 + 5 files changed, 5 insertions(+) diff --git a/mssql/setup.py b/mssql/setup.py index 082a3fc8d..c1fd74855 100644 --- a/mssql/setup.py +++ b/mssql/setup.py @@ -12,6 +12,7 @@ url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", + "sqlalchemy", "pymssql", ], python_requires=">=3.7", diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 12d87ff45..6ab7cfa76 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -387,6 +387,7 @@ sphinxcontrib-serializinghtml==1.1.5 # via sphinx sqlalchemy==1.4.46 # via + # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres diff --git a/requirements/3.7.txt b/requirements/3.7.txt index cb2fc471f..49bd54617 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -403,6 +403,7 @@ sphinxcontrib-serializinghtml==1.1.5 # via sphinx sqlalchemy==1.4.46 # via + # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres diff --git a/requirements/3.8.txt b/requirements/3.8.txt index bff674ccc..66b45f7a0 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -394,6 +394,7 @@ sphinxcontrib-serializinghtml==1.1.5 # via sphinx sqlalchemy==1.4.46 # via + # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres diff --git a/requirements/3.9.txt b/requirements/3.9.txt index cd8caea88..0388c44c9 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -388,6 +388,7 @@ sphinxcontrib-serializinghtml==1.1.5 # via sphinx sqlalchemy==1.4.46 # via + # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres From 617bb24e3a3e023ac5806286699a9a6834f032c1 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 11:21:07 -0500 Subject: [PATCH 144/425] Update `checkout` and `setup-python` actions. --- .github/workflows/docs.yml | 4 ++-- .github/workflows/main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b74625373..893fa5f9d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,9 +9,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.10" cache: pip diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a90d61e60..886962983 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,9 +41,9 @@ jobs: - selenium runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: pip From 79ac028d82e15adf76240e214935f4b5b0157cd1 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 14:31:25 -0500 Subject: [PATCH 145/425] Fix readthedocs build. --- .readthedocs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index cc40a2e5d..0b37a92d6 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,6 +3,9 @@ version: 2 +sphinx: + configuration: conf.py + build: os: ubuntu-22.04 tools: From 96cf752ca8e8cc1ab3f70cf7d1a47b7b933afa16 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 14:23:23 -0500 Subject: [PATCH 146/425] Move mysql test from core to mysql package. --- core/tests/test_new_docker_api.py | 11 ----------- mysql/tests/test_mysql.py | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/tests/test_new_docker_api.py b/core/tests/test_new_docker_api.py index 644840b0d..f1bd3ad6d 100644 --- a/core/tests/test_new_docker_api.py +++ b/core/tests/test_new_docker_api.py @@ -1,8 +1,6 @@ import os -import re from pathlib import Path -from testcontainers import mysql from testcontainers.core.container import DockerContainer @@ -21,15 +19,6 @@ def test_docker_custom_image(): assert int(port) > 0 -def test_docker_env_variables(): - container = mysql.MySqlContainer("mariadb:10.6.5")\ - .with_bind_ports(3306, 32785).maybe_emulate_amd64() - with container: - url = container.get_connection_url() - pattern = r'mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db' - assert re.match(pattern, url) - - def test_docker_kwargs(): code_dir = Path(__file__).parent container_first = DockerContainer("nginx:latest") diff --git a/mysql/tests/test_mysql.py b/mysql/tests/test_mysql.py index b3b8c24c6..348de9076 100644 --- a/mysql/tests/test_mysql.py +++ b/mysql/tests/test_mysql.py @@ -1,3 +1,4 @@ +import re import sqlalchemy import pytest from testcontainers.core.utils import is_arm @@ -20,3 +21,13 @@ def test_docker_run_mariadb(): result = e.execute("select version()") for row in result: assert row[0].startswith('10.6.5') + + +@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') +def test_docker_env_variables(): + container = MySqlContainer("mariadb:10.6.5")\ + .with_bind_ports(3306, 32785).maybe_emulate_amd64() + with container: + url = container.get_connection_url() + pattern = r'mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db' + assert re.match(pattern, url) From 5d933997df3045c0db5910a33ce0281c04b5cd43 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 15:18:04 -0500 Subject: [PATCH 147/425] Update issue templates --- .../bug-or-unexpected-behavior.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md diff --git a/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md b/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md new file mode 100644 index 000000000..56242051e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md @@ -0,0 +1,35 @@ +--- +name: Bug or unexpected behavior +about: Create a report to help us improve. +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** + +A clear and concise description of what the bug is. What did you expect to happen? What happened instead? + +**To Reproduce** + +Provide a self-contained code snippet that illustrates the bug or unexpected behavior. Ideally, send a Pull Request to illustrate with a test that illustrates the problem. + +```python +raise RuntimeError("something went wrong") +``` + +**Runtime environment** + +Provide a summary of your runtime environment. Which operating system, python version, and docker version are you using? What is the version of `testcontainers-python` you are using? You can run the following commands to get the relevant information. + +```bash +# Get the operating system information (on a unix os). +$ uname -a +# Get the python version. +$ python --version +# Get the docker version and other docker information. +$ docker info +# Get all python packages. +$ pip freeze +``` From ee3c3f8937a9e5badf5362e192d2c51a1fe0c746 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 14:50:59 -0500 Subject: [PATCH 148/425] Fix mysql environment variable test. --- mysql/tests/test_mysql.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mysql/tests/test_mysql.py b/mysql/tests/test_mysql.py index 348de9076..cf4032594 100644 --- a/mysql/tests/test_mysql.py +++ b/mysql/tests/test_mysql.py @@ -3,6 +3,7 @@ import pytest from testcontainers.core.utils import is_arm from testcontainers.mysql import MySqlContainer +from unittest import mock @pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') @@ -23,11 +24,10 @@ def test_docker_run_mariadb(): assert row[0].startswith('10.6.5') -@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') def test_docker_env_variables(): - container = MySqlContainer("mariadb:10.6.5")\ - .with_bind_ports(3306, 32785).maybe_emulate_amd64() - with container: + with mock.patch.dict("os.environ", MYSQL_USER="demo", MYSQL_DATABASE="custom_db"), \ + MySqlContainer("mariadb:10.6.5").with_bind_ports(3306, 32785).maybe_emulate_amd64() \ + as container: url = container.get_connection_url() pattern = r'mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db' assert re.match(pattern, url) From d1abc1a18a50abd4a96ea749f225edab232404df Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 15:43:20 -0500 Subject: [PATCH 149/425] More informative logs in `waiting_utils`. --- core/testcontainers/core/waiting_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index c147ed959..c2a0d6ba2 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -18,6 +18,7 @@ import wrapt +from testcontainers.core.container import DockerContainer from testcontainers.core import config from testcontainers.core.exceptions import TimeoutException from testcontainers.core.utils import setup_logger @@ -41,9 +42,10 @@ def wait_container_is_ready(*transient_exceptions): transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) @wrapt.decorator - def wrapper(wrapped, instance, args, kwargs): + def wrapper(wrapped, instance: DockerContainer, args, kwargs): exception = None - logger.info("Waiting to be ready...") + logger.info("Waiting for container %s with image %s to be ready...", instance._container, + instance.image) for attempt_no in range(config.MAX_TRIES): try: return wrapped(*args, **kwargs) From 1a6afc634e0c39faf0d503b61f33335e5941bdcd Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 15:56:07 -0500 Subject: [PATCH 150/425] Fix circular import. --- core/testcontainers/core/waiting_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index c2a0d6ba2..94c92afbf 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -18,7 +18,7 @@ import wrapt -from testcontainers.core.container import DockerContainer +from .import container from testcontainers.core import config from testcontainers.core.exceptions import TimeoutException from testcontainers.core.utils import setup_logger @@ -42,7 +42,7 @@ def wait_container_is_ready(*transient_exceptions): transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) @wrapt.decorator - def wrapper(wrapped, instance: DockerContainer, args, kwargs): + def wrapper(wrapped, instance: "container.DockerContainer", args, kwargs): exception = None logger.info("Waiting for container %s with image %s to be ready...", instance._container, instance.image) From fcdd3675af70c1ef825669d518b44ddad684096e Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 16:02:42 -0500 Subject: [PATCH 151/425] Remove type annotation. --- core/testcontainers/core/waiting_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 94c92afbf..39d83b604 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -18,7 +18,6 @@ import wrapt -from .import container from testcontainers.core import config from testcontainers.core.exceptions import TimeoutException from testcontainers.core.utils import setup_logger @@ -42,7 +41,7 @@ def wait_container_is_ready(*transient_exceptions): transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) @wrapt.decorator - def wrapper(wrapped, instance: "container.DockerContainer", args, kwargs): + def wrapper(wrapped, instance, args, kwargs): exception = None logger.info("Waiting for container %s with image %s to be ready...", instance._container, instance.image) From 256c7a76fe839483a40fec7321beb22c8a92a5db Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 16:12:35 -0500 Subject: [PATCH 152/425] Fix logging if `instance` is not a `DockerContainer`. --- core/testcontainers/core/waiting_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 39d83b604..2d1b89df3 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -42,9 +42,15 @@ def wait_container_is_ready(*transient_exceptions): @wrapt.decorator def wrapper(wrapped, instance, args, kwargs): + from .container import DockerContainer + + if isinstance(instance, DockerContainer): + logger.info("Waiting for container %s with image %s to be ready ...", + instance._container, instance.image) + else: + logger.info("Waiting for %s to be ready ...", instance) + exception = None - logger.info("Waiting for container %s with image %s to be ready...", instance._container, - instance.image) for attempt_no in range(config.MAX_TRIES): try: return wrapped(*args, **kwargs) From f7f1805fb1f681168abc0ec8b5049b47b3040f19 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 16:32:58 -0500 Subject: [PATCH 153/425] Add CODEOWNERS. --- CODEOWNERS | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..aa08e1052 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,23 @@ +/arangodb @nshine +/azurite @pffijt +/clickhouse @yakimka +# /compose +# /core +/elasticsearch @nivm @daTokenizer +/google @tillahoffmann +/kafka @ash1425 +/keycloak @timbmg +/localstack @ImFlog +# /meta +/minio @maltehedderich +/mongodb @dabrign +# /mssql +# /mysql +/neo4j @eastlondoner +# /nginx +/opensearch @maltehedderich +# /oracle +# /postgres +/rabbitmq @KerstenBreuer +/redis @daTokenizer +# /selenium From 383b12e9d63b4b105c65e587e87c380355871a36 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 17:03:18 -0500 Subject: [PATCH 154/425] Use relative imports in core package. --- core/testcontainers/core/container.py | 8 ++++---- core/testcontainers/core/docker_client.py | 4 +--- core/testcontainers/core/exceptions.py | 10 +--------- core/testcontainers/core/generic.py | 6 +++--- core/testcontainers/core/waiting_utils.py | 7 +++---- 5 files changed, 12 insertions(+), 23 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 78be81a72..21ec0fab7 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,10 +1,10 @@ import os from docker.models.containers import Container -from testcontainers.core.waiting_utils import wait_container_is_ready -from testcontainers.core.docker_client import DockerClient -from testcontainers.core.exceptions import ContainerStartException -from testcontainers.core.utils import setup_logger, inside_container, is_arm +from .waiting_utils import wait_container_is_ready +from .docker_client import DockerClient +from .exceptions import ContainerStartException +from .utils import setup_logger, inside_container, is_arm logger = setup_logger(__name__) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 2af50f716..3fd075e76 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -17,9 +17,7 @@ from docker.errors import NotFound from docker.models.containers import Container -from testcontainers.core.utils import inside_container -from testcontainers.core.utils import default_gateway_ip -from testcontainers.core.utils import setup_logger +from .utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) diff --git a/core/testcontainers/core/exceptions.py b/core/testcontainers/core/exceptions.py index b7e8447cd..ff19acf0a 100644 --- a/core/testcontainers/core/exceptions.py +++ b/core/testcontainers/core/exceptions.py @@ -12,15 +12,7 @@ # under the License. -class ContainerStartException(Exception): - pass - - -class TimeoutException(Exception): - pass - - -class NoSuchBrowserException(Exception): +class ContainerStartException(RuntimeError): pass diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 1b235dcf1..62c590fb6 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -11,10 +11,10 @@ # License for the specific language governing permissions and limitations # under the License. -from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_container_is_ready -ADDITIONAL_TRANSIENT_ERRORS = [] +from .container import DockerContainer +from .waiting_utils import wait_container_is_ready +ADDITIONAL_TRANSIENT_ERRORS = [] try: from sqlalchemy.exc import DBAPIError ADDITIONAL_TRANSIENT_ERRORS.append(DBAPIError) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 2d1b89df3..fa7b68804 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -18,9 +18,8 @@ import wrapt -from testcontainers.core import config -from testcontainers.core.exceptions import TimeoutException -from testcontainers.core.utils import setup_logger +from . import config +from .utils import setup_logger logger = setup_logger(__name__) @@ -59,7 +58,7 @@ def wrapper(wrapped, instance, args, kwargs): f"failed: {traceback.format_exc()}") time.sleep(config.SLEEP_TIME) exception = e - raise TimeoutException( + raise TimeoutError( f'Wait time ({config.MAX_TRIES * config.SLEEP_TIME}s) exceeded for {wrapped.__name__}' f'(args: {args}, kwargs {kwargs}). Exception: {exception}' ) From fce23a4bbc3dbee82983168bbcb04fe3ac2006fd Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 17:37:36 -0500 Subject: [PATCH 155/425] Add type annotations for core package. --- compose/testcontainers/compose/__init__.py | 2 +- core/testcontainers/core/container.py | 47 ++++++-------- core/testcontainers/core/docker_client.py | 72 +++++++++++++--------- core/testcontainers/core/exceptions.py | 2 +- core/testcontainers/core/generic.py | 26 ++++---- core/testcontainers/core/utils.py | 16 ++--- core/testcontainers/core/waiting_utils.py | 22 ++++--- 7 files changed, 99 insertions(+), 88 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index cefd0cc3f..3d6d9d5c9 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -12,7 +12,7 @@ from testcontainers.core.exceptions import NoSuchPortExposed -class DockerCompose(object): +class DockerCompose: """ Manage docker compose environments. diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 21ec0fab7..ba7865286 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,5 +1,6 @@ -import os from docker.models.containers import Container +import os +from typing import Iterable, Optional, Tuple from .waiting_utils import wait_container_is_ready from .docker_client import DockerClient @@ -9,7 +10,7 @@ logger = setup_logger(__name__) -class DockerContainer(object): +class DockerContainer: """ Basic container object to spin up Docker instances. @@ -21,7 +22,7 @@ class DockerContainer(object): >>> with DockerContainer("hello-world") as container: ... delay = wait_for_logs(container, "Hello from Docker!") """ - def __init__(self, image, docker_client_kw: dict = None, **kwargs): + def __init__(self, image: str, docker_client_kw: Optional[dict] = None, **kwargs) -> None: self.env = {} self.ports = {} self.volumes = {} @@ -36,13 +37,12 @@ def with_env(self, key: str, value: str) -> 'DockerContainer': self.env[key] = value return self - def with_bind_ports(self, container: int, - host: int = None) -> 'DockerContainer': + def with_bind_ports(self, container: int, host: int = None) -> 'DockerContainer': self.ports[container] = host return self - def with_exposed_ports(self, *ports) -> 'DockerContainer': - for port in list(ports): + def with_exposed_ports(self, *ports: Iterable[int]) -> 'DockerContainer': + for port in ports: self.ports[port] = None return self @@ -55,25 +55,20 @@ def maybe_emulate_amd64(self) -> 'DockerContainer': return self.with_kwargs(platform='linux/amd64') return self - def start(self): + def start(self) -> 'DockerContainer': logger.info("Pulling image %s", self.image) docker_client = self.get_docker_client() - self._container = docker_client.run(self.image, - command=self._command, - detach=True, - environment=self.env, - ports=self.ports, - name=self._name, - volumes=self.volumes, - **self._kwargs - ) + self._container = docker_client.run( + self.image, command=self._command, detach=True, environment=self.env, ports=self.ports, + name=self._name, volumes=self.volumes, **self._kwargs + ) logger.info("Container started: %s", self._container.short_id) return self - def stop(self, force=True, delete_volume=True): + def stop(self, force=True, delete_volume=True) -> None: self.get_wrapped_container().remove(force=force, v=delete_volume) - def __enter__(self): + def __enter__(self) -> 'DockerContainer': return self.start() def __exit__(self, exc_type, exc_val, exc_tb): @@ -110,7 +105,7 @@ def get_container_host_ip(self) -> str: return host @wait_container_is_ready() - def get_exposed_port(self, port) -> str: + def get_exposed_port(self, port: int) -> str: mapped_port = self.get_docker_client().port(self._container.id, port) if inside_container(): gateway_ip = self.get_docker_client().gateway_ip(self._container.id) @@ -128,9 +123,7 @@ def with_name(self, name: str) -> 'DockerContainer': self._name = name return self - def with_volume_mapping(self, host: str, container: str, - mode: str = 'ro') -> 'DockerContainer': - # '/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'} + def with_volume_mapping(self, host: str, container: str, mode: str = 'ro') -> 'DockerContainer': mapping = {'bind': container, 'mode': mode} self.volumes[host] = mapping return self @@ -141,12 +134,12 @@ def get_wrapped_container(self) -> Container: def get_docker_client(self) -> DockerClient: return self._docker - def get_logs(self): + def get_logs(self) -> Tuple[str, str]: if not self._container: - raise ContainerStartException("Container should be started before") + raise ContainerStartException("Container should be started before getting logs") return self._container.logs(stderr=False), self._container.logs(stdout=False) - def exec(self, command): + def exec(self, command) -> Tuple[int, str]: if not self._container: - raise ContainerStartException("Container should be started before") + raise ContainerStartException("Container should be started before executing a command") return self.get_wrapped_container().exec_run(command) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 3fd075e76..e2e4b8b2c 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -11,11 +11,13 @@ # License for the specific language governing permissions and limitations # under the License. import atexit -import os -import urllib import docker from docker.errors import NotFound -from docker.models.containers import Container +from docker.models.containers import Container, ContainerCollection +import functools as ft +import os +from typing import List, Optional, Union +import urllib from .utils import default_gateway_ip, inside_container, setup_logger @@ -23,7 +25,7 @@ LOGGER = setup_logger(__name__) -def _stop_container(container): +def _stop_container(container: Container) -> None: try: container.stop() except NotFound: @@ -33,53 +35,63 @@ def _stop_container(container): container.image, ex) -class DockerClient(object): - def __init__(self, **kwargs): +class DockerClient: + """ + Thin wrapper around :class:`docker.DockerClient` for a more functional interface. + """ + def __init__(self, **kwargs) -> None: self.client = docker.from_env(**kwargs) - def run(self, image: str, - command: str = None, - environment: dict = None, - ports: dict = None, - detach: bool = False, - stdout: bool = True, - stderr: bool = False, - remove: bool = False, **kwargs) -> Container: - container = self.client.containers.run(image, - command=command, - stdout=stdout, - stderr=stderr, - remove=remove, - detach=detach, - environment=environment, - ports=ports, - **kwargs) - atexit.register(_stop_container, container) - + @ft.wraps(ContainerCollection.run) + def run(self, image: str, command: Union[str, List[str]] = None, + environment: Optional[dict] = None, ports: Optional[dict] = None, + detach: bool = False, stdout: bool = True, stderr: bool = False, remove: bool = False, + **kwargs) -> Container: + container = self.client.containers.run( + image, command=command, stdout=stdout, stderr=stderr, remove=remove, detach=detach, + environment=environment, ports=ports, **kwargs + ) + if detach: + atexit.register(_stop_container, container) return container - def port(self, container_id, port): + def port(self, container_id: str, port: int) -> int: + """ + Lookup the public-facing port that is NAT-ed to :code:`port`. + """ port_mappings = self.client.api.port(container_id, port) if not port_mappings: raise ConnectionError(f'port mapping for container {container_id} and port {port} is ' 'not available') return port_mappings[0]["HostPort"] - def get_container(self, container_id): + def get_container(self, container_id: str) -> Container: + """ + Get the container with a given identifier. + """ containers = self.client.api.containers(filters={'id': container_id}) if not containers: raise RuntimeError(f'could not get container with id {container_id}') return containers[0] - def bridge_ip(self, container_id): + def bridge_ip(self, container_id: str) -> str: + """ + Get the bridge ip address for a container. + """ container = self.get_container(container_id) return container['NetworkSettings']['Networks']['bridge']['IPAddress'] - def gateway_ip(self, container_id): + def gateway_ip(self, container_id: str) -> str: + """ + Get the gateway ip address for a container. + """ container = self.get_container(container_id) return container['NetworkSettings']['Networks']['bridge']['Gateway'] - def host(self): + def host(self) -> str: + """ + Get the hostname or ip address of the docker host. + """ # https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 # if os env TC_HOST is set, use it host = os.environ.get('TC_HOST') diff --git a/core/testcontainers/core/exceptions.py b/core/testcontainers/core/exceptions.py index ff19acf0a..8bf027630 100644 --- a/core/testcontainers/core/exceptions.py +++ b/core/testcontainers/core/exceptions.py @@ -16,5 +16,5 @@ class ContainerStartException(RuntimeError): pass -class NoSuchPortExposed(Exception): +class NoSuchPortExposed(RuntimeError): pass diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 62c590fb6..f295bc01a 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -10,8 +10,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from typing import Optional from .container import DockerContainer +from .exceptions import ContainerStartException from .waiting_utils import wait_container_is_ready ADDITIONAL_TRANSIENT_ERRORS = [] @@ -23,24 +25,24 @@ class DbContainer(DockerContainer): - def __init__(self, image, **kwargs): - super(DbContainer, self).__init__(image, **kwargs) - + """ + Generic database container. + """ @wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS) - def _connect(self): + def _connect(self) -> None: import sqlalchemy engine = sqlalchemy.create_engine(self.get_connection_url()) engine.connect() - def get_connection_url(self): + def get_connection_url(self) -> str: raise NotImplementedError - def _create_connection_url(self, dialect, username, password, - host=None, port=None, db_name=None): + def _create_connection_url(self, dialect: str, username: str, password: str, + host: Optional[str] = None, port: Optional[int] = None, + db_name: Optional[str] = None) -> str: if self._container is None: - raise RuntimeError("container has not been started") - if not host: - host = self.get_container_host_ip() + raise ContainerStartException("container has not been started") + host = host or self.get_container_host_ip() port = self.get_exposed_port(port) url = "{dialect}://{username}:{password}@{host}:{port}".format( dialect=dialect, username=username, password=password, host=host, port=port @@ -49,11 +51,11 @@ def _create_connection_url(self, dialect, username, password, url += '/' + db_name return url - def start(self): + def start(self) -> 'DbContainer': self._configure() super().start() self._connect() return self - def _configure(self): + def _configure(self) -> None: raise NotImplementedError diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index 47fd1de27..c1090b332 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -9,7 +9,7 @@ WIN = "win" -def setup_logger(name): +def setup_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) logger.setLevel(logging.INFO) handler = logging.StreamHandler() @@ -18,7 +18,7 @@ def setup_logger(name): return logger -def os_name(): +def os_name() -> str: pl = sys.platform if pl == "linux" or pl == "linux2": return LINUX @@ -28,23 +28,23 @@ def os_name(): return WIN -def is_mac(): +def is_mac() -> bool: return MAC == os_name() -def is_linux(): +def is_linux() -> bool: return LINUX == os_name() -def is_windows(): +def is_windows() -> bool: return WIN == os_name() -def is_arm(): +def is_arm() -> bool: return platform.machine() in ('arm64', 'aarch64') -def inside_container(): +def inside_container() -> bool: """ Returns true if we are running inside a container. @@ -53,7 +53,7 @@ def inside_container(): return os.path.exists('/.dockerenv') -def default_gateway_ip(): +def default_gateway_ip() -> str: """ Returns gateway IP address of the host that testcontainer process is running on diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index fa7b68804..a6650fb76 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -15,6 +15,7 @@ import re import time import traceback +from typing import Any, Callable, Iterable, Mapping, Optional import wrapt @@ -28,19 +29,21 @@ TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionError) -def wait_container_is_ready(*transient_exceptions): +def wait_container_is_ready(*transient_exceptions) -> Callable: """ Wait until container is ready. - Function that spawn container should be decorated by this method - Max wait is configured by config. Default is 120 sec. - Polling interval is 1 sec. - :return: - """ + Function that spawn container should be decorated by this method Max wait is configured by + config. Default is 120 sec. Polling interval is 1 sec. + + Args: + *transient_exceptions: Additional transient exceptions that should be retried if raised. Any + non-transient exceptions are fatal, and the exception is re-raised immediately. + """ transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) @wrapt.decorator - def wrapper(wrapped, instance, args, kwargs): + def wrapper(wrapped: Callable, instance: Any, args: Iterable, kwargs: Mapping) -> Any: from .container import DockerContainer if isinstance(instance, DockerContainer): @@ -67,11 +70,12 @@ def wrapper(wrapped, instance, args, kwargs): @wait_container_is_ready() -def wait_for(condition): +def wait_for(condition: Callable[..., bool]) -> bool: return condition() -def wait_for_logs(container, predicate, timeout=None, interval=1): +def wait_for_logs(container, predicate: Callable, timeout: Optional[float] = None, + interval: float = 1) -> float: """ Wait for the container to emit logs satisfying the predicate. From a98c9e970884d74a30640e1a3b47965e9d7466ff Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 17:44:53 -0500 Subject: [PATCH 156/425] Use `TYPE_CHECKING` guard to avoid circular imports. https://adamj.eu/tech/2021/05/13/python-type-hints-how-to-fix-circular-imports/ --- core/testcontainers/core/waiting_utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index a6650fb76..9e87805a5 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -15,13 +15,15 @@ import re import time import traceback -from typing import Any, Callable, Iterable, Mapping, Optional - +from typing import Any, Callable, Iterable, Mapping, Optional, TYPE_CHECKING import wrapt from . import config from .utils import setup_logger +if TYPE_CHECKING: + from .container import DockerContainer + logger = setup_logger(__name__) @@ -74,8 +76,8 @@ def wait_for(condition: Callable[..., bool]) -> bool: return condition() -def wait_for_logs(container, predicate: Callable, timeout: Optional[float] = None, - interval: float = 1) -> float: +def wait_for_logs(container: "DockerContainer", predicate: Callable, + timeout: Optional[float] = None, interval: float = 1) -> float: """ Wait for the container to emit logs satisfying the predicate. From b4c36a12f2e264b1ed1a00060467950b733bbb13 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:32:26 -0500 Subject: [PATCH 157/425] Add type annotations for arangodb. --- arangodb/testcontainers/arangodb/__init__.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/arangodb/testcontainers/arangodb/__init__.py b/arangodb/testcontainers/arangodb/__init__.py index 597a1f2b0..463037535 100644 --- a/arangodb/testcontainers/arangodb/__init__.py +++ b/arangodb/testcontainers/arangodb/__init__.py @@ -40,7 +40,7 @@ def __init__(self, arango_root_password: str = "passwd", arango_no_auth: typing.Optional[bool] = None, arango_random_root_password: typing.Optional[bool] = None, - **kwargs): + **kwargs) -> None: """ Args: image: Actual docker image/tag to pull. @@ -69,23 +69,16 @@ def __init__(self, else arango_random_root_password )) - def _configure(self): + def _configure(self) -> None: self.with_env("ARANGO_ROOT_PASSWORD", self.arango_root_password) if self.arango_no_auth: self.with_env("ARANGO_NO_AUTH", "1") if self.arango_random_root_password: self.with_env("ARANGO_RANDOM_ROOT_PASSWORD", "1") - def get_connection_url(self): - # for now, single host over HTTP - scheme = "http" + def get_connection_url(self) -> str: port = self.get_exposed_port(self.port_to_expose) - url = f"{scheme}://{self.get_container_host_ip()}:{port}" + return f"http://{self.get_container_host_ip()}:{port}" - return url - - def _connect(self): - wait_for_logs( - self, - predicate="is ready for business", - timeout=MAX_TRIES) + def _connect(self) -> None: + wait_for_logs(self, predicate="is ready for business", timeout=MAX_TRIES) From 7e47c78d0c71e91c6b661df6c43d4993ac12e31e Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:32:37 -0500 Subject: [PATCH 158/425] Add type annotations for azurite. --- azurite/testcontainers/azurite/__init__.py | 26 +++++++++------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/azurite/testcontainers/azurite/__init__.py b/azurite/testcontainers/azurite/__init__.py index 7020fdc09..9859e003b 100644 --- a/azurite/testcontainers/azurite/__init__.py +++ b/azurite/testcontainers/azurite/__init__.py @@ -12,6 +12,7 @@ # under the License. import os import socket +from typing import Iterable, Optional from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -48,21 +49,14 @@ class AzuriteContainer(DockerContainer): _QUEUE_SERVICE_PORT = 10_001 _TABLE_SERVICE_PORT = 10_002 - def __init__( - self, - image="mcr.microsoft.com/azure-storage/azurite:latest", - ports_to_expose=None, - **kwargs - ): + def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest", + ports_to_expose: Optional[Iterable[int]] = None, **kwargs) -> None: """ Constructs an AzuriteContainer. - Parameters - ---------- - image: str - Expects an image with tag. - ports_to_expose: List[int] - Expects a list with port numbers to expose. - kwargs + Args: + image: Expects an image with tag. + ports_to_expose: List with port numbers to expose. + **kwargs: Keyword arguments passed to super class. """ super().__init__(image=image, **kwargs) @@ -80,7 +74,7 @@ def __init__( self.with_env("AZURITE_ACCOUNTS", f"{self._AZURITE_ACCOUNT_NAME}:{self._AZURITE_ACCOUNT_KEY}") - def get_connection_string(self): + def get_connection_string(self) -> str: host_ip = self.get_container_host_ip() connection_string = f"DefaultEndpointsProtocol=http;" \ f"AccountName={self._AZURITE_ACCOUNT_NAME};" \ @@ -103,13 +97,13 @@ def get_connection_string(self): return connection_string - def start(self): + def start(self) -> 'AzuriteContainer': super().start() self._connect() return self @wait_container_is_ready(OSError) - def _connect(self): + def _connect(self) -> None: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((self.get_container_host_ip(), int(self.get_exposed_port(next(iter(self.ports)))))) From 053d07f3a0fb36237d4842fc2a987f25db6444ce Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:32:46 -0500 Subject: [PATCH 159/425] Add type annotations for clickhouse. --- .../testcontainers/clickhouse/__init__.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index 4362a080f..da550e141 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. import os +from typing import Optional import clickhouse_driver from clickhouse_driver.errors import Error @@ -45,12 +46,12 @@ class ClickHouseContainer(DbContainer): def __init__( self, - image="clickhouse/clickhouse-server:latest", - port=9000, - user=None, - password=None, - dbname=None - ): + image: str = "clickhouse/clickhouse-server:latest", + port: int = 9000, + user: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None + ) -> None: super().__init__(image=image) self.CLICKHOUSE_USER = user or self.CLICKHOUSE_USER @@ -60,16 +61,16 @@ def __init__( self.with_exposed_ports(self.port_to_expose) @wait_container_is_ready(Error, EOFError) - def _connect(self): + def _connect(self) -> None: with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: client.execute("SELECT version()") - def _configure(self): + def _configure(self) -> None: self.with_env("CLICKHOUSE_USER", self.CLICKHOUSE_USER) self.with_env("CLICKHOUSE_PASSWORD", self.CLICKHOUSE_PASSWORD) self.with_env("CLICKHOUSE_DB", self.CLICKHOUSE_DB) - def get_connection_url(self, host=None): + def get_connection_url(self, host: Optional[str] = None) -> str: return self._create_connection_url( dialect="clickhouse", username=self.CLICKHOUSE_USER, From 20ac61c1d5ec708159de33475408eb2a602d1ddf Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:32:55 -0500 Subject: [PATCH 160/425] Add type annotations for compose. --- compose/testcontainers/compose/__init__.py | 131 ++++++++------------- 1 file changed, 52 insertions(+), 79 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 3d6d9d5c9..d2e3e169a 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -7,6 +7,7 @@ import requests import subprocess +from typing import Iterable, List, Optional, Tuple, Union from testcontainers.core.waiting_utils import wait_container_is_ready from testcontainers.core.exceptions import NoSuchPortExposed @@ -16,18 +17,12 @@ class DockerCompose: """ Manage docker compose environments. - Parameters - ---------- - filepath: str - The relative directory containing the docker compose configuration file - compose_file_name: str - The file name of the docker compose configuration file - pull: bool - Attempts to pull images before launching environment - build: bool - Whether to build images referenced in the configuration file - env_file: str - Path to an env file containing environment variables to pass to docker compose + Args: + filepath: Relative directory containing the docker compose configuration file. + compose_file_name: File name of the docker compose configuration file. + pull: Pull images before launching environment. + build: Build images referenced in the configuration file. + env_file: Path to an env file containing environment variables to pass to docker compose. Example ------- @@ -67,37 +62,33 @@ class DockerCompose: expose: - "5555" """ - def __init__( self, - filepath, - compose_file_name="docker-compose.yml", - pull=False, - build=False, - env_file=None): + filepath: str, + compose_file_name: Union[str, Iterable] = "docker-compose.yml", + pull: bool = False, + build: bool = False, + env_file: Optional[str] = None) -> None: self.filepath = filepath - self.compose_file_names = compose_file_name if isinstance( - compose_file_name, (list, tuple) - ) else [compose_file_name] + self.compose_file_names = [compose_file_name] if isinstance(compose_file_name, str) else \ + list(compose_file_name) self.pull = pull self.build = build self.env_file = env_file - def __enter__(self): + def __enter__(self) -> "DockerCompose": self.start() return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.stop() - def docker_compose_command(self): + def docker_compose_command(self) -> List[str]: """ Returns command parts used for the docker compose commands - Returns - ------- - list[str] - The docker compose command parts + Returns: + cmd: Docker compose command parts. """ docker_compose_cmd = ['docker-compose'] for file in self.compose_file_names: @@ -106,7 +97,7 @@ def docker_compose_command(self): docker_compose_cmd += ['--env-file', self.env_file] return docker_compose_cmd - def start(self): + def start(self) -> None: """ Starts the docker compose environment. """ @@ -120,21 +111,20 @@ def start(self): self._call_command(cmd=up_cmd) - def stop(self): + def stop(self) -> None: """ Stops the docker compose environment. """ down_cmd = self.docker_compose_command() + ['down', '-v'] self._call_command(cmd=down_cmd) - def get_logs(self): + def get_logs(self) -> Tuple[str, str]: """ Returns all log output from stdout and stderr - Returns - ------- - tuple[bytes, bytes] - stdout, stderr + Returns: + stdout: Standard output stream. + stderr: Standard error stream. """ logs_cmd = self.docker_compose_command() + ["logs"] result = subprocess.run( @@ -145,21 +135,17 @@ def get_logs(self): ) return result.stdout, result.stderr - def exec_in_container(self, service_name, command): + def exec_in_container(self, service_name: str, command: List[str]) -> Tuple[str, str]: """ Executes a command in the container of one of the services. - Parameters - ---------- - service_name: str - Name of the docker compose service to run the command in - command: list[str] - The command to execute + Args: + service_name: Name of the docker compose service to run the command in. + command: Command to execute. - Returns - ------- - tuple[str, str, int] - stdout, stderr, return code + Returns: + stdout: Standard output stream. + stderr: Standard error stream. """ exec_cmd = self.docker_compose_command() + ['exec', '-T', service_name] + command result = subprocess.run( @@ -170,43 +156,33 @@ def exec_in_container(self, service_name, command): ) return result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode - def get_service_port(self, service_name, port): + def get_service_port(self, service_name: str, port: int) -> int: """ Returns the mapped port for one of the services. - Parameters - ---------- - service_name: str - Name of the docker compose service - port: int - The internal port to get the mapping for + Args: + service_name: Name of the docker compose service. + port: Internal port to get the mapping for. - Returns - ------- - str: - The mapped port on the host + Returns: + mapped_port: Mapped port on the host. """ return self._get_service_info(service_name, port)[1] - def get_service_host(self, service_name, port): + def get_service_host(self, service_name: str, port: int) -> str: """ Returns the host for one of the services. - Parameters - ---------- - service_name: str - Name of the docker compose service - port: int - The internal port to get the host for + Args: + service_name: Name of the docker compose service. + port: Internal port to get the mapping for. - Returns - ------- - str: - The hostname for the service + Returns: + host: Hostname for the service. """ return self._get_service_info(service_name, port)[0] - def _get_service_info(self, service, port): + def _get_service_info(self, service: str, port: int) -> List[str]: port_cmd = self.docker_compose_command() + ["port", service, str(port)] output = subprocess.check_output(port_cmd, cwd=self.filepath).decode("utf-8") result = str(output).rstrip().split(":") @@ -214,23 +190,20 @@ def _get_service_info(self, service, port): raise NoSuchPortExposed(f"port {port} is not exposed for service {service}") return result - def _call_command(self, cmd, filepath=None): + def _call_command(self, cmd: Union[str, List[str]], filepath: Optional[str] = None) -> None: if filepath is None: filepath = self.filepath subprocess.call(cmd, cwd=filepath) @wait_container_is_ready(requests.exceptions.ConnectionError) - def wait_for(self, url): + def wait_for(self, url: str) -> 'DockerCompose': """ - Waits for a response from a given URL. This is typically used to - block until a service in the environment has started and is responding. - Note that it does not assert any sort of return code, only check that - the connection was successful. + Waits for a response from a given URL. This is typically used to block until a service in + the environment has started and is responding. Note that it does not assert any sort of + return code, only check that the connection was successful. - Parameters - ---------- - url: str - URL from one of the services in the environment to use to wait on + Args: + url: URL from one of the services in the environment to use to wait on. """ requests.get(url) return self From 54e0d8e7ef8f8621c8001d923c26043ec7184cc6 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:33:08 -0500 Subject: [PATCH 161/425] Update type annotations for core. --- core/testcontainers/core/container.py | 4 +-- core/testcontainers/core/waiting_utils.py | 30 +++++++++-------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index ba7865286..65acf9bed 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -71,10 +71,10 @@ def stop(self, force=True, delete_volume=True) -> None: def __enter__(self) -> 'DockerContainer': return self.start() - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.stop() - def __del__(self): + def __del__(self) -> None: """ Try to remove the container in all circumstances """ diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 9e87805a5..8daaac879 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -15,7 +15,7 @@ import re import time import traceback -from typing import Any, Callable, Iterable, Mapping, Optional, TYPE_CHECKING +from typing import Any, Callable, Iterable, Mapping, Optional, TYPE_CHECKING, Union import wrapt from . import config @@ -76,27 +76,21 @@ def wait_for(condition: Callable[..., bool]) -> bool: return condition() -def wait_for_logs(container: "DockerContainer", predicate: Callable, +def wait_for_logs(container: "DockerContainer", predicate: Union[Callable, str], timeout: Optional[float] = None, interval: float = 1) -> float: """ Wait for the container to emit logs satisfying the predicate. - Parameters - ---------- - container : DockerContainer - Container whose logs to wait for. - predicate : callable or str - Predicate that should be satisfied by the logs. If a string, the it is used as the pattern - for a multiline regular expression search. - timeout : float or None - Number of seconds to wait for the predicate to be satisfied. Defaults to wait indefinitely. - interval : float - Interval at which to poll the logs. - - Returns - ------- - duration : float - Number of seconds until the predicate was satisfied. + Args: + container: Container whose logs to wait for. + predicate: Predicate that should be satisfied by the logs. If a string, the it is used as + the pattern for a multiline regular expression search. + timeout: Number of seconds to wait for the predicate to be satisfied. Defaults to wait + indefinitely. + interval: Interval at which to poll the logs. + + Returns: + duration: Number of seconds until the predicate was satisfied. """ if isinstance(predicate, str): predicate = re.compile(predicate, re.MULTILINE).search From a0417a96198738360b98c648186feabcb06832ed Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:33:21 -0500 Subject: [PATCH 162/425] Add type annotations for elasticsearch. --- elasticsearch/testcontainers/elasticsearch/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index aa21ffeda..7b19dcacd 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -72,7 +72,7 @@ class ElasticSearchContainer(DockerContainer): '8.3.3' """ - def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs): + def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs) -> None: super(ElasticSearchContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) @@ -84,17 +84,17 @@ def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs): self.with_env(key, value) @wait_container_is_ready() - def _connect(self): + def _connect(self) -> None: res = urllib.request.urlopen(self.get_url()) if res.status != 200: raise Exception() - def get_url(self): + def get_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) return 'http://{}:{}'.format(host, port) - def start(self): + def start(self) -> "ElasticSearchContainer": super().start() self._connect() return self From 13587b343d4f97fde21e138c5a23e909bc24f6d3 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:33:31 -0500 Subject: [PATCH 163/425] Add type annotations for google. --- google/testcontainers/google/pubsub.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 0dc1d9463..9ab6f80ac 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -10,7 +10,9 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +from google.cloud import pubsub +import grpc +from typing import Optional from testcontainers.core.container import DockerContainer @@ -34,9 +36,8 @@ def test_docker_run_pubsub(): topic_path = publisher.topic_path(pubsub.project, "my-topic") topic = publisher.create_topic(topic_path) """ - - def __init__(self, image="google/cloud-sdk:emulators", - project="test-project", port=8432, **kwargs): + def __init__(self, image: str = "google/cloud-sdk:emulators", project: str = "test-project", + port: int = 8432, **kwargs) -> None: super(PubSubContainer, self).__init__(image=image, **kwargs) self.project = project self.port = port @@ -46,21 +47,19 @@ def __init__(self, image="google/cloud-sdk:emulators", project=self.project, port=self.port, )) - def get_pubsub_emulator_host(self): + def get_pubsub_emulator_host(self) -> str: return "{host}:{port}".format(host=self.get_container_host_ip(), port=self.get_exposed_port(self.port)) - def _get_channel(self, channel=None): + def _get_channel(self, channel: Optional[grpc.Channel] = None) -> grpc.Channel: if channel is None: - import grpc return grpc.insecure_channel(target=self.get_pubsub_emulator_host()) + return channel - def get_publisher_client(self, **kwargs): - from google.cloud import pubsub + def get_publisher_client(self, **kwargs) -> pubsub.PublisherClient: kwargs['channel'] = self._get_channel(kwargs.get('channel')) return pubsub.PublisherClient(**kwargs) - def get_subscriber_client(self, **kwargs): - from google.cloud import pubsub + def get_subscriber_client(self, **kwargs) -> pubsub.SubscriberClient: kwargs['channel'] = self._get_channel(kwargs.get('channel')) return pubsub.SubscriberClient(**kwargs) From 47a43eacec2c20a9c11acd487f22cd812378d976 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:33:37 -0500 Subject: [PATCH 164/425] Add type annotations for kafka. --- kafka/testcontainers/kafka/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/kafka/testcontainers/kafka/__init__.py b/kafka/testcontainers/kafka/__init__.py index 9344c4351..0e0ec09bd 100644 --- a/kafka/testcontainers/kafka/__init__.py +++ b/kafka/testcontainers/kafka/__init__.py @@ -26,7 +26,8 @@ class KafkaContainer(DockerContainer): KAFKA_PORT = 9093 TC_START_SCRIPT = '/tc-start.sh' - def __init__(self, image="confluentinc/cp-kafka:5.4.3", port_to_expose=KAFKA_PORT, **kwargs): + def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: int = KAFKA_PORT, + **kwargs) -> None: super(KafkaContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) @@ -42,19 +43,19 @@ def __init__(self, image="confluentinc/cp-kafka:5.4.3", port_to_expose=KAFKA_POR self.with_env('KAFKA_LOG_FLUSH_INTERVAL_MESSAGES', '10000000') self.with_env('KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS', '0') - def get_bootstrap_server(self): + def get_bootstrap_server(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) return '{}:{}'.format(host, port) @wait_container_is_ready(UnrecognizedBrokerVersion, NoBrokersAvailable, KafkaError, ValueError) - def _connect(self): + def _connect(self) -> None: bootstrap_server = self.get_bootstrap_server() consumer = KafkaConsumer(group_id='test', bootstrap_servers=[bootstrap_server]) if not consumer.bootstrap_connected(): raise KafkaError("Unable to connect with kafka container!") - def tc_start(self): + def tc_start(self) -> None: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) listeners = 'PLAINTEXT://{}:{},BROKER://$(hostname -i):9092'.format(host, port) @@ -78,7 +79,7 @@ def tc_start(self): ) self.create_file(data, KafkaContainer.TC_START_SCRIPT) - def start(self): + def start(self) -> "KafkaContainer": script = KafkaContainer.TC_START_SCRIPT command = 'sh -c "while [ ! -f {} ]; do sleep 0.1; done; sh {}"'.format(script, script) self.with_command(command) @@ -87,7 +88,7 @@ def start(self): self._connect() return self - def create_file(self, content: bytes, path: str): + def create_file(self, content: bytes, path: str) -> None: with BytesIO() as archive, tarfile.TarFile(fileobj=archive, mode="w") as tar: tarinfo = tarfile.TarInfo(name=path) tarinfo.size = len(content) From cd927170a71000575edc1dfec0ee87e5d0db0a7c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:33:48 -0500 Subject: [PATCH 165/425] Add type annotations for keycloak. --- keycloak/testcontainers/keycloak/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/keycloak/testcontainers/keycloak/__init__.py b/keycloak/testcontainers/keycloak/__init__.py index b0ffa198b..9d5f332d2 100644 --- a/keycloak/testcontainers/keycloak/__init__.py +++ b/keycloak/testcontainers/keycloak/__init__.py @@ -35,33 +35,33 @@ class KeycloakContainer(DockerContainer): KEYCLOAK_USER = os.environ.get("KEYCLOAK_USER", "test") KEYCLOAK_PASSWORD = os.environ.get("KEYCLOAK_PASSWORD", "test") - def __init__(self, image="jboss/keycloak:latest"): + def __init__(self, image="jboss/keycloak:latest") -> None: super(KeycloakContainer, self).__init__(image=image) self.port_to_expose = 8080 self.with_exposed_ports(self.port_to_expose) - def _configure(self): + def _configure(self) -> None: self.with_env("KEYCLOAK_USER", self.KEYCLOAK_USER) self.with_env("KEYCLOAK_PASSWORD", self.KEYCLOAK_PASSWORD) - def get_url(self): + def get_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) return "http://{}:{}".format(host, port) @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) - def _connect(self): + def _connect(self) -> None: url = self.get_url() response = requests.get("{}/auth".format(url), timeout=1) response.raise_for_status() - def start(self): + def start(self) -> "KeycloakContainer": self._configure() super().start() self._connect() return self - def get_client(self, **kwargs): + def get_client(self, **kwargs) -> KeycloakAdmin: default_kwargs = dict( server_url="{}/auth/".format(self.get_url()), username=self.KEYCLOAK_USER, From 6431f47734e3ada5ab27801559f395b2ad484a41 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:33:56 -0500 Subject: [PATCH 166/425] Add type annotations for localstack. --- .../testcontainers/localstack/__init__.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index c76a5f2f0..f4124091f 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -36,31 +36,35 @@ class LocalStackContainer(DockerContainer): scan_result = dynamo_client.scan(TableName='foo') # Do something with the scan result """ - EDGE_PORT = 4566 - IMAGE = 'localstack/localstack:0.11.4' - - def __init__(self, image=IMAGE, **kwargs): + def __init__(self, image: str = 'localstack/localstack:0.11.4', edge_port: int = 4566, + **kwargs) -> None: super(LocalStackContainer, self).__init__(image, **kwargs) - self.with_exposed_ports(LocalStackContainer.EDGE_PORT) + self.edge_port = edge_port + self.with_exposed_ports(self.edge_port) - def with_services(self, *services): + def with_services(self, *services) -> "LocalStackContainer": """ Restrict what services to run. By default all localstack services are launched. - :return: the DockerContainer to allow chaining of 'with_*' calls. + + Args: + services: Sequency of services to launch. + + Returns: + self: Container to allow chaining of 'with_*' calls. """ return self.with_env('SERVICES', ','.join(services)) - def get_url(self): + def get_url(self) -> str: """ Use this to call localstack instead of real AWS services. ex: boto3.client('lambda', endpoint_url=localstack.get_url()) :return: the endpoint where localstack is reachable. """ host = self.get_container_host_ip() - port = self.get_exposed_port(LocalStackContainer.EDGE_PORT) + port = self.get_exposed_port(self.edge_port) return 'http://{}:{}'.format(host, port) - def start(self, timeout=60): + def start(self, timeout: float = 60) -> "LocalStackContainer": super().start() wait_for_logs(self, r'Ready\.\n', timeout=timeout) return self From b6807bdc0a155b525474d2b34cee4c9b6b6fe5c2 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:34:10 -0500 Subject: [PATCH 167/425] Add type annotations for minio. --- minio/testcontainers/minio/__init__.py | 27 +++++++++----------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/minio/testcontainers/minio/__init__.py b/minio/testcontainers/minio/__init__.py index 039cff647..f97947648 100644 --- a/minio/testcontainers/minio/__init__.py +++ b/minio/testcontainers/minio/__init__.py @@ -34,24 +34,15 @@ class MinioContainer(DockerContainer): ... retrieved_content = client.get_object("test", "testfile.txt").data """ - def __init__( - self, - image="minio/minio:RELEASE.2022-12-02T19-19-22Z", - port_to_expose=9000, - access_key="minioadmin", - secret_key="minioadmin", - **kwargs, - ): + def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", + port_to_expose: int = 9000, access_key: str = "minioadmin", + secret_key: str = "minioadmin", **kwargs) -> None: """ Args: - image (str, optional): The Docker image to use for the Minio container. - Defaults to "minio/minio:RELEASE.2022-12-02T19-19-22Z". - port_to_expose (int, optional): The port to expose on the container. - Defaults to 9000. - access_key (str, optional): The access key for client connections. - Defaults to "minioadmin". - secret_key (str, optional): The secret key for client connections. - Defaults to "minioadmin". + image: Docker image to use for the MinIO container. + port_to_expose: Port to expose on the container. + access_key: Access key for client connections. + secret_key: Secret key for client connections. """ super(MinioContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose @@ -96,14 +87,14 @@ def get_config(self) -> dict: } @wait_container_is_ready(ConnectionError) - def _healthcheck(self): + def _healthcheck(self) -> None: """This is an internal method used to check if the Minio container is healthy and ready to receive requests.""" url = f"http://{self.get_config()['endpoint']}/minio/health/live" response: Response = get(url) response.raise_for_status() - def start(self): + def start(self) -> "MinioContainer": """This method starts the Minio container and runs the healthcheck to verify that the container is ready to use.""" super().start() From e88d2c53acd988d94b77dc6f5b49eee07b69b034 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:34:16 -0500 Subject: [PATCH 168/425] Add type annotations for mongodb. --- mongodb/testcontainers/mongodb/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/mongodb/testcontainers/mongodb/__init__.py b/mongodb/testcontainers/mongodb/__init__.py index eeaf8e0d7..279630383 100644 --- a/mongodb/testcontainers/mongodb/__init__.py +++ b/mongodb/testcontainers/mongodb/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. import os - +from pymongo import MongoClient from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -50,21 +50,18 @@ class MongoDbContainer(DbContainer): MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") MONGO_DB = os.environ.get("MONGO_DB", "test") - def __init__(self, - image: str = "mongo:latest", - port_to_expose: int = 27017, - **kwargs): + def __init__(self, image: str = "mongo:latest", port_to_expose: int = 27017, **kwargs) -> None: super(MongoDbContainer, self).__init__(image=image, **kwargs) self.command = "mongo" self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) - def _configure(self): + def _configure(self) -> None: self.with_env("MONGO_INITDB_ROOT_USERNAME", self.MONGO_INITDB_ROOT_USERNAME) self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.MONGO_INITDB_ROOT_PASSWORD) self.with_env("MONGO_DB", self.MONGO_DB) - def get_connection_url(self): + def get_connection_url(self) -> str: return self._create_connection_url( dialect='mongodb', username=self.MONGO_INITDB_ROOT_USERNAME, @@ -73,9 +70,8 @@ def get_connection_url(self): ) @wait_container_is_ready() - def _connect(self): - from pymongo import MongoClient + def _connect(self) -> MongoClient: return MongoClient(self.get_connection_url()) - def get_connection_client(self): + def get_connection_client(self) -> MongoClient: return self._connect() From 8ce72319ef3ca3f061972bfd19a377023b44ef4a Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:34:23 -0500 Subject: [PATCH 169/425] Add type annotations for mssql. --- mssql/testcontainers/mssql/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index 6dd623181..817000533 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -1,5 +1,5 @@ from os import environ - +from typing import Optional from testcontainers.core.generic import DbContainer @@ -24,8 +24,9 @@ class SqlServerContainer(DbContainer): linux-mac/installing-the-microsoft-odbc-driver-for-sql-server>`_. """ - def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA", password=None, - port=1433, dbname="tempdb", dialect='mssql+pymssql', **kwargs): + def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", user: str = "SA", + password: Optional[str] = None, port: int = 1433, dbname: str = "tempdb", + dialect: str = 'mssql+pymssql', **kwargs) -> None: super(SqlServerContainer, self).__init__(image, **kwargs) self.port_to_expose = port @@ -36,13 +37,13 @@ def __init__(self, image="mcr.microsoft.com/mssql/server:2019-latest", user="SA" self.SQLSERVER_DBNAME = dbname self.dialect = dialect - def _configure(self): + def _configure(self) -> None: self.with_env("SA_PASSWORD", self.SQLSERVER_PASSWORD) self.with_env("SQLSERVER_USER", self.SQLSERVER_USER) self.with_env("SQLSERVER_DBNAME", self.SQLSERVER_DBNAME) self.with_env("ACCEPT_EULA", 'Y') - def get_connection_url(self): + def get_connection_url(self) -> str: return super()._create_connection_url( dialect=self.dialect, username=self.SQLSERVER_USER, password=self.SQLSERVER_PASSWORD, db_name=self.SQLSERVER_DBNAME, port=self.port_to_expose From bf6ff4ba1f59fa5ee644a2b3bc0ab656f25d7586 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:34:30 -0500 Subject: [PATCH 170/425] Add type annotations for mysql. --- mysql/testcontainers/mysql/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index 0f5167072..a662b0bdb 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. from os import environ - +from typing import Optional from testcontainers.core.generic import DbContainer @@ -36,13 +36,9 @@ class MySqlContainer(DbContainer): ... version, = result.fetchone() """ - def __init__(self, - image="mysql:latest", - MYSQL_USER=None, - MYSQL_ROOT_PASSWORD=None, - MYSQL_PASSWORD=None, - MYSQL_DATABASE=None, - **kwargs): + def __init__(self, image: str = "mysql:latest", MYSQL_USER: Optional[str] = None, + MYSQL_ROOT_PASSWORD: Optional[str] = None, MYSQL_PASSWORD: Optional[str] = None, + MYSQL_DATABASE: Optional[str] = None, **kwargs) -> None: super(MySqlContainer, self).__init__(image, **kwargs) self.port_to_expose = 3306 self.with_exposed_ports(self.port_to_expose) @@ -54,7 +50,7 @@ def __init__(self, if self.MYSQL_USER == 'root': self.MYSQL_ROOT_PASSWORD = self.MYSQL_PASSWORD - def _configure(self): + def _configure(self) -> None: self.with_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) self.with_env("MYSQL_DATABASE", self.MYSQL_DATABASE) @@ -62,7 +58,7 @@ def _configure(self): self.with_env("MYSQL_USER", self.MYSQL_USER) self.with_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) - def get_connection_url(self): + def get_connection_url(self) -> str: return super()._create_connection_url(dialect="mysql+pymysql", username=self.MYSQL_USER, password=self.MYSQL_PASSWORD, From 46bf316b0879d275aa3b32ab6a58259bb2762caa Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:34:37 -0500 Subject: [PATCH 171/425] Add type annotations for neo4j. --- neo4j/testcontainers/neo4j/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index daa127ec3..89b6b6746 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -13,7 +13,7 @@ import os -from neo4j import GraphDatabase +from neo4j import Driver, GraphDatabase from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -38,29 +38,25 @@ class Neo4jContainer(DbContainer): # The official image requires a change of password on startup. NEO4J_ADMIN_PASSWORD = os.environ.get("NEO4J_ADMIN_PASSWORD", "password") - # Default port for the binary Bolt protocol. DEFAULT_BOLT_PORT = 7687 - AUTH_FORMAT = "neo4j/{password}" - NEO4J_STARTUP_TIMEOUT_SECONDS = 10 - NEO4J_USER = "neo4j" - def __init__(self, image="neo4j:latest", **kwargs): + def __init__(self, image: str = "neo4j:latest", **kwargs) -> None: super(Neo4jContainer, self).__init__(image, **kwargs) self.bolt_port = Neo4jContainer.DEFAULT_BOLT_PORT self.with_exposed_ports(self.bolt_port) self._driver = None - def _configure(self): + def _configure(self) -> None: self.with_env( "NEO4J_AUTH", Neo4jContainer.AUTH_FORMAT.format(password=Neo4jContainer.NEO4J_ADMIN_PASSWORD) ) - def get_connection_url(self): + def get_connection_url(self) -> str: return "{dialect}://{host}:{port}".format( dialect="bolt", host=self.get_container_host_ip(), @@ -68,7 +64,7 @@ def get_connection_url(self): ) @wait_container_is_ready() - def _connect(self): + def _connect(self) -> None: # First we wait for Neo4j to say it's listening wait_for_logs( self, @@ -83,7 +79,7 @@ def _connect(self): with driver.session() as session: session.run("RETURN 1").single() - def get_driver(self, **kwargs): + def get_driver(self, **kwargs) -> Driver: return GraphDatabase.driver( self.get_connection_url(), auth=(Neo4jContainer.NEO4J_USER, Neo4jContainer.NEO4J_ADMIN_PASSWORD), From 087cada844b3a0105b22e201b0464cb5547d87a5 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:34:44 -0500 Subject: [PATCH 172/425] Add type annotations for nginx. --- nginx/testcontainers/nginx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/testcontainers/nginx/__init__.py b/nginx/testcontainers/nginx/__init__.py index a9c336181..2cb8851cf 100644 --- a/nginx/testcontainers/nginx/__init__.py +++ b/nginx/testcontainers/nginx/__init__.py @@ -14,7 +14,7 @@ class NginxContainer(DockerContainer): - def __init__(self, image="nginx:latest", port_to_expose=80, **kwargs): + def __init__(self, image: str = "nginx:latest", port_to_expose: int = 80, **kwargs) -> None: super(NginxContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) From 29e4b4bd97d3bac9f00fdc5f7ed388e3792f9a51 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:35:00 -0500 Subject: [PATCH 173/425] Add type annotations for opensearch. --- .../testcontainers/opensearch/__init__.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/opensearch/testcontainers/opensearch/__init__.py b/opensearch/testcontainers/opensearch/__init__.py index 36d6addcb..5192a2bfa 100644 --- a/opensearch/testcontainers/opensearch/__init__.py +++ b/opensearch/testcontainers/opensearch/__init__.py @@ -28,21 +28,13 @@ class OpenSearchContainer(DockerContainer): ... search_result = client.search(index="test", body={"query": {"match_all": {}}}) """ - def __init__( - self, - image="opensearchproject/opensearch:2.4.0", - port_to_expose=9200, - security_enabled=False, - **kwargs, - ): + def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", + port_to_expose: int = 9200, security_enabled: bool = False, **kwargs) -> None: """ Args: - image (str, optional): The Docker image to use for the container. - Defaults to "opensearchproject/opensearch:2.4.0". - port_to_expose (int, optional): The port to expose on the container. - Defaults to 9200. - security_enabled (bool, optional): `False` disables the security plugin in OpenSearch. - Defaults to False. + image: Docker image to use for the container. + port_to_expose: Port to expose on the container. + security_enabled: :code:`False` disables the security plugin in OpenSearch. """ super(OpenSearchContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose @@ -54,7 +46,7 @@ def __init__( if security_enabled: self.with_env("plugins.security.allow_default_init_securityindex", "true") - def get_config(self): + def get_config(self) -> dict: """This method returns the configuration of the OpenSearch container, including the host, port, user, and password. @@ -91,13 +83,13 @@ def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: ) @wait_container_is_ready(ConnectionError, TransportError) - def _healthcheck(self): + def _healthcheck(self) -> None: """This is an internal method used to check if the OpenSearch container is healthy and ready to receive requests.""" client: OpenSearchContainer = self.get_client() client.cluster.health(wait_for_status="green") - def start(self): + def start(self) -> "OpenSearchContainer": """This method starts the OpenSearch container and runs the healthcheck to verify that the container is ready to use.""" super().start() From bb63fea38b459926232a13f51a472ac6cf6e0cfc Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:35:08 -0500 Subject: [PATCH 174/425] Add type annotations for oracle. --- oracle/testcontainers/oracle/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oracle/testcontainers/oracle/__init__.py b/oracle/testcontainers/oracle/__init__.py index 595bbe32e..989a20b75 100644 --- a/oracle/testcontainers/oracle/__init__.py +++ b/oracle/testcontainers/oracle/__init__.py @@ -17,17 +17,17 @@ class OracleDbContainer(DbContainer): ... result = e.execute("select * from V$VERSION") """ - def __init__(self, image="wnameless/oracle-xe-11g-r2:latest", **kwargs): + def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) -> None: super(OracleDbContainer, self).__init__(image=image, **kwargs) self.container_port = 1521 self.with_exposed_ports(self.container_port) self.with_env("ORACLE_ALLOW_REMOTE", "true") - def get_connection_url(self): + def get_connection_url(self) -> str: return super()._create_connection_url( dialect="oracle", username="system", password="oracle", port=self.container_port, db_name="xe" ) - def _configure(self): + def _configure(self) -> None: pass From 557bc44fb55565b3e6817b6dcd06b469b02d192e Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:35:19 -0500 Subject: [PATCH 175/425] Add type annotations for postgres. --- postgres/testcontainers/postgres/__init__.py | 27 ++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index d32da472a..a8a99f2e5 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. import os - +from typing import Optional from testcontainers.core.generic import DbContainer @@ -39,13 +39,9 @@ class PostgresContainer(DbContainer): POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "test") POSTGRES_DB = os.environ.get("POSTGRES_DB", "test") - def __init__(self, - image="postgres:latest", - port=5432, user=None, - password=None, - dbname=None, - driver="psycopg2", - **kwargs): + def __init__(self, image: str = "postgres:latest", port: int = 5432, user: Optional[str] = None, + password: Optional[str] = None, dbname: Optional[str] = None, + driver: str = "psycopg2", **kwargs) -> None: super(PostgresContainer, self).__init__(image=image, **kwargs) self.POSTGRES_USER = user or self.POSTGRES_USER self.POSTGRES_PASSWORD = password or self.POSTGRES_PASSWORD @@ -55,15 +51,14 @@ def __init__(self, self.with_exposed_ports(self.port_to_expose) - def _configure(self): + def _configure(self) -> None: self.with_env("POSTGRES_USER", self.POSTGRES_USER) self.with_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) self.with_env("POSTGRES_DB", self.POSTGRES_DB) - def get_connection_url(self, host=None): - return super()._create_connection_url(dialect="postgresql+{}".format(self.driver), - username=self.POSTGRES_USER, - password=self.POSTGRES_PASSWORD, - db_name=self.POSTGRES_DB, - host=host, - port=self.port_to_expose) + def get_connection_url(self, host=None) -> str: + return super()._create_connection_url( + dialect="postgresql+{}".format(self.driver), username=self.POSTGRES_USER, + password=self.POSTGRES_PASSWORD, db_name=self.POSTGRES_DB, host=host, + port=self.port_to_expose, + ) From 5d04bfb251d816c7e347058b3ee422238e9f4e68 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:35:31 -0500 Subject: [PATCH 176/425] Add type annotations for rabbitmq. --- rabbitmq/testcontainers/rabbitmq/__init__.py | 24 ++++++-------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/rabbitmq/testcontainers/rabbitmq/__init__.py b/rabbitmq/testcontainers/rabbitmq/__init__.py index 402404bca..144f1de80 100644 --- a/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -27,25 +27,15 @@ class RabbitMqContainer(DockerContainer): RABBITMQ_DEFAULT_USER = os.environ.get("RABBITMQ_DEFAULT_USER", "guest") RABBITMQ_DEFAULT_PASS = os.environ.get("RABBITMQ_DEFAULT_PASS", "guest") - def __init__( - self, - image: str = "rabbitmq:latest", - port: Optional[int] = None, - username: Optional[str] = None, - password: Optional[str] = None, - **kwargs, - ) -> None: + def __init__(self, image: str = "rabbitmq:latest", port: Optional[int] = None, + username: Optional[str] = None, password: Optional[str] = None, **kwargs) -> None: """Initialize the RabbitMQ test container. Args: - image (str, optional): - The docker image from docker hub. Defaults to "rabbitmq:latest". - port (int, optional): - The port to reach the AMQP API. Defaults to 5672. - username (str, optional): - Overwrite the default username which is "guest". - password (str, optional): - Overwrite the default username which is "guest". + image: Docker image from docker hub. Defaults to "rabbitmq:latest". + port: Port to reach the AMQP API. Defaults to 5672. + username: RabbitMQ username. + password: RabbitMQ password. """ super(RabbitMqContainer, self).__init__(image=image, **kwargs) self.RABBITMQ_NODE_PORT = port or int(self.RABBITMQ_NODE_PORT) @@ -81,7 +71,7 @@ def get_connection_params(self) -> pika.ConnectionParameters: credentials=credentials, ) - def start(self): + def start(self) -> "RabbitMqContainer": """Start the test container.""" super().start() self.readiness_probe() From 391ad823692e409f661967f44c60aad65703081d Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:35:48 -0500 Subject: [PATCH 177/425] Add type annotations for redis. --- redis/testcontainers/redis/__init__.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/redis/testcontainers/redis/__init__.py b/redis/testcontainers/redis/__init__.py index b7da14407..92fdccec8 100644 --- a/redis/testcontainers/redis/__init__.py +++ b/redis/testcontainers/redis/__init__.py @@ -29,7 +29,7 @@ class RedisContainer(DockerContainer): >>> with RedisContainer() as redis_container: ... redis_client = redis_container.get_client() """ - def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs): + def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs) -> None: super(RedisContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.password = password @@ -38,23 +38,20 @@ def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **k self.with_command(f"redis-server --requirepass {self.password}") @wait_container_is_ready(redis.exceptions.ConnectionError) - def _connect(self): + def _connect(self) -> None: client = self.get_client() if not client.ping(): raise redis.exceptions.ConnectionError("Could not connect to Redis") - def get_client(self, **kwargs): - """get redis client + def get_client(self, **kwargs) -> redis.Redis: + """ + Get a redis client. - Parameters - ---------- - kwargs: dict - Keyword arguments passed to `redis.Redis`. + Args: + **kwargs: Keyword arguments passed to `redis.Redis`. - Returns - ------- - client: redis.Redis - Redis client to connect to the container. + Returns: + client: Redis client to connect to the container. """ return redis.Redis( host=self.get_container_host_ip(), @@ -63,7 +60,7 @@ def get_client(self, **kwargs): **kwargs, ) - def start(self): + def start(self) -> "RedisContainer": super().start() self._connect() return self From dee53c67e1155e4394765a3ebc0c4e2bc1cddb66 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:35:55 -0500 Subject: [PATCH 178/425] Add type annotations for selenium. --- selenium/testcontainers/selenium/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/selenium/testcontainers/selenium/__init__.py b/selenium/testcontainers/selenium/__init__.py index 597abca28..58034185b 100644 --- a/selenium/testcontainers/selenium/__init__.py +++ b/selenium/testcontainers/selenium/__init__.py @@ -17,8 +17,10 @@ Allows to spin up selenium containers for testing with browsers. """ +from selenium import webdriver from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready +from typing import Optional import urllib3 @@ -28,7 +30,7 @@ } -def get_image_name(capabilities): +def get_image_name(capabilities: str) -> str: return IMAGES[capabilities['browserName']] @@ -49,7 +51,7 @@ class BrowserWebDriverContainer(DockerContainer): You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ - def __init__(self, capabilities, image=None, **kwargs): + def __init__(self, capabilities: str, image: Optional[str] = None, **kwargs) -> None: self.capabilities = capabilities self.image = image or get_image_name(capabilities) self.port_to_expose = 4444 @@ -57,21 +59,20 @@ def __init__(self, capabilities, image=None, **kwargs): super(BrowserWebDriverContainer, self).__init__(image=self.image, **kwargs) self.with_exposed_ports(self.port_to_expose, self.vnc_port_to_expose) - def _configure(self): + def _configure(self) -> None: self.with_env("no_proxy", "localhost") self.with_env("HUB_ENV_no_proxy", "localhost") @wait_container_is_ready(urllib3.exceptions.HTTPError) - def _connect(self): - from selenium import webdriver + def _connect(self) -> webdriver.Remote: return webdriver.Remote( command_executor=(self.get_connection_url()), desired_capabilities=self.capabilities) - def get_driver(self): + def get_driver(self) -> webdriver.Remote: return self._connect() def get_connection_url(self) -> str: ip = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) - return 'http://{}:{}/wd/hub'.format(ip, port) + return f'http://{ip}:{port}/wd/hub' From 69900da891c49fc9515d55c8eb8d2f192df07efa Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 18:47:37 -0500 Subject: [PATCH 179/425] Use google docstring syntax for examples. https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html --- arangodb/testcontainers/arangodb/__init__.py | 34 ++++----- azurite/testcontainers/azurite/__init__.py | 26 +++---- .../testcontainers/clickhouse/__init__.py | 22 +++--- compose/testcontainers/compose/__init__.py | 75 ++++++++++--------- .../testcontainers/elasticsearch/__init__.py | 24 +++--- google/testcontainers/google/pubsub.py | 26 +++---- kafka/testcontainers/kafka/__init__.py | 12 +-- keycloak/testcontainers/keycloak/__init__.py | 12 +-- .../testcontainers/localstack/__init__.py | 26 +++---- minio/testcontainers/minio/__init__.py | 32 ++++---- mongodb/testcontainers/mongodb/__init__.py | 46 ++++++------ mssql/testcontainers/mssql/__init__.py | 21 ++---- mysql/testcontainers/mysql/__init__.py | 27 +++---- neo4j/testcontainers/neo4j/__init__.py | 18 ++--- .../testcontainers/opensearch/__init__.py | 29 ++++--- oracle/testcontainers/oracle/__init__.py | 16 ++-- postgres/testcontainers/postgres/__init__.py | 28 +++---- rabbitmq/testcontainers/rabbitmq/__init__.py | 19 ++--- redis/testcontainers/redis/__init__.py | 12 +-- selenium/testcontainers/selenium/__init__.py | 16 ++-- 20 files changed, 260 insertions(+), 261 deletions(-) diff --git a/arangodb/testcontainers/arangodb/__init__.py b/arangodb/testcontainers/arangodb/__init__.py index 463037535..af7156fb2 100644 --- a/arangodb/testcontainers/arangodb/__init__.py +++ b/arangodb/testcontainers/arangodb/__init__.py @@ -12,26 +12,26 @@ class ArangoDbContainer(DbContainer): """ ArangoDB container. - Example - ------- - The example will spin up a ArangoDB container. - You may use the :code:`get_connection_url()` method which returns a arangoclient-compatible url - in format :code:`scheme://host:port`. As of now, only a single host is supported (over HTTP). + Example: - .. doctest:: + This example spins up an ArangoDB container. You may use the :code:`get_connection_url()` + method which returns a arangoclient-compatible url in format :code:`scheme://host:port`. As + of now, only a single host is supported (over HTTP). - >>> from testcontainers.arangodb import ArangoDbContainer - >>> from arango import ArangoClient + .. doctest:: - >>> with ArangoDbContainer("arangodb:3.9.1") as arango: - ... client = ArangoClient(hosts=arango.get_connection_url()) - ... - ... # Connect - ... sys_db = client.db(username="root", password="passwd") - ... - ... # Create a new database named "test". - ... sys_db.create_database("test") - True + >>> from testcontainers.arangodb import ArangoDbContainer + >>> from arango import ArangoClient + + >>> with ArangoDbContainer("arangodb:3.9.1") as arango: + ... client = ArangoClient(hosts=arango.get_connection_url()) + ... + ... # Connect + ... sys_db = client.db(username="root", password="passwd") + ... + ... # Create a new database named "test". + ... sys_db.create_database("test") + True """ def __init__(self, diff --git a/azurite/testcontainers/azurite/__init__.py b/azurite/testcontainers/azurite/__init__.py index 9859e003b..37cada2f0 100644 --- a/azurite/testcontainers/azurite/__init__.py +++ b/azurite/testcontainers/azurite/__init__.py @@ -25,19 +25,19 @@ class AzuriteContainer(DockerContainer): :code:`get_connection_string` can be used to create a client for Blob service, Queue service and Table service. - Example - ------- - .. doctest:: - - >>> from testcontainers.azurite import AzuriteContainer - >>> from azure.storage.blob import BlobServiceClient - - >>> with AzuriteContainer() as azurite_container: - ... connection_string = azurite_container.get_connection_string() - ... client = BlobServiceClient.from_connection_string( - ... connection_string, - ... api_version="2019-12-12" - ... ) + Example: + + .. doctest:: + + >>> from testcontainers.azurite import AzuriteContainer + >>> from azure.storage.blob import BlobServiceClient + + >>> with AzuriteContainer() as azurite_container: + ... connection_string = azurite_container.get_connection_string() + ... client = BlobServiceClient.from_connection_string( + ... connection_string, + ... api_version="2019-12-12" + ... ) """ _AZURITE_ACCOUNT_NAME = os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index da550e141..b6ffce609 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -24,20 +24,20 @@ class ClickHouseContainer(DbContainer): """ ClickHouse database container. - Example - ------- - The example spins up a ClickHouse database and connects to it - using the :code:`clickhouse-driver`. + Example: - .. doctest:: + The example spins up a ClickHouse database and connects to it using the + :code:`clickhouse-driver`. - >>> import clickhouse_driver - >>> from testcontainers.clickhouse import ClickHouseContainer + .. doctest:: - >>> with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: - ... client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) - ... client.execute("select 'working'") - [('working',)] + >>> import clickhouse_driver + >>> from testcontainers.clickhouse import ClickHouseContainer + + >>> with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: + ... client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) + ... client.execute("select 'working'") + [('working',)] """ CLICKHOUSE_USER = os.environ.get("CLICKHOUSE_USER", "test") diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index d2e3e169a..6f5f2109e 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -24,43 +24,44 @@ class DockerCompose: build: Build images referenced in the configuration file. env_file: Path to an env file containing environment variables to pass to docker compose. - Example - ------- - .. doctest:: - - with DockerCompose("/home/project", - compose_file_name=["docker-compose-1.yml", "docker-compose-2.yml"], - pull=True) as compose: - host = compose.get_service_host("hub", 4444) - port = compose.get_service_port("hub", 4444) - driver = webdriver.Remote( - command_executor=("http://{}:{}/wd/hub".format(host,port)), - desired_capabilities=CHROME, - ) - driver.get("http://automation-remarks.com") - stdout, stderr = compose.get_logs() - if stderr: - print("Errors\\n:{}".format(stderr)) - - - .. code-block:: yaml - - hub: - image: selenium/hub - ports: - - "4444:4444" - firefox: - image: selenium/node-firefox - links: - - hub - expose: - - "5555" - chrome: - image: selenium/node-chrome - links: - - hub - expose: - - "5555" + Example: + + This example spins up chrome and firefox containers using docker compose. + + .. doctest:: + + compose_filename = ["docker-compose-1.yml", "docker-compose-2.yml"] + with DockerCompose("/home/project", compose_file_name=compose_file_name, pull=True) as \ + compose: + host = compose.get_service_host("hub", 4444) + port = compose.get_service_port("hub", 4444) + driver = webdriver.Remote( + command_executor=("http://{}:{}/wd/hub".format(host,port)), + desired_capabilities=CHROME, + ) + driver.get("http://automation-remarks.com") + stdout, stderr = compose.get_logs() + if stderr: + print("Errors\\n:{}".format(stderr)) + + .. code-block:: yaml + + hub: + image: selenium/hub + ports: + - "4444:4444" + firefox: + image: selenium/node-firefox + links: + - hub + expose: + - "5555" + chrome: + image: selenium/node-chrome + links: + - hub + expose: + - "5555" """ def __init__( self, diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index 7b19dcacd..4c2fbbd27 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -58,18 +58,18 @@ class ElasticSearchContainer(DockerContainer): """ ElasticSearch container. - Example - ------- - .. doctest:: - - >>> import json - >>> import urllib - >>> from testcontainers.elasticsearch import ElasticSearchContainer - - >>> with ElasticSearchContainer(f'elasticsearch:8.3.3') as es: - ... resp = urllib.request.urlopen(es.get_url()) - ... json.loads(resp.read().decode())['version']['number'] - '8.3.3' + Example: + + .. doctest:: + + >>> import json + >>> import urllib + >>> from testcontainers.elasticsearch import ElasticSearchContainer + + >>> with ElasticSearchContainer(f'elasticsearch:8.3.3') as es: + ... resp = urllib.request.urlopen(es.get_url()) + ... json.loads(resp.read().decode())['version']['number'] + '8.3.3' """ def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs) -> None: diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 9ab6f80ac..8a75baa27 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -20,21 +20,21 @@ class PubSubContainer(DockerContainer): """ PubSub container for testing managed message queues. - Example - ------- - The example will spin up a Google Cloud PubSub emulator that you can use for integration tests. - The :code:`pubsub` instance provides convenience methods :code:`get_publisher` and - :code:`get_subscriber` to connect to the emulator without having to set the environment variable - :code:`PUBSUB_EMULATOR_HOST`. + Example: - .. doctest:: + The example will spin up a Google Cloud PubSub emulator that you can use for integration + tests. The :code:`pubsub` instance provides convenience methods :code:`get_publisher` and + :code:`get_subscriber` to connect to the emulator without having to set the environment + variable :code:`PUBSUB_EMULATOR_HOST`. - def test_docker_run_pubsub(): - config = PubSubContainer('google/cloud-sdk:emulators') - with config as pubsub: - publisher = pubsub.get_publisher() - topic_path = publisher.topic_path(pubsub.project, "my-topic") - topic = publisher.create_topic(topic_path) + .. doctest:: + + def test_docker_run_pubsub(): + config = PubSubContainer('google/cloud-sdk:emulators') + with config as pubsub: + publisher = pubsub.get_publisher() + topic_path = publisher.topic_path(pubsub.project, "my-topic") + topic = publisher.create_topic(topic_path) """ def __init__(self, image: str = "google/cloud-sdk:emulators", project: str = "test-project", port: int = 8432, **kwargs) -> None: diff --git a/kafka/testcontainers/kafka/__init__.py b/kafka/testcontainers/kafka/__init__.py index 0e0ec09bd..b7cb914f2 100644 --- a/kafka/testcontainers/kafka/__init__.py +++ b/kafka/testcontainers/kafka/__init__.py @@ -14,14 +14,14 @@ class KafkaContainer(DockerContainer): """ Kafka container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.kafka import KafkaContainer + .. doctest:: - >>> with KafkaContainer() as kafka: - ... connection = kafka.get_bootstrap_server() + >>> from testcontainers.kafka import KafkaContainer + + >>> with KafkaContainer() as kafka: + ... connection = kafka.get_bootstrap_server() """ KAFKA_PORT = 9093 TC_START_SCRIPT = '/tc-start.sh' diff --git a/keycloak/testcontainers/keycloak/__init__.py b/keycloak/testcontainers/keycloak/__init__.py index 9d5f332d2..c73ffdf07 100644 --- a/keycloak/testcontainers/keycloak/__init__.py +++ b/keycloak/testcontainers/keycloak/__init__.py @@ -23,14 +23,14 @@ class KeycloakContainer(DockerContainer): """ Keycloak container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.keycloak import KeycloakContainer + .. doctest:: - >>> with KeycloakContainer() as kc: - ... keycloak = kc.get_client() + >>> from testcontainers.keycloak import KeycloakContainer + + >>> with KeycloakContainer() as kc: + ... keycloak = kc.get_client() """ KEYCLOAK_USER = os.environ.get("KEYCLOAK_USER", "test") KEYCLOAK_PASSWORD = os.environ.get("KEYCLOAK_PASSWORD", "test") diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index f4124091f..61c34490f 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -18,23 +18,23 @@ class LocalStackContainer(DockerContainer): """ Localstack container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.localstack import LocalStackContainer + .. doctest:: - >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: - ... localstack.with_services("dynamodb", "lambda") - ... dynamo_endpoint = localstack.get_url() - + >>> from testcontainers.localstack import LocalStackContainer - The endpoint can be used to create a client with the boto3 library: - .. doctest:: + >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: + ... localstack.with_services("dynamodb", "lambda") + ... dynamo_endpoint = localstack.get_url() + - dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) - scan_result = dynamo_client.scan(TableName='foo') - # Do something with the scan result + The endpoint can be used to create a client with the boto3 library: + .. doctest:: + + dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) + scan_result = dynamo_client.scan(TableName='foo') + # Do something with the scan result """ def __init__(self, image: str = 'localstack/localstack:0.11.4', edge_port: int = 4566, **kwargs) -> None: diff --git a/minio/testcontainers/minio/__init__.py b/minio/testcontainers/minio/__init__.py index f97947648..bdfb8963f 100644 --- a/minio/testcontainers/minio/__init__.py +++ b/minio/testcontainers/minio/__init__.py @@ -14,24 +14,24 @@ class MinioContainer(DockerContainer): The method :code:`get_config` can be used to retrieve the endpoint, access key and secret key of the container. - Example - ------- - .. doctest:: + Example: - >>> import io - >>> from testcontainers.minio import MinioContainer + .. doctest:: - >>> with MinioContainer() as minio: - ... client = minio.get_client() - ... client.make_bucket("test") - ... test_content = b"Hello World" - ... write_result = client.put_object( - ... "test", - ... "testfile.txt", - ... io.BytesIO(test_content), - ... length=len(test_content), - ... ) - ... retrieved_content = client.get_object("test", "testfile.txt").data + >>> import io + >>> from testcontainers.minio import MinioContainer + + >>> with MinioContainer() as minio: + ... client = minio.get_client() + ... client.make_bucket("test") + ... test_content = b"Hello World" + ... write_result = client.put_object( + ... "test", + ... "testfile.txt", + ... io.BytesIO(test_content), + ... length=len(test_content), + ... ) + ... retrieved_content = client.get_object("test", "testfile.txt").data """ def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", diff --git a/mongodb/testcontainers/mongodb/__init__.py b/mongodb/testcontainers/mongodb/__init__.py index 279630383..535dfffb9 100644 --- a/mongodb/testcontainers/mongodb/__init__.py +++ b/mongodb/testcontainers/mongodb/__init__.py @@ -20,31 +20,31 @@ class MongoDbContainer(DbContainer): """ Mongo document-based database container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.mongodb import MongoDbContainer + .. doctest:: - >>> with MongoDbContainer("mongo:latest") as mongo: - ... db = mongo.get_connection_client().test - ... # Insert a database entry - ... result = db.restaurants.insert_one( - ... { - ... "address": { - ... "street": "2 Avenue", - ... "zipcode": "10075", - ... "building": "1480", - ... "coord": [-73.9557413, 40.7720266] - ... }, - ... "borough": "Manhattan", - ... "cuisine": "Italian", - ... "name": "Vella", - ... "restaurant_id": "41704620" - ... } - ... ) - ... # Find the restaurant document - ... cursor = db.restaurants.find({"borough": "Manhattan"}) + >>> from testcontainers.mongodb import MongoDbContainer + + >>> with MongoDbContainer("mongo:latest") as mongo: + ... db = mongo.get_connection_client().test + ... # Insert a database entry + ... result = db.restaurants.insert_one( + ... { + ... "address": { + ... "street": "2 Avenue", + ... "zipcode": "10075", + ... "building": "1480", + ... "coord": [-73.9557413, 40.7720266] + ... }, + ... "borough": "Manhattan", + ... "cuisine": "Italian", + ... "name": "Vella", + ... "restaurant_id": "41704620" + ... } + ... ) + ... # Find the restaurant document + ... cursor = db.restaurants.find({"borough": "Manhattan"}) """ MONGO_INITDB_ROOT_USERNAME = os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index 817000533..678d12891 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -5,23 +5,18 @@ class SqlServerContainer(DbContainer): """ - Microsoft Sql Server database container. + Microsoft SQL Server database container. - Example - ------- - .. doctest:: + Example: - >>> import sqlalchemy - >>> from testcontainers.mssql import SqlServerContainer + .. doctest:: - >>> with SqlServerContainer() as mssql: - ... e = sqlalchemy.create_engine(mssql.get_connection_url()) - ... result = e.execute("select @@VERSION") + >>> import sqlalchemy + >>> from testcontainers.mssql import SqlServerContainer - Notes - ----- - Requires `ODBC Driver 17 for SQL Server `_. + >>> with SqlServerContainer() as mssql: + ... e = sqlalchemy.create_engine(mssql.get_connection_url()) + ... result = e.execute("select @@VERSION") """ def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", user: str = "SA", diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index a662b0bdb..ecc5f68fe 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -19,21 +19,22 @@ class MySqlContainer(DbContainer): """ MySql database container. - Example - ------- - The example will spin up a MySql database to which you can connect with the credentials passed - in the constructor. Alternatively, you may use the :code:`get_connection_url()` method which - returns a sqlalchemy-compatible url in format - :code:`dialect+driver://username:password@host:port/database`. - .. doctest:: + Example: - >>> import sqlalchemy - >>> from testcontainers.mysql import MySqlContainer + The example will spin up a MySql database to which you can connect with the credentials + passed in the constructor. Alternatively, you may use the :code:`get_connection_url()` + method which returns a sqlalchemy-compatible url in format + :code:`dialect+driver://username:password@host:port/database`. - >>> with MySqlContainer('mysql:5.7.17') as mysql: - ... e = sqlalchemy.create_engine(mysql.get_connection_url()) - ... result = e.execute("select version()") - ... version, = result.fetchone() + .. doctest:: + + >>> import sqlalchemy + >>> from testcontainers.mysql import MySqlContainer + + >>> with MySqlContainer('mysql:5.7.17') as mysql: + ... e = sqlalchemy.create_engine(mysql.get_connection_url()) + ... result = e.execute("select version()") + ... version, = result.fetchone() """ def __init__(self, image: str = "mysql:latest", MYSQL_USER: Optional[str] = None, diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index 89b6b6746..96b2b17ad 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -23,17 +23,17 @@ class Neo4jContainer(DbContainer): """ Neo4j Graph Database (Standalone) database container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.neo4j import Neo4jContainer + .. doctest:: - >>> with Neo4jContainer() as neo4j, \ - neo4j.get_driver() as driver, \ - driver.session() as session: - ... result = session.run("MATCH (n) RETURN n LIMIT 1") - ... record = result.single() + >>> from testcontainers.neo4j import Neo4jContainer + + >>> with Neo4jContainer() as neo4j, \ + neo4j.get_driver() as driver, \ + driver.session() as session: + ... result = session.run("MATCH (n) RETURN n LIMIT 1") + ... record = result.single() """ # The official image requires a change of password on startup. diff --git a/opensearch/testcontainers/opensearch/__init__.py b/opensearch/testcontainers/opensearch/__init__.py index 5192a2bfa..c307f69f9 100644 --- a/opensearch/testcontainers/opensearch/__init__.py +++ b/opensearch/testcontainers/opensearch/__init__.py @@ -7,25 +7,24 @@ class OpenSearchContainer(DockerContainer): """ - The following example demonstrates how to create a new index in an OpenSearch container - and add a document to it. It also shows how to search within the created index. The refresh - step in between makes sure that the newly created document is available for search. + The following example demonstrates how to create a new index in an OpenSearch container and add + a document to it. It also shows how to search within the created index. The refresh step in + between makes sure that the newly created document is available for search. - The method :code:`get_client` can be used to create a OpenSearch Python Client. - The method :code:`get_config` can be used to retrieve the host, port, user - and password of the container. + The method :code:`get_client` can be used to create a OpenSearch Python Client. The method + :code:`get_config` can be used to retrieve the host, port, user, and password of the container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.opensearch import OpenSearchContainer + .. doctest:: - >>> with OpenSearchContainer() as opensearch: - ... client = opensearch.get_client() - ... creation_result = client.index(index="test", body={"test": "test"}) - ... refresh_result = client.indices.refresh(index="test") - ... search_result = client.search(index="test", body={"query": {"match_all": {}}}) + >>> from testcontainers.opensearch import OpenSearchContainer + + >>> with OpenSearchContainer() as opensearch: + ... client = opensearch.get_client() + ... creation_result = client.index(index="test", body={"test": "test"}) + ... refresh_result = client.indices.refresh(index="test") + ... search_result = client.search(index="test", body={"query": {"match_all": {}}}) """ def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", diff --git a/oracle/testcontainers/oracle/__init__.py b/oracle/testcontainers/oracle/__init__.py index 989a20b75..9e28bc294 100644 --- a/oracle/testcontainers/oracle/__init__.py +++ b/oracle/testcontainers/oracle/__init__.py @@ -5,16 +5,16 @@ class OracleDbContainer(DbContainer): """ Oracle database container. - Example - ------- - .. code-block:: + Example: - >>> import sqlalchemy - >>> from testcontainers.oracle import OracleDbContainer + .. code-block:: - >>> with OracleDbContainer() as oracle: - ... e = sqlalchemy.create_engine(oracle.get_connection_url()) - ... result = e.execute("select * from V$VERSION") + >>> import sqlalchemy + >>> from testcontainers.oracle import OracleDbContainer + + >>> with OracleDbContainer() as oracle: + ... e = sqlalchemy.create_engine(oracle.get_connection_url()) + ... result = e.execute("select * from V$VERSION") """ def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) -> None: diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index a8a99f2e5..daad94ab3 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -19,21 +19,23 @@ class PostgresContainer(DbContainer): """ Postgres database container. - Example - ------- - The example spins up a Postgres database and connects to it using the :code:`psycopg` driver. - .. doctest:: + Example: - >>> from testcontainers.postgres import PostgresContainer - >>> import sqlalchemy + The example spins up a Postgres database and connects to it using the :code:`psycopg` + driver. - >>> postgres_container = PostgresContainer("postgres:9.5") - >>> with postgres_container as postgres: - ... e = sqlalchemy.create_engine(postgres.get_connection_url()) - ... result = e.execute("select version()") - ... version, = result.fetchone() - >>> version - 'PostgreSQL 9.5...' + .. doctest:: + + >>> from testcontainers.postgres import PostgresContainer + >>> import sqlalchemy + + >>> postgres_container = PostgresContainer("postgres:9.5") + >>> with postgres_container as postgres: + ... e = sqlalchemy.create_engine(postgres.get_connection_url()) + ... result = e.execute("select version()") + ... version, = result.fetchone() + >>> version + 'PostgreSQL 9.5...' """ POSTGRES_USER = os.environ.get("POSTGRES_USER", "test") POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "test") diff --git a/rabbitmq/testcontainers/rabbitmq/__init__.py b/rabbitmq/testcontainers/rabbitmq/__init__.py index 144f1de80..905fd95e5 100644 --- a/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -9,18 +9,19 @@ class RabbitMqContainer(DockerContainer): """ Test container for RabbitMQ. The example below spins up a RabbitMQ broker and uses the - `pika` client library (https://pypi.org/project/pika/) to establish a connection to the broker. + `pika client library <(https://pypi.org/project/pika/)>`__ to establish a connection to the + broker. - Example - ------- - .. doctest:: + Example: - >>> import pika - >>> from testcontainers.rabbitmq import RabbitMqContainer + .. doctest:: - >>> with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq: - ... connection = pika.BlockingConnection(rabbitmq.get_connection_params()) - ... channel = connection.channel() + >>> import pika + >>> from testcontainers.rabbitmq import RabbitMqContainer + + >>> with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq: + ... connection = pika.BlockingConnection(rabbitmq.get_connection_params()) + ... channel = connection.channel() """ RABBITMQ_NODE_PORT = os.environ.get("RABBITMQ_NODE_PORT", 5672) diff --git a/redis/testcontainers/redis/__init__.py b/redis/testcontainers/redis/__init__.py index 92fdccec8..713dd5274 100644 --- a/redis/testcontainers/redis/__init__.py +++ b/redis/testcontainers/redis/__init__.py @@ -20,14 +20,14 @@ class RedisContainer(DockerContainer): """ Redis container. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.redis import RedisContainer + .. doctest:: - >>> with RedisContainer() as redis_container: - ... redis_client = redis_container.get_client() + >>> from testcontainers.redis import RedisContainer + + >>> with RedisContainer() as redis_container: + ... redis_client = redis_container.get_client() """ def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs) -> None: super(RedisContainer, self).__init__(image, **kwargs) diff --git a/selenium/testcontainers/selenium/__init__.py b/selenium/testcontainers/selenium/__init__.py index 58034185b..db201157a 100644 --- a/selenium/testcontainers/selenium/__init__.py +++ b/selenium/testcontainers/selenium/__init__.py @@ -38,17 +38,17 @@ class BrowserWebDriverContainer(DockerContainer): """ Selenium browser container for Chrome or Firefox. - Example - ------- - .. doctest:: + Example: - >>> from testcontainers.selenium import BrowserWebDriverContainer - >>> from selenium.webdriver import DesiredCapabilities + .. doctest:: - >>> with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as chrome: - ... webdriver = chrome.get_driver() + >>> from testcontainers.selenium import BrowserWebDriverContainer + >>> from selenium.webdriver import DesiredCapabilities - You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. + >>> with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as chrome: + ... webdriver = chrome.get_driver() + + You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ def __init__(self, capabilities: str, image: Optional[str] = None, **kwargs) -> None: From 7a9490a4b07c95a39f521881253bf46fd2b3807c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Mon, 16 Jan 2023 17:25:36 -0500 Subject: [PATCH 180/425] Add python requirements to readthedocs build. --- .readthedocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 0b37a92d6..5a80deec6 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -10,3 +10,7 @@ build: os: ubuntu-22.04 tools: python: "3.10" + +python: + install: + - requirements: requirements/3.10.txt From 5c39762c0b8ae70556adebced17e35c440e54ad7 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 27 Jan 2023 10:26:24 +0100 Subject: [PATCH 181/425] Neo4j: verify_connectivity for connection test The driver offers an API specifically crafted to test if it can successfully connect. This tends to exchange less data than a dummy query and hence should be slightly faster. See also https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.Driver.verify_connectivity --- neo4j/testcontainers/neo4j/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index 96b2b17ad..264c03da2 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -76,8 +76,7 @@ def _connect(self) -> None: with self.get_driver() as driver: # Drivers may or may not be lazy # force them to do a round trip to confirm neo4j is working - with driver.session() as session: - session.run("RETURN 1").single() + driver.verify_connectivity() def get_driver(self, **kwargs) -> Driver: return GraphDatabase.driver( From 0f9ad24f2c0df362ee15b81ce8d7d36b9f98e6e1 Mon Sep 17 00:00:00 2001 From: Marcos Sebastian Date: Mon, 30 Jan 2023 15:12:55 +0100 Subject: [PATCH 182/425] FIX: Added URLError to exceptions to wait for in elasticsearch --- elasticsearch/testcontainers/elasticsearch/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index 4c2fbbd27..b6436aa75 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -14,6 +14,7 @@ import re import urllib from typing import Dict +from urllib.error import URLError from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -83,7 +84,7 @@ def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs) -> None for key, value in _environment_by_version(major_version).items(): self.with_env(key, value) - @wait_container_is_ready() + @wait_container_is_ready(URLError) def _connect(self) -> None: res = urllib.request.urlopen(self.get_url()) if res.status != 200: From e9c2e0d5f521d16f50ee66afe0cf58d09f85a78e Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 11:04:46 -0500 Subject: [PATCH 183/425] Add workflow to add labels for triaging. --- .github/workflows/labels.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/labels.yml diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 000000000..e277d3ed0 --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,26 @@ +name: Automatically add or remove labels +on: + issue_comment: + types: + - created +jobs: + add-label: + if: github.event.actor.login != 'tillahoffmann' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add label + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: requires-attention + remove-label: + if: github.event.actor.login == 'tillahoffmann' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Remove label + uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: requires-attention From 264ae88ae703c50f260a202f74db76446fb26620 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 11:18:00 -0500 Subject: [PATCH 184/425] Wrap username check in double braces. --- .github/workflows/labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index e277d3ed0..81167d182 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -5,7 +5,7 @@ on: - created jobs: add-label: - if: github.event.actor.login != 'tillahoffmann' + if: ${{ github.event.actor.login != 'tillahoffmann' }} runs-on: ubuntu-latest permissions: issues: write @@ -15,7 +15,7 @@ jobs: with: labels: requires-attention remove-label: - if: github.event.actor.login == 'tillahoffmann' + if: ${{ github.event.actor.login == 'tillahoffmann' }} runs-on: ubuntu-latest permissions: issues: write From b75a6910a8071f456a2c61692f6cadce05c42ecc Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 11:20:30 -0500 Subject: [PATCH 185/425] Add job to show the login. --- .github/workflows/labels.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 81167d182..a679ff7e6 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -4,6 +4,11 @@ on: types: - created jobs: + show-login: + runs-on: ubuntu-latest + steps: + - name: Show login + run: echo ${{ github.event.actor.login }} add-label: if: ${{ github.event.actor.login != 'tillahoffmann' }} runs-on: ubuntu-latest From f236ac2018e7676d3f731cf31dac3abcc78ca516 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 11:56:28 -0500 Subject: [PATCH 186/425] Re-enable to reduce GitHub Action burden. --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 886962983..15d79b937 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,6 @@ on: jobs: build: strategy: - fail-fast: false matrix: python-version: - "3.7" From 44c0c91e0c1c5127e7052706ec56e8a5d908193c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 12:38:44 -0500 Subject: [PATCH 187/425] Show event metadata. --- .github/workflows/labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index a679ff7e6..f3f16df73 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -7,8 +7,8 @@ jobs: show-login: runs-on: ubuntu-latest steps: - - name: Show login - run: echo ${{ github.event.actor.login }} + - name: Show metadata + run: echo ${{ github.event }} add-label: if: ${{ github.event.actor.login != 'tillahoffmann' }} runs-on: ubuntu-latest From 919b517c3b642e0b90ec9c9fc7663c6496a5b344 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 13:04:10 -0500 Subject: [PATCH 188/425] Show metadata as JSON. --- .github/workflows/labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index f3f16df73..67747befd 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -4,11 +4,11 @@ on: types: - created jobs: - show-login: + show-metadata: runs-on: ubuntu-latest steps: - name: Show metadata - run: echo ${{ github.event }} + run: echo ${{ toJson(github.event) }} add-label: if: ${{ github.event.actor.login != 'tillahoffmann' }} runs-on: ubuntu-latest From 6c2be6916e9ba12438fcfac86ad38d30ae0a20f8 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 13:43:05 -0500 Subject: [PATCH 189/425] Fix metadata printing and event attribute names. --- .github/workflows/labels.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 67747befd..4c2ecbe7b 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -7,10 +7,13 @@ jobs: show-metadata: runs-on: ubuntu-latest steps: + # Use environment variable to print the metadata (cf. https://github.com/actions/runner/issues/1656#issuecomment-1030077729). - name: Show metadata - run: echo ${{ toJson(github.event) }} + run: echo $JSON + env: + JSON: ${{ toJson(github.event) }} add-label: - if: ${{ github.event.actor.login != 'tillahoffmann' }} + if: ${{ github.event.user.login != 'tillahoffmann' }} runs-on: ubuntu-latest permissions: issues: write @@ -20,7 +23,7 @@ jobs: with: labels: requires-attention remove-label: - if: ${{ github.event.actor.login == 'tillahoffmann' }} + if: ${{ github.event.user.login == 'tillahoffmann' }} runs-on: ubuntu-latest permissions: issues: write From 74ff6bddf5a8fb7eac843f84833b1160ee7c06d0 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 13:50:37 -0500 Subject: [PATCH 190/425] Fix github event attribute path. --- .github/workflows/labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 4c2ecbe7b..5ab0598a5 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -13,7 +13,7 @@ jobs: env: JSON: ${{ toJson(github.event) }} add-label: - if: ${{ github.event.user.login != 'tillahoffmann' }} + if: ${{ github.event.issue.user.login != 'tillahoffmann' }} runs-on: ubuntu-latest permissions: issues: write @@ -23,7 +23,7 @@ jobs: with: labels: requires-attention remove-label: - if: ${{ github.event.user.login == 'tillahoffmann' }} + if: ${{ github.event.issue.user.login == 'tillahoffmann' }} runs-on: ubuntu-latest permissions: issues: write From 104d6285bc6ab9b2582242e5e7ed4b0471511d83 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 14:00:03 -0500 Subject: [PATCH 191/425] Drop permission restriction for adding/removing labels. --- .github/workflows/labels.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 5ab0598a5..13f60502d 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -15,8 +15,6 @@ jobs: add-label: if: ${{ github.event.issue.user.login != 'tillahoffmann' }} runs-on: ubuntu-latest - permissions: - issues: write steps: - name: Add label uses: actions-ecosystem/action-add-labels@v1 @@ -25,8 +23,6 @@ jobs: remove-label: if: ${{ github.event.issue.user.login == 'tillahoffmann' }} runs-on: ubuntu-latest - permissions: - issues: write steps: - name: Remove label uses: actions-ecosystem/action-remove-labels@v1 From b042364d23c68bd62bf074b80385f090a8dd84bc Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 14:12:47 -0500 Subject: [PATCH 192/425] Fix github event attribute path. --- .github/workflows/labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 13f60502d..2ad1e66b6 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -13,7 +13,7 @@ jobs: env: JSON: ${{ toJson(github.event) }} add-label: - if: ${{ github.event.issue.user.login != 'tillahoffmann' }} + if: ${{ github.event.user.login != 'tillahoffmann' }} runs-on: ubuntu-latest steps: - name: Add label @@ -21,7 +21,7 @@ jobs: with: labels: requires-attention remove-label: - if: ${{ github.event.issue.user.login == 'tillahoffmann' }} + if: ${{ github.event.user.login == 'tillahoffmann' }} runs-on: ubuntu-latest steps: - name: Remove label From 93ce2ad14fa083d7c0583e6dc6aa986270c39447 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 14:17:05 -0500 Subject: [PATCH 193/425] Fix github event attribute path. --- .github/workflows/labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 2ad1e66b6..c6624e509 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -13,7 +13,7 @@ jobs: env: JSON: ${{ toJson(github.event) }} add-label: - if: ${{ github.event.user.login != 'tillahoffmann' }} + if: ${{ github.event.comment.user.login != 'tillahoffmann' }} runs-on: ubuntu-latest steps: - name: Add label @@ -21,7 +21,7 @@ jobs: with: labels: requires-attention remove-label: - if: ${{ github.event.user.login == 'tillahoffmann' }} + if: ${{ github.event.comment.user.login == 'tillahoffmann' }} runs-on: ubuntu-latest steps: - name: Remove label From 17de79923a56bf9b2ade84e2c9fee8180cc634e9 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 12:19:08 -0500 Subject: [PATCH 194/425] Update dependencies. --- requirements/3.10.txt | 82 +++++++++++++++++++++++-------------------- requirements/3.7.txt | 74 ++++++++++++++++++++------------------ requirements/3.8.txt | 79 +++++++++++++++++++++-------------------- requirements/3.9.txt | 82 +++++++++++++++++++++++-------------------- 4 files changed, 166 insertions(+), 151 deletions(-) diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 6ab7cfa76..85903304c 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -73,7 +73,7 @@ # via -r requirements.in -e file:selenium # via -r requirements.in -alabaster==0.7.12 +alabaster==0.7.13 # via sphinx asn1crypto==1.5.1 # via scramp @@ -89,7 +89,7 @@ attrs==22.2.0 # outcome # pytest # trio -azure-core==1.26.2 +azure-core==1.26.3 # via # azure-storage-blob # msrest @@ -99,9 +99,9 @@ babel==2.11.0 # via sphinx bcrypt==4.0.1 # via paramiko -bleach==5.0.1 +bleach==6.0.0 # via readme-renderer -cachetools==5.2.0 +cachetools==5.3.0 # via google-auth certifi==2022.12.7 # via @@ -114,15 +114,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==2.1.1 +charset-normalizer==3.0.1 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -commonmark==0.9.1 - # via rich -coverage[toml]==7.0.3 +coverage[toml]==7.1.0 # via # codecov # pytest-cov @@ -136,7 +134,7 @@ cx-oracle==8.3.0 # via testcontainers-oracle distro==1.8.0 # via docker-compose -dnspython==2.2.1 +dnspython==2.3.0 # via pymongo docker[ssh]==6.0.1 # via @@ -164,18 +162,18 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.15.0 +google-auth==2.16.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.57.1 +googleapis-common-protos[grpc]==1.58.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.1 +greenlet==2.0.2 # via sqlalchemy -grpc-google-iam-v1==0.12.4 +grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub grpcio==1.51.1 # via @@ -197,7 +195,7 @@ importlib-metadata==6.0.0 # via # keyring # twine -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest isodate==0.6.1 # via msrest @@ -215,36 +213,40 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markupsafe==2.1.1 +markdown-it-py==2.1.0 + # via rich +markupsafe==2.1.2 # via jinja2 mccabe==0.6.1 # via flake8 -minio==7.1.12 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.13 # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.3.0 +neo4j==5.5.0 # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib -opensearch-py==2.0.1 +opensearch-py==2.1.1 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==22.0 +packaging==23.0 # via # docker # pytest # sphinx -paramiko==2.12.0 +paramiko==3.0.0 # via docker pg8000==1.29.4 # via -r requirements.in pika==1.3.1 # via testcontainers-rabbitmq -pkginfo==1.9.4 +pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest @@ -253,6 +255,7 @@ protobuf==3.20.3 # google-api-core # google-cloud-pubsub # googleapis-common-protos + # grpc-google-iam-v1 # grpcio-status psycopg2-binary==2.9.5 # via testcontainers-postgres @@ -288,23 +291,23 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.0 +pytest==7.2.1 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.4 +python-arango==7.5.6 # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 -python-dotenv==0.21.0 +python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.8.0 +python-keycloak==2.12.0 # via testcontainers-keycloak -pytz==2022.7 +pytz==2022.7.1 # via # babel # clickhouse-driver @@ -315,9 +318,9 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.4.0 +redis==4.5.1 # via testcontainers-redis -requests==2.28.1 +requests==2.28.2 # via # azure-core # codecov @@ -341,7 +344,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.1 +rich==13.3.1 # via twine rsa==4.9 # via @@ -351,7 +354,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.7.2 +selenium==4.8.0 # via testcontainers-selenium six==1.16.0 # via @@ -362,7 +365,6 @@ six==1.16.0 # google-auth # isodate # jsonschema - # paramiko # python-dateutil # websocket-client sniffio==1.3.0 @@ -371,13 +373,13 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.1 +sphinx==6.1.3 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -385,7 +387,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.46 +sqlalchemy==2.0.3 # via # testcontainers-mssql # testcontainers-mysql @@ -405,13 +407,15 @@ trio-websocket==0.9.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.4.0 - # via azure-core +typing-extensions==4.5.0 + # via + # azure-core + # sqlalchemy tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.13 +urllib3[socks]==1.26.14 # via # docker # minio @@ -433,7 +437,7 @@ wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.11.0 +zipp==3.13.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 49bd54617..18dd9d49a 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -73,7 +73,7 @@ # via -r requirements.in -e file:selenium # via -r requirements.in -alabaster==0.7.12 +alabaster==0.7.13 # via sphinx asn1crypto==1.5.1 # via scramp @@ -89,7 +89,7 @@ attrs==22.2.0 # outcome # pytest # trio -azure-core==1.26.2 +azure-core==1.26.3 # via # azure-storage-blob # msrest @@ -103,11 +103,11 @@ backports-zoneinfo==0.2.1 # tzlocal bcrypt==4.0.1 # via paramiko -bleach==5.0.1 +bleach==6.0.0 # via readme-renderer cached-property==1.5.2 # via docker-compose -cachetools==5.2.0 +cachetools==5.3.0 # via google-auth certifi==2022.12.7 # via @@ -120,15 +120,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==2.1.1 +charset-normalizer==3.0.1 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -commonmark==0.9.1 - # via rich -coverage[toml]==7.0.3 +coverage[toml]==7.1.0 # via # codecov # pytest-cov @@ -142,7 +140,7 @@ cx-oracle==8.3.0 # via testcontainers-oracle distro==1.8.0 # via docker-compose -dnspython==2.2.1 +dnspython==2.3.0 # via pymongo docker[ssh]==6.0.1 # via @@ -170,18 +168,18 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.15.0 +google-auth==2.16.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.57.1 +googleapis-common-protos[grpc]==1.58.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.1 +greenlet==2.0.2 # via sqlalchemy -grpc-google-iam-v1==0.12.4 +grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub grpcio==1.51.1 # via @@ -213,7 +211,7 @@ importlib-metadata==6.0.0 # twine importlib-resources==5.10.2 # via keyring -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest isodate==0.6.1 # via msrest @@ -231,36 +229,40 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markupsafe==2.1.1 +markdown-it-py==2.1.0 + # via rich +markupsafe==2.1.2 # via jinja2 mccabe==0.6.1 # via flake8 -minio==7.1.12 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.13 # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.3.0 +neo4j==5.5.0 # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib -opensearch-py==2.0.1 +opensearch-py==2.1.1 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==22.0 +packaging==23.0 # via # docker # pytest # sphinx -paramiko==2.12.0 +paramiko==3.0.0 # via docker pg8000==1.29.4 # via -r requirements.in pika==1.3.1 # via testcontainers-rabbitmq -pkginfo==1.9.4 +pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest @@ -269,6 +271,7 @@ protobuf==3.20.3 # google-api-core # google-cloud-pubsub # googleapis-common-protos + # grpc-google-iam-v1 # grpcio-status psycopg2-binary==2.9.5 # via testcontainers-postgres @@ -304,23 +307,23 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.0 +pytest==7.2.1 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.4 +python-arango==7.5.6 # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 -python-dotenv==0.21.0 +python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.8.0 +python-keycloak==2.12.0 # via testcontainers-keycloak -pytz==2022.7 +pytz==2022.7.1 # via # babel # clickhouse-driver @@ -331,9 +334,9 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.4.0 +redis==4.5.1 # via testcontainers-redis -requests==2.28.1 +requests==2.28.2 # via # azure-core # codecov @@ -357,7 +360,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.1 +rich==13.3.1 # via twine rsa==4.9 # via @@ -367,7 +370,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.7.2 +selenium==4.8.0 # via testcontainers-selenium six==1.16.0 # via @@ -378,7 +381,6 @@ six==1.16.0 # google-auth # isodate # jsonschema - # paramiko # python-dateutil # websocket-client sniffio==1.3.0 @@ -401,7 +403,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.46 +sqlalchemy==2.0.3 # via # testcontainers-mssql # testcontainers-mysql @@ -421,19 +423,21 @@ trio-websocket==0.9.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.4.0 +typing-extensions==4.5.0 # via # async-timeout # azure-core # h11 # importlib-metadata + # markdown-it-py # redis # rich + # sqlalchemy tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.13 +urllib3[socks]==1.26.14 # via # docker # minio @@ -455,7 +459,7 @@ wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.11.0 +zipp==3.13.0 # via # importlib-metadata # importlib-resources diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 66b45f7a0..79810f5ae 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -73,7 +73,7 @@ # via -r requirements.in -e file:selenium # via -r requirements.in -alabaster==0.7.12 +alabaster==0.7.13 # via sphinx asn1crypto==1.5.1 # via scramp @@ -89,7 +89,7 @@ attrs==22.2.0 # outcome # pytest # trio -azure-core==1.26.2 +azure-core==1.26.3 # via # azure-storage-blob # msrest @@ -103,9 +103,9 @@ backports-zoneinfo==0.2.1 # tzlocal bcrypt==4.0.1 # via paramiko -bleach==5.0.1 +bleach==6.0.0 # via readme-renderer -cachetools==5.2.0 +cachetools==5.3.0 # via google-auth certifi==2022.12.7 # via @@ -118,15 +118,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==2.1.1 +charset-normalizer==3.0.1 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -commonmark==0.9.1 - # via rich -coverage[toml]==7.0.3 +coverage[toml]==7.1.0 # via # codecov # pytest-cov @@ -140,7 +138,7 @@ cx-oracle==8.3.0 # via testcontainers-oracle distro==1.8.0 # via docker-compose -dnspython==2.2.1 +dnspython==2.3.0 # via pymongo docker[ssh]==6.0.1 # via @@ -168,18 +166,18 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.15.0 +google-auth==2.16.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.57.1 +googleapis-common-protos[grpc]==1.58.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.1 +greenlet==2.0.2 # via sqlalchemy -grpc-google-iam-v1==0.12.4 +grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub grpcio==1.51.1 # via @@ -204,7 +202,7 @@ importlib-metadata==6.0.0 # twine importlib-resources==5.10.2 # via keyring -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest isodate==0.6.1 # via msrest @@ -222,36 +220,40 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markupsafe==2.1.1 +markdown-it-py==2.1.0 + # via rich +markupsafe==2.1.2 # via jinja2 mccabe==0.6.1 # via flake8 -minio==7.1.12 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.13 # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.3.0 +neo4j==5.5.0 # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib -opensearch-py==2.0.1 +opensearch-py==2.1.1 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==22.0 +packaging==23.0 # via # docker # pytest # sphinx -paramiko==2.12.0 +paramiko==3.0.0 # via docker pg8000==1.29.4 # via -r requirements.in pika==1.3.1 # via testcontainers-rabbitmq -pkginfo==1.9.4 +pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest @@ -260,6 +262,7 @@ protobuf==3.20.3 # google-api-core # google-cloud-pubsub # googleapis-common-protos + # grpc-google-iam-v1 # grpcio-status psycopg2-binary==2.9.5 # via testcontainers-postgres @@ -295,23 +298,23 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.0 +pytest==7.2.1 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.4 +python-arango==7.5.6 # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 -python-dotenv==0.21.0 +python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.8.0 +python-keycloak==2.12.0 # via testcontainers-keycloak -pytz==2022.7 +pytz==2022.7.1 # via # babel # clickhouse-driver @@ -322,9 +325,9 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.4.0 +redis==4.5.1 # via testcontainers-redis -requests==2.28.1 +requests==2.28.2 # via # azure-core # codecov @@ -348,7 +351,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.1 +rich==13.3.1 # via twine rsa==4.9 # via @@ -358,7 +361,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.7.2 +selenium==4.8.0 # via testcontainers-selenium six==1.16.0 # via @@ -369,7 +372,6 @@ six==1.16.0 # google-auth # isodate # jsonschema - # paramiko # python-dateutil # websocket-client sniffio==1.3.0 @@ -378,13 +380,13 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.1 +sphinx==6.1.3 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -392,7 +394,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.46 +sqlalchemy==2.0.3 # via # testcontainers-mssql # testcontainers-mysql @@ -412,15 +414,16 @@ trio-websocket==0.9.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.4.0 +typing-extensions==4.5.0 # via # azure-core # rich + # sqlalchemy tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.13 +urllib3[socks]==1.26.14 # via # docker # minio @@ -442,7 +445,7 @@ wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.11.0 +zipp==3.13.0 # via # importlib-metadata # importlib-resources diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 0388c44c9..7da5249fb 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -73,7 +73,7 @@ # via -r requirements.in -e file:selenium # via -r requirements.in -alabaster==0.7.12 +alabaster==0.7.13 # via sphinx asn1crypto==1.5.1 # via scramp @@ -89,7 +89,7 @@ attrs==22.2.0 # outcome # pytest # trio -azure-core==1.26.2 +azure-core==1.26.3 # via # azure-storage-blob # msrest @@ -99,9 +99,9 @@ babel==2.11.0 # via sphinx bcrypt==4.0.1 # via paramiko -bleach==5.0.1 +bleach==6.0.0 # via readme-renderer -cachetools==5.2.0 +cachetools==5.3.0 # via google-auth certifi==2022.12.7 # via @@ -114,15 +114,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==2.1.1 +charset-normalizer==3.0.1 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -commonmark==0.9.1 - # via rich -coverage[toml]==7.0.3 +coverage[toml]==7.1.0 # via # codecov # pytest-cov @@ -136,7 +134,7 @@ cx-oracle==8.3.0 # via testcontainers-oracle distro==1.8.0 # via docker-compose -dnspython==2.2.1 +dnspython==2.3.0 # via pymongo docker[ssh]==6.0.1 # via @@ -164,18 +162,18 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.15.0 +google-auth==2.16.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.57.1 +googleapis-common-protos[grpc]==1.58.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.1 +greenlet==2.0.2 # via sqlalchemy -grpc-google-iam-v1==0.12.4 +grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub grpcio==1.51.1 # via @@ -198,7 +196,7 @@ importlib-metadata==6.0.0 # keyring # sphinx # twine -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest isodate==0.6.1 # via msrest @@ -216,36 +214,40 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markupsafe==2.1.1 +markdown-it-py==2.1.0 + # via rich +markupsafe==2.1.2 # via jinja2 mccabe==0.6.1 # via flake8 -minio==7.1.12 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.13 # via testcontainers-minio more-itertools==9.0.0 # via jaraco-classes msrest==0.7.1 # via azure-storage-blob -neo4j==5.3.0 +neo4j==5.5.0 # via testcontainers-neo4j oauthlib==3.2.2 # via requests-oauthlib -opensearch-py==2.0.1 +opensearch-py==2.1.1 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==22.0 +packaging==23.0 # via # docker # pytest # sphinx -paramiko==2.12.0 +paramiko==3.0.0 # via docker pg8000==1.29.4 # via -r requirements.in pika==1.3.1 # via testcontainers-rabbitmq -pkginfo==1.9.4 +pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest @@ -254,6 +256,7 @@ protobuf==3.20.3 # google-api-core # google-cloud-pubsub # googleapis-common-protos + # grpc-google-iam-v1 # grpcio-status psycopg2-binary==2.9.5 # via testcontainers-postgres @@ -289,23 +292,23 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.0 +pytest==7.2.1 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.4 +python-arango==7.5.6 # via testcontainers-arangodb python-dateutil==2.8.2 # via pg8000 -python-dotenv==0.21.0 +python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.8.0 +python-keycloak==2.12.0 # via testcontainers-keycloak -pytz==2022.7 +pytz==2022.7.1 # via # babel # clickhouse-driver @@ -316,9 +319,9 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.4.0 +redis==4.5.1 # via testcontainers-redis -requests==2.28.1 +requests==2.28.2 # via # azure-core # codecov @@ -342,7 +345,7 @@ requests-toolbelt==0.9.1 # twine rfc3986==2.0.0 # via twine -rich==13.0.1 +rich==13.3.1 # via twine rsa==4.9 # via @@ -352,7 +355,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.7.2 +selenium==4.8.0 # via testcontainers-selenium six==1.16.0 # via @@ -363,7 +366,6 @@ six==1.16.0 # google-auth # isodate # jsonschema - # paramiko # python-dateutil # websocket-client sniffio==1.3.0 @@ -372,13 +374,13 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.1 +sphinx==6.1.3 # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -386,7 +388,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==1.4.46 +sqlalchemy==2.0.3 # via # testcontainers-mssql # testcontainers-mysql @@ -406,13 +408,15 @@ trio-websocket==0.9.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.4.0 - # via azure-core +typing-extensions==4.5.0 + # via + # azure-core + # sqlalchemy tzdata==2022.7 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver -urllib3[socks]==1.26.13 +urllib3[socks]==1.26.14 # via # docker # minio @@ -434,7 +438,7 @@ wrapt==1.14.1 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.11.0 +zipp==3.13.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: From 4c5c3e09b276db04881651280397dd21b9e02fec Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 14:32:42 -0500 Subject: [PATCH 195/425] Remove deprecated sqlalchemy `engine.execute`. https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-to-2-0-step-two-turn-on-removedin20warnings --- mssql/testcontainers/mssql/__init__.py | 5 +++-- mssql/tests/test_mssql.py | 18 ++++++++++-------- mysql/testcontainers/mysql/__init__.py | 7 ++++--- mysql/tests/test_mysql.py | 18 ++++++++++-------- oracle/testcontainers/oracle/__init__.py | 5 +++-- oracle/tests/test_oracle.py | 17 +++++++++-------- postgres/testcontainers/postgres/__init__.py | 7 ++++--- postgres/tests/test_postgres.py | 14 ++++++++------ 8 files changed, 51 insertions(+), 40 deletions(-) diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index 678d12891..a639f6a09 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -15,8 +15,9 @@ class SqlServerContainer(DbContainer): >>> from testcontainers.mssql import SqlServerContainer >>> with SqlServerContainer() as mssql: - ... e = sqlalchemy.create_engine(mssql.get_connection_url()) - ... result = e.execute("select @@VERSION") + ... engine = sqlalchemy.create_engine(mssql.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute("select @@VERSION") """ def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", user: str = "SA", diff --git a/mssql/tests/test_mssql.py b/mssql/tests/test_mssql.py index 63b0a0135..c45b9191d 100644 --- a/mssql/tests/test_mssql.py +++ b/mssql/tests/test_mssql.py @@ -6,13 +6,15 @@ def test_docker_run_mssql(): image = 'mcr.microsoft.com/azure-sql-edge' dialect = 'mssql+pymssql' with SqlServerContainer(image, dialect=dialect) as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute('select @@servicename') - for row in result: - assert row[0] == 'MSSQLSERVER' + engine = sqlalchemy.create_engine(mssql.get_connection_url()) + with engine.begin() as connection: + result = connection.execute('select @@servicename') + for row in result: + assert row[0] == 'MSSQLSERVER' with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: - e = sqlalchemy.create_engine(mssql.get_connection_url()) - result = e.execute('select @@servicename') - for row in result: - assert row[0] == 'MSSQLSERVER' + engine = sqlalchemy.create_engine(mssql.get_connection_url()) + with engine.begin() as connection: + result = connection.execute('select @@servicename') + for row in result: + assert row[0] == 'MSSQLSERVER' diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index ecc5f68fe..3d71ec273 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -32,9 +32,10 @@ class MySqlContainer(DbContainer): >>> from testcontainers.mysql import MySqlContainer >>> with MySqlContainer('mysql:5.7.17') as mysql: - ... e = sqlalchemy.create_engine(mysql.get_connection_url()) - ... result = e.execute("select version()") - ... version, = result.fetchone() + ... engine = sqlalchemy.create_engine(mysql.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute("select version()") + ... version, = result.fetchone() """ def __init__(self, image: str = "mysql:latest", MYSQL_USER: Optional[str] = None, diff --git a/mysql/tests/test_mysql.py b/mysql/tests/test_mysql.py index cf4032594..39a9c4e96 100644 --- a/mysql/tests/test_mysql.py +++ b/mysql/tests/test_mysql.py @@ -10,18 +10,20 @@ def test_docker_run_mysql(): config = MySqlContainer('mysql:5.7.17') with config as mysql: - e = sqlalchemy.create_engine(mysql.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].startswith('5.7.17') + engine = sqlalchemy.create_engine(mysql.get_connection_url()) + with engine.begin() as connection: + result = connection.execute("select version()") + for row in result: + assert row[0].startswith('5.7.17') def test_docker_run_mariadb(): with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: - e = sqlalchemy.create_engine(mariadb.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].startswith('10.6.5') + engine = sqlalchemy.create_engine(mariadb.get_connection_url()) + with engine.begin() as connection: + result = connection.execute("select version()") + for row in result: + assert row[0].startswith('10.6.5') def test_docker_env_variables(): diff --git a/oracle/testcontainers/oracle/__init__.py b/oracle/testcontainers/oracle/__init__.py index 9e28bc294..c7ce2f308 100644 --- a/oracle/testcontainers/oracle/__init__.py +++ b/oracle/testcontainers/oracle/__init__.py @@ -13,8 +13,9 @@ class OracleDbContainer(DbContainer): >>> from testcontainers.oracle import OracleDbContainer >>> with OracleDbContainer() as oracle: - ... e = sqlalchemy.create_engine(oracle.get_connection_url()) - ... result = e.execute("select * from V$VERSION") + ... engine = sqlalchemy.create_engine(oracle.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute("select * from V$VERSION") """ def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) -> None: diff --git a/oracle/tests/test_oracle.py b/oracle/tests/test_oracle.py index 495de3c0d..a2151164b 100644 --- a/oracle/tests/test_oracle.py +++ b/oracle/tests/test_oracle.py @@ -5,12 +5,13 @@ @pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") def test_docker_run_oracle(): + versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', + 'PL/SQL Release 11.2.0.2.0 - Production', + 'CORE\t11.2.0.2.0\tProduction', + 'TNS for Linux: Version 11.2.0.2.0 - Production', + 'NLSRTL Version 11.2.0.2.0 - Production'} with OracleDbContainer() as oracledb: - e = sqlalchemy.create_engine(oracledb.get_connection_url()) - result = e.execute("select * from V$VERSION") - versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', - 'PL/SQL Release 11.2.0.2.0 - Production', - 'CORE\t11.2.0.2.0\tProduction', - 'TNS for Linux: Version 11.2.0.2.0 - Production', - 'NLSRTL Version 11.2.0.2.0 - Production'} - assert {row[0] for row in result} == versions + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + result = connection.execute("select * from V$VERSION") + assert {row[0] for row in result} == versions diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index daad94ab3..2e301814f 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -31,9 +31,10 @@ class PostgresContainer(DbContainer): >>> postgres_container = PostgresContainer("postgres:9.5") >>> with postgres_container as postgres: - ... e = sqlalchemy.create_engine(postgres.get_connection_url()) - ... result = e.execute("select version()") - ... version, = result.fetchone() + ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute("select version()") + ... version, = result.fetchone() >>> version 'PostgreSQL 9.5...' """ diff --git a/postgres/tests/test_postgres.py b/postgres/tests/test_postgres.py index 7bad16f84..b85686733 100644 --- a/postgres/tests/test_postgres.py +++ b/postgres/tests/test_postgres.py @@ -5,14 +5,16 @@ def test_docker_run_postgres(): postgres_container = PostgresContainer("postgres:9.5") with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - result = e.execute("select version()") - for row in result: - assert row[0].lower().startswith("postgresql 9.5") + engine = sqlalchemy.create_engine(postgres.get_connection_url()) + with engine.begin() as connection: + result = connection.execute("select version()") + for row in result: + assert row[0].lower().startswith("postgresql 9.5") def test_docker_run_postgres_with_driver_pg8000(): postgres_container = PostgresContainer("postgres:9.5", driver="pg8000") with postgres_container as postgres: - e = sqlalchemy.create_engine(postgres.get_connection_url()) - e.execute("select 1=1") + engine = sqlalchemy.create_engine(postgres.get_connection_url()) + with engine.begin() as connection: + connection.execute("select 1=1") From ecd21e9ec4f160f0bc7cf39392d9806b80cdef52 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 14:40:39 -0500 Subject: [PATCH 196/425] Wrap query strings in `sqlalchemy.text`. --- mssql/testcontainers/mssql/__init__.py | 2 +- mssql/tests/test_mssql.py | 4 ++-- mysql/testcontainers/mysql/__init__.py | 2 +- mysql/tests/test_mysql.py | 4 ++-- oracle/testcontainers/oracle/__init__.py | 2 +- oracle/tests/test_oracle.py | 2 +- postgres/testcontainers/postgres/__init__.py | 2 +- postgres/tests/test_postgres.py | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index a639f6a09..efd8ab1bc 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -17,7 +17,7 @@ class SqlServerContainer(DbContainer): >>> with SqlServerContainer() as mssql: ... engine = sqlalchemy.create_engine(mssql.get_connection_url()) ... with engine.begin() as connection: - ... result = connection.execute("select @@VERSION") + ... result = connection.execute(sqlalchemy.text("select @@VERSION")) """ def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", user: str = "SA", diff --git a/mssql/tests/test_mssql.py b/mssql/tests/test_mssql.py index c45b9191d..b615f1fff 100644 --- a/mssql/tests/test_mssql.py +++ b/mssql/tests/test_mssql.py @@ -8,13 +8,13 @@ def test_docker_run_mssql(): with SqlServerContainer(image, dialect=dialect) as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) with engine.begin() as connection: - result = connection.execute('select @@servicename') + result = connection.execute(sqlalchemy.text('select @@servicename')) for row in result: assert row[0] == 'MSSQLSERVER' with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) with engine.begin() as connection: - result = connection.execute('select @@servicename') + result = connection.execute(sqlalchemy.text('select @@servicename')) for row in result: assert row[0] == 'MSSQLSERVER' diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index 3d71ec273..1b734678e 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -34,7 +34,7 @@ class MySqlContainer(DbContainer): >>> with MySqlContainer('mysql:5.7.17') as mysql: ... engine = sqlalchemy.create_engine(mysql.get_connection_url()) ... with engine.begin() as connection: - ... result = connection.execute("select version()") + ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() """ diff --git a/mysql/tests/test_mysql.py b/mysql/tests/test_mysql.py index 39a9c4e96..c4e48d1ff 100644 --- a/mysql/tests/test_mysql.py +++ b/mysql/tests/test_mysql.py @@ -12,7 +12,7 @@ def test_docker_run_mysql(): with config as mysql: engine = sqlalchemy.create_engine(mysql.get_connection_url()) with engine.begin() as connection: - result = connection.execute("select version()") + result = connection.execute(sqlalchemy.text("select version()")) for row in result: assert row[0].startswith('5.7.17') @@ -21,7 +21,7 @@ def test_docker_run_mariadb(): with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: engine = sqlalchemy.create_engine(mariadb.get_connection_url()) with engine.begin() as connection: - result = connection.execute("select version()") + result = connection.execute(sqlalchemy.text("select version()")) for row in result: assert row[0].startswith('10.6.5') diff --git a/oracle/testcontainers/oracle/__init__.py b/oracle/testcontainers/oracle/__init__.py index c7ce2f308..b82a9c00a 100644 --- a/oracle/testcontainers/oracle/__init__.py +++ b/oracle/testcontainers/oracle/__init__.py @@ -15,7 +15,7 @@ class OracleDbContainer(DbContainer): >>> with OracleDbContainer() as oracle: ... engine = sqlalchemy.create_engine(oracle.get_connection_url()) ... with engine.begin() as connection: - ... result = connection.execute("select * from V$VERSION") + ... result = connection.execute(sqlalchemy.text("select * from V$VERSION")) """ def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) -> None: diff --git a/oracle/tests/test_oracle.py b/oracle/tests/test_oracle.py index a2151164b..ccbcc4b69 100644 --- a/oracle/tests/test_oracle.py +++ b/oracle/tests/test_oracle.py @@ -13,5 +13,5 @@ def test_docker_run_oracle(): with OracleDbContainer() as oracledb: engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: - result = connection.execute("select * from V$VERSION") + result = connection.execute(sqlalchemy.text("select * from V$VERSION")) assert {row[0] for row in result} == versions diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index 2e301814f..fb8206952 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -33,7 +33,7 @@ class PostgresContainer(DbContainer): >>> with postgres_container as postgres: ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) ... with engine.begin() as connection: - ... result = connection.execute("select version()") + ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() >>> version 'PostgreSQL 9.5...' diff --git a/postgres/tests/test_postgres.py b/postgres/tests/test_postgres.py index b85686733..c00c1b3fe 100644 --- a/postgres/tests/test_postgres.py +++ b/postgres/tests/test_postgres.py @@ -7,7 +7,7 @@ def test_docker_run_postgres(): with postgres_container as postgres: engine = sqlalchemy.create_engine(postgres.get_connection_url()) with engine.begin() as connection: - result = connection.execute("select version()") + result = connection.execute(sqlalchemy.text("select version()")) for row in result: assert row[0].lower().startswith("postgresql 9.5") @@ -17,4 +17,4 @@ def test_docker_run_postgres_with_driver_pg8000(): with postgres_container as postgres: engine = sqlalchemy.create_engine(postgres.get_connection_url()) with engine.begin() as connection: - connection.execute("select 1=1") + connection.execute(sqlalchemy.text("select 1=1")) From 62cc2b24b9c6b95d9622e3377ecc65de8200a07a Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 16 Feb 2023 17:20:01 -0500 Subject: [PATCH 197/425] Add triage label to all issues and pull requests. --- .../workflows/{labels.yml => attention-label.yml} | 4 ++-- .github/workflows/triage-label.yml | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) rename .github/workflows/{labels.yml => attention-label.yml} (90%) create mode 100644 .github/workflows/triage-label.yml diff --git a/.github/workflows/labels.yml b/.github/workflows/attention-label.yml similarity index 90% rename from .github/workflows/labels.yml rename to .github/workflows/attention-label.yml index c6624e509..97d384443 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/attention-label.yml @@ -19,7 +19,7 @@ jobs: - name: Add label uses: actions-ecosystem/action-add-labels@v1 with: - labels: requires-attention + labels: '👀 requires attention' remove-label: if: ${{ github.event.comment.user.login == 'tillahoffmann' }} runs-on: ubuntu-latest @@ -27,4 +27,4 @@ jobs: - name: Remove label uses: actions-ecosystem/action-remove-labels@v1 with: - labels: requires-attention + labels: '👀 requires attention' diff --git a/.github/workflows/triage-label.yml b/.github/workflows/triage-label.yml new file mode 100644 index 000000000..6ca75ccb1 --- /dev/null +++ b/.github/workflows/triage-label.yml @@ -0,0 +1,13 @@ +name: Automatically add triage labels to new issues and pull requests +on: + issues: + types: + - opened +jobs: + add-label: + runs-on: ubuntu-latest + steps: + - name: Add label + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: '🔀 requires triage' From 5f5536af54ae5c4d2af4a18aa8305140fd6d44f1 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:16:21 -0500 Subject: [PATCH 198/425] Remove class-level variables from azurite. --- azurite/testcontainers/azurite/__init__.py | 63 ++++++++++------------ 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/azurite/testcontainers/azurite/__init__.py b/azurite/testcontainers/azurite/__init__.py index 37cada2f0..f31860578 100644 --- a/azurite/testcontainers/azurite/__init__.py +++ b/azurite/testcontainers/azurite/__init__.py @@ -39,18 +39,11 @@ class AzuriteContainer(DockerContainer): ... api_version="2019-12-12" ... ) """ - - _AZURITE_ACCOUNT_NAME = os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") - _AZURITE_ACCOUNT_KEY = os.environ.get("AZURITE_ACCOUNT_KEY", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDX" - "J1OUzFT50uSRZ6IFsuFq2UVErCz4I6" - "tq/K1SZFPTOtr/KBHBeksoGMGw==") - - _BLOB_SERVICE_PORT = 10_000 - _QUEUE_SERVICE_PORT = 10_001 - _TABLE_SERVICE_PORT = 10_002 - - def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest", - ports_to_expose: Optional[Iterable[int]] = None, **kwargs) -> None: + def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest", *, + ports_to_expose: Optional[Iterable[int]] = None, blob_service_port: int = 10_000, + queue_service_port: int = 10_001, table_service_port: int = 10_002, + account_name: Optional[str] = None, account_key: Optional[str] = None, **kwargs) \ + -> None: """ Constructs an AzuriteContainer. Args: @@ -59,41 +52,41 @@ def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest" **kwargs: Keyword arguments passed to super class. """ super().__init__(image=image, **kwargs) - - if ports_to_expose is None: - ports_to_expose = [ - self._BLOB_SERVICE_PORT, - self._QUEUE_SERVICE_PORT, - self._TABLE_SERVICE_PORT - ] - - if len(ports_to_expose) == 0: - raise ValueError("Expected a list with port numbers to expose") + self.account_name = account_name or os.environ.get( + "AZURITE_ACCOUNT_NAME", "devstoreaccount1") + self.account_key = account_key or os.environ.get( + "AZURITE_ACCOUNT_KEY", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/" + "K1SZFPTOtr/KBHBeksoGMGw==") + + self.blob_service_port = blob_service_port + self.queue_service_port = queue_service_port + self.table_service_port = table_service_port + if not ports_to_expose: + ports_to_expose = [blob_service_port, queue_service_port, table_service_port] self.with_exposed_ports(*ports_to_expose) - self.with_env("AZURITE_ACCOUNTS", - f"{self._AZURITE_ACCOUNT_NAME}:{self._AZURITE_ACCOUNT_KEY}") + self.with_env("AZURITE_ACCOUNTS", f"{self.account_name}:{self.account_key}") def get_connection_string(self) -> str: host_ip = self.get_container_host_ip() connection_string = f"DefaultEndpointsProtocol=http;" \ - f"AccountName={self._AZURITE_ACCOUNT_NAME};" \ - f"AccountKey={self._AZURITE_ACCOUNT_KEY};" + f"AccountName={self.account_name};" \ + f"AccountKey={self.account_key};" - if self._BLOB_SERVICE_PORT in self.ports: + if self.blob_service_port in self.ports: connection_string += f"BlobEndpoint=http://{host_ip}:" \ - f"{self.get_exposed_port(self._BLOB_SERVICE_PORT)}" \ - f"/{self._AZURITE_ACCOUNT_NAME};" + f"{self.get_exposed_port(self.blob_service_port)}" \ + f"/{self.account_name};" - if self._QUEUE_SERVICE_PORT in self.ports: + if self.queue_service_port in self.ports: connection_string += f"QueueEndpoint=http://{host_ip}:" \ - f"{self.get_exposed_port(self._QUEUE_SERVICE_PORT)}" \ - f"/{self._AZURITE_ACCOUNT_NAME};" + f"{self.get_exposed_port(self.queue_service_port)}" \ + f"/{self.account_name};" - if self._TABLE_SERVICE_PORT in self.ports: + if self.table_service_port in self.ports: connection_string += f"TableEndpoint=http://{host_ip}:" \ - f"{self.get_exposed_port(self._TABLE_SERVICE_PORT)}" \ - f"/{self._AZURITE_ACCOUNT_NAME};" + f"{self.get_exposed_port(self.table_service_port)}" \ + f"/{self.account_name};" return connection_string From 7a06617f09d2b376cc5533bf1132182652b03978 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:45:49 -0500 Subject: [PATCH 199/425] Precompute `TIMEOUT`. --- arangodb/testcontainers/arangodb/__init__.py | 4 ++-- core/testcontainers/core/config.py | 1 + core/testcontainers/core/waiting_utils.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/arangodb/testcontainers/arangodb/__init__.py b/arangodb/testcontainers/arangodb/__init__.py index af7156fb2..4cb714c8f 100644 --- a/arangodb/testcontainers/arangodb/__init__.py +++ b/arangodb/testcontainers/arangodb/__init__.py @@ -2,7 +2,7 @@ ArangoDB container support. """ from os import environ -from testcontainers.core.config import MAX_TRIES +from testcontainers.core.config import TIMEOUT from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_for_logs import typing @@ -81,4 +81,4 @@ def get_connection_url(self) -> str: return f"http://{self.get_container_host_ip()}:{port}" def _connect(self) -> None: - wait_for_logs(self, predicate="is ready for business", timeout=MAX_TRIES) + wait_for_logs(self, predicate="is ready for business", timeout=TIMEOUT) diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index b17d1f79d..e7673f755 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -2,3 +2,4 @@ MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120)) SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) +TIMEOUT = MAX_TRIES * SLEEP_TIME diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 8daaac879..17c9f471d 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -64,8 +64,8 @@ def wrapper(wrapped: Callable, instance: Any, args: Iterable, kwargs: Mapping) - time.sleep(config.SLEEP_TIME) exception = e raise TimeoutError( - f'Wait time ({config.MAX_TRIES * config.SLEEP_TIME}s) exceeded for {wrapped.__name__}' - f'(args: {args}, kwargs {kwargs}). Exception: {exception}' + f'Wait time ({config.TIMEOUT}s) exceeded for {wrapped.__name__}(args: {args}, kwargs ' + f'{kwargs}). Exception: {exception}' ) return wrapper From ad3aef74702eb224443558645a8770d6d02cfa24 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:46:09 -0500 Subject: [PATCH 200/425] Remove static variables from `clickhouse`. --- .../testcontainers/clickhouse/__init__.py | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index b6ffce609..518eab079 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -39,24 +39,21 @@ class ClickHouseContainer(DbContainer): ... client.execute("select 'working'") [('working',)] """ - - CLICKHOUSE_USER = os.environ.get("CLICKHOUSE_USER", "test") - CLICKHOUSE_PASSWORD = os.environ.get("CLICKHOUSE_PASSWORD", "test") - CLICKHOUSE_DB = os.environ.get("CLICKHOUSE_DB", "test") - def __init__( self, image: str = "clickhouse/clickhouse-server:latest", port: int = 9000, - user: Optional[str] = None, + username: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None + dbname: Optional[str] = None, + user: None = None, ) -> None: super().__init__(image=image) - - self.CLICKHOUSE_USER = user or self.CLICKHOUSE_USER - self.CLICKHOUSE_PASSWORD = password or self.CLICKHOUSE_PASSWORD - self.CLICKHOUSE_DB = dbname or self.CLICKHOUSE_DB + if user: + raise ValueError("use `username` instead") + self.username = username or os.environ.get("CLICKHOUSE_USER", "test") + self.password = password or os.environ.get("CLICKHOUSE_PASSWORD", "test") + self.dbname = dbname or self.os.environ.get("CLICKHOUSE_DB", "test") self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) @@ -66,16 +63,16 @@ def _connect(self) -> None: client.execute("SELECT version()") def _configure(self) -> None: - self.with_env("CLICKHOUSE_USER", self.CLICKHOUSE_USER) - self.with_env("CLICKHOUSE_PASSWORD", self.CLICKHOUSE_PASSWORD) - self.with_env("CLICKHOUSE_DB", self.CLICKHOUSE_DB) + self.with_env("CLICKHOUSE_USER", self.username) + self.with_env("CLICKHOUSE_PASSWORD", self.password) + self.with_env("CLICKHOUSE_DB", self.dbname) def get_connection_url(self, host: Optional[str] = None) -> str: return self._create_connection_url( dialect="clickhouse", - username=self.CLICKHOUSE_USER, - password=self.CLICKHOUSE_PASSWORD, - db_name=self.CLICKHOUSE_DB, + username=self.username, + password=self.password, + db_name=self.dbname, host=host, port=self.port_to_expose, ) From 355ac716f1a9d0a61159b53081230b2d062969ae Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:46:44 -0500 Subject: [PATCH 201/425] Remove static variables from `kafka`. --- kafka/testcontainers/kafka/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kafka/testcontainers/kafka/__init__.py b/kafka/testcontainers/kafka/__init__.py index b7cb914f2..fa0d490ce 100644 --- a/kafka/testcontainers/kafka/__init__.py +++ b/kafka/testcontainers/kafka/__init__.py @@ -23,10 +23,9 @@ class KafkaContainer(DockerContainer): >>> with KafkaContainer() as kafka: ... connection = kafka.get_bootstrap_server() """ - KAFKA_PORT = 9093 TC_START_SCRIPT = '/tc-start.sh' - def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: int = KAFKA_PORT, + def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: int = 9093, **kwargs) -> None: super(KafkaContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose From d35737635ef24dee6520dedfd96bb3478d660efa Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:47:15 -0500 Subject: [PATCH 202/425] Remove static variables from `keycloak`. --- keycloak/testcontainers/keycloak/__init__.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/keycloak/testcontainers/keycloak/__init__.py b/keycloak/testcontainers/keycloak/__init__.py index c73ffdf07..9ed91b212 100644 --- a/keycloak/testcontainers/keycloak/__init__.py +++ b/keycloak/testcontainers/keycloak/__init__.py @@ -17,6 +17,7 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready +from typing import Optional class KeycloakContainer(DockerContainer): @@ -32,17 +33,17 @@ class KeycloakContainer(DockerContainer): >>> with KeycloakContainer() as kc: ... keycloak = kc.get_client() """ - KEYCLOAK_USER = os.environ.get("KEYCLOAK_USER", "test") - KEYCLOAK_PASSWORD = os.environ.get("KEYCLOAK_PASSWORD", "test") - - def __init__(self, image="jboss/keycloak:latest") -> None: + def __init__(self, image="jboss/keycloak:latest", username: Optional[str] = None, + password: Optional[str] = None, port: int = 8080) -> None: super(KeycloakContainer, self).__init__(image=image) - self.port_to_expose = 8080 + self.username = username or os.environ.get("KEYCLOAK_USER", "test") + self.password = password or os.environ.get("KEYCLOAK_PASSWORD", "test") + self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) def _configure(self) -> None: - self.with_env("KEYCLOAK_USER", self.KEYCLOAK_USER) - self.with_env("KEYCLOAK_PASSWORD", self.KEYCLOAK_PASSWORD) + self.with_env("KEYCLOAK_USER", self.username) + self.with_env("KEYCLOAK_PASSWORD", self.password) def get_url(self) -> str: host = self.get_container_host_ip() @@ -64,8 +65,8 @@ def start(self) -> "KeycloakContainer": def get_client(self, **kwargs) -> KeycloakAdmin: default_kwargs = dict( server_url="{}/auth/".format(self.get_url()), - username=self.KEYCLOAK_USER, - password=self.KEYCLOAK_PASSWORD, + username=self.username, + password=self.password, realm_name="master", verify=True, ) From bfb820063553abe19ae38d3f5680bdaa67452a65 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:47:42 -0500 Subject: [PATCH 203/425] Remove static variables from `mongodb`. --- mongodb/testcontainers/mongodb/__init__.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/mongodb/testcontainers/mongodb/__init__.py b/mongodb/testcontainers/mongodb/__init__.py index 535dfffb9..a3ac9cf2b 100644 --- a/mongodb/testcontainers/mongodb/__init__.py +++ b/mongodb/testcontainers/mongodb/__init__.py @@ -14,6 +14,7 @@ from pymongo import MongoClient from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_container_is_ready +from typing import Optional class MongoDbContainer(DbContainer): @@ -46,26 +47,27 @@ class MongoDbContainer(DbContainer): ... # Find the restaurant document ... cursor = db.restaurants.find({"borough": "Manhattan"}) """ - MONGO_INITDB_ROOT_USERNAME = os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") - MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") - MONGO_DB = os.environ.get("MONGO_DB", "test") - - def __init__(self, image: str = "mongo:latest", port_to_expose: int = 27017, **kwargs) -> None: + def __init__(self, image: str = "mongo:latest", port_to_expose: int = 27017, + username: Optional[str] = None, password: Optional[str] = None, + dbname: Optional[str] = None, **kwargs) -> None: super(MongoDbContainer, self).__init__(image=image, **kwargs) + self.username = username or os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") + self.password = password or os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") + self.dbname = dbname or os.environ.get("MONGO_DB", "test") self.command = "mongo" self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) def _configure(self) -> None: - self.with_env("MONGO_INITDB_ROOT_USERNAME", self.MONGO_INITDB_ROOT_USERNAME) - self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.MONGO_INITDB_ROOT_PASSWORD) - self.with_env("MONGO_DB", self.MONGO_DB) + self.with_env("MONGO_INITDB_ROOT_USERNAME", self.username) + self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.password) + self.with_env("MONGO_DB", self.dbname) def get_connection_url(self) -> str: return self._create_connection_url( dialect='mongodb', - username=self.MONGO_INITDB_ROOT_USERNAME, - password=self.MONGO_INITDB_ROOT_PASSWORD, + username=self.username, + password=self.password, port=self.port_to_expose, ) From e072dbacf04b38aa0fe036e1bfa79e697fd9b8da Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:48:39 -0500 Subject: [PATCH 204/425] Remove static variables from `mssql`. --- mssql/testcontainers/mssql/__init__.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index efd8ab1bc..1e7d852b7 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -20,27 +20,30 @@ class SqlServerContainer(DbContainer): ... result = connection.execute(sqlalchemy.text("select @@VERSION")) """ - def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", user: str = "SA", - password: Optional[str] = None, port: int = 1433, dbname: str = "tempdb", - dialect: str = 'mssql+pymssql', **kwargs) -> None: + def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", + username: str = "SA", password: Optional[str] = None, port: int = 1433, + dbname: str = "tempdb", dialect: str = 'mssql+pymssql', user: None = None, + **kwargs) -> None: super(SqlServerContainer, self).__init__(image, **kwargs) + if user: + raise ValueError("use `username` instead") self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) - self.SQLSERVER_PASSWORD = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") - self.SQLSERVER_USER = user - self.SQLSERVER_DBNAME = dbname + self.password = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") + self.username = username + self.dbname = dbname self.dialect = dialect def _configure(self) -> None: - self.with_env("SA_PASSWORD", self.SQLSERVER_PASSWORD) - self.with_env("SQLSERVER_USER", self.SQLSERVER_USER) - self.with_env("SQLSERVER_DBNAME", self.SQLSERVER_DBNAME) + self.with_env("SA_PASSWORD", self.password) + self.with_env("SQLSERVER_USER", self.username) + self.with_env("SQLSERVER_DBNAME", self.dbname) self.with_env("ACCEPT_EULA", 'Y') def get_connection_url(self) -> str: return super()._create_connection_url( - dialect=self.dialect, username=self.SQLSERVER_USER, password=self.SQLSERVER_PASSWORD, - db_name=self.SQLSERVER_DBNAME, port=self.port_to_expose + dialect=self.dialect, username=self.username, password=self.password, + db_name=self.dbname, port=self.port_to_expose ) From 1d6e402ac20b115af334f43a1a4eebc46729f5ed Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:51:36 -0500 Subject: [PATCH 205/425] Remove static variables from `mysql`. --- mysql/testcontainers/mysql/__init__.py | 48 ++++++++++++++++---------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index 1b734678e..b5efe7675 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -37,32 +37,42 @@ class MySqlContainer(DbContainer): ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() """ - - def __init__(self, image: str = "mysql:latest", MYSQL_USER: Optional[str] = None, - MYSQL_ROOT_PASSWORD: Optional[str] = None, MYSQL_PASSWORD: Optional[str] = None, - MYSQL_DATABASE: Optional[str] = None, **kwargs) -> None: + def __init__(self, image: str = "mysql:latest", username: Optional[str] = None, + root_password: Optional[str] = None, password: Optional[str] = None, + dbname: Optional[str] = None, port: int = 3306, MYSQL_USER: None = None, + MYSQL_ROOT_PASSWORD: None = None, MYSQL_PASSWORD: None = None, + MYSQL_DATABASE: None = None, **kwargs) -> None: super(MySqlContainer, self).__init__(image, **kwargs) - self.port_to_expose = 3306 + if MYSQL_USER: + raise ValueError("use `username` instead of `MYSQL_USER`") + if MYSQL_ROOT_PASSWORD: + raise ValueError("use `root_password` instead of `MYSQL_ROOT_PASSWORD`") + if MYSQL_PASSWORD: + raise ValueError("use `password` instead of `MYSQL_PASSWORD`") + if MYSQL_DATABASE: + raise ValueError("use `dbname` instead of `MYSQL_DATABASE`") + + self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) - self.MYSQL_USER = MYSQL_USER or environ.get('MYSQL_USER', 'test') - self.MYSQL_ROOT_PASSWORD = MYSQL_ROOT_PASSWORD or environ.get('MYSQL_ROOT_PASSWORD', 'test') - self.MYSQL_PASSWORD = MYSQL_PASSWORD or environ.get('MYSQL_PASSWORD', 'test') - self.MYSQL_DATABASE = MYSQL_DATABASE or environ.get('MYSQL_DATABASE', 'test') + self.username = username or environ.get('MYSQL_USER', 'test') + self.root_password = root_password or environ.get('MYSQL_ROOT_PASSWORD', 'test') + self.password = password or environ.get('MYSQL_PASSWORD', 'test') + self.dbname = dbname or environ.get('MYSQL_DATABASE', 'test') - if self.MYSQL_USER == 'root': - self.MYSQL_ROOT_PASSWORD = self.MYSQL_PASSWORD + if self.username == 'root': + self.root_password = self.password def _configure(self) -> None: - self.with_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) - self.with_env("MYSQL_DATABASE", self.MYSQL_DATABASE) + self.with_env("MYSQL_ROOT_PASSWORD", self.root_password) + self.with_env("MYSQL_DATABASE", self.dbname) - if self.MYSQL_USER != "root": - self.with_env("MYSQL_USER", self.MYSQL_USER) - self.with_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) + if self.username != "root": + self.with_env("MYSQL_USER", self.username) + self.with_env("MYSQL_PASSWORD", self.password) def get_connection_url(self) -> str: return super()._create_connection_url(dialect="mysql+pymysql", - username=self.MYSQL_USER, - password=self.MYSQL_PASSWORD, - db_name=self.MYSQL_DATABASE, + username=self.username, + password=self.password, + db_name=self.dbname, port=self.port_to_expose) From e8e4078d611bd26ede06173b60fd465372aa4c53 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:51:54 -0500 Subject: [PATCH 206/425] Remove static variables from `neo4j`. --- neo4j/testcontainers/neo4j/__init__.py | 32 ++++++++------------------ 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index 264c03da2..e7402644f 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -15,8 +15,10 @@ from neo4j import Driver, GraphDatabase +from testcontainers.core.config import TIMEOUT from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs +from typing import Optional class Neo4jContainer(DbContainer): @@ -35,26 +37,17 @@ class Neo4jContainer(DbContainer): ... result = session.run("MATCH (n) RETURN n LIMIT 1") ... record = result.single() """ - - # The official image requires a change of password on startup. - NEO4J_ADMIN_PASSWORD = os.environ.get("NEO4J_ADMIN_PASSWORD", "password") - # Default port for the binary Bolt protocol. - DEFAULT_BOLT_PORT = 7687 - AUTH_FORMAT = "neo4j/{password}" - NEO4J_STARTUP_TIMEOUT_SECONDS = 10 - NEO4J_USER = "neo4j" - - def __init__(self, image: str = "neo4j:latest", **kwargs) -> None: + def __init__(self, image: str = "neo4j:latest", *, bolt_port: int = 7687, + password: Optional[str] = None, username: Optional[str] = None, **kwargs) -> None: super(Neo4jContainer, self).__init__(image, **kwargs) - self.bolt_port = Neo4jContainer.DEFAULT_BOLT_PORT + self.username = username or os.environ.get("NEO4J_USER", "password") + self.password = password or os.environ.get("NEO4J_PASSWORD", "password") + self.bolt_port = bolt_port self.with_exposed_ports(self.bolt_port) self._driver = None def _configure(self) -> None: - self.with_env( - "NEO4J_AUTH", - Neo4jContainer.AUTH_FORMAT.format(password=Neo4jContainer.NEO4J_ADMIN_PASSWORD) - ) + self.with_env("NEO4J_AUTH", f"neo4j/{self.password}") def get_connection_url(self) -> str: return "{dialect}://{host}:{port}".format( @@ -65,12 +58,7 @@ def get_connection_url(self) -> str: @wait_container_is_ready() def _connect(self) -> None: - # First we wait for Neo4j to say it's listening - wait_for_logs( - self, - "Remote interface available at", - Neo4jContainer.NEO4J_STARTUP_TIMEOUT_SECONDS, - ) + wait_for_logs(self, "Remote interface available at", TIMEOUT) # Then we actually check that the container really is listening with self.get_driver() as driver: @@ -81,6 +69,6 @@ def _connect(self) -> None: def get_driver(self, **kwargs) -> Driver: return GraphDatabase.driver( self.get_connection_url(), - auth=(Neo4jContainer.NEO4J_USER, Neo4jContainer.NEO4J_ADMIN_PASSWORD), + auth=(self.username, self.password), **kwargs ) From 043dea074453b72d8f660051dffe0938924eb522 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:52:35 -0500 Subject: [PATCH 207/425] Rename user to username in `opensearch`. --- opensearch/testcontainers/opensearch/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/opensearch/testcontainers/opensearch/__init__.py b/opensearch/testcontainers/opensearch/__init__.py index c307f69f9..e8e690520 100644 --- a/opensearch/testcontainers/opensearch/__init__.py +++ b/opensearch/testcontainers/opensearch/__init__.py @@ -12,7 +12,8 @@ class OpenSearchContainer(DockerContainer): between makes sure that the newly created document is available for search. The method :code:`get_client` can be used to create a OpenSearch Python Client. The method - :code:`get_config` can be used to retrieve the host, port, user, and password of the container. + :code:`get_config` can be used to retrieve the host, port, username, and password of the + container. Example: @@ -47,16 +48,16 @@ def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", def get_config(self) -> dict: """This method returns the configuration of the OpenSearch container, - including the host, port, user, and password. + including the host, port, username, and password. Returns: - dict: {`host`: str, `port`: str, `user`: str, `password`: str} + dict: {`host`: str, `port`: str, `username`: str, `password`: str} """ return { "host": self.get_container_host_ip(), "port": self.get_exposed_port(self.port_to_expose), - "user": "admin", + "username": "admin", "password": "admin", } @@ -75,7 +76,7 @@ def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: "port": config["port"], } ], - http_auth=(config["user"], config["password"]), + http_auth=(config["username"], config["password"]), use_ssl=self.security_enabled, verify_certs=verify_certs, **kwargs, From d407859091bacb27c00c1ed07d0e5a392954a7e1 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:53:19 -0500 Subject: [PATCH 208/425] Remove static variables from `postgres`. --- postgres/testcontainers/postgres/__init__.py | 29 ++++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index fb8206952..26a283680 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -38,30 +38,29 @@ class PostgresContainer(DbContainer): >>> version 'PostgreSQL 9.5...' """ - POSTGRES_USER = os.environ.get("POSTGRES_USER", "test") - POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "test") - POSTGRES_DB = os.environ.get("POSTGRES_DB", "test") - - def __init__(self, image: str = "postgres:latest", port: int = 5432, user: Optional[str] = None, - password: Optional[str] = None, dbname: Optional[str] = None, - driver: str = "psycopg2", **kwargs) -> None: + def __init__(self, image: str = "postgres:latest", port: int = 5432, + username: Optional[str] = None, password: Optional[str] = None, + dbname: Optional[str] = None, driver: str = "psycopg2", user: None = None, + **kwargs) -> None: + if user: + raise ValueError("use `username` instead of `user`") super(PostgresContainer, self).__init__(image=image, **kwargs) - self.POSTGRES_USER = user or self.POSTGRES_USER - self.POSTGRES_PASSWORD = password or self.POSTGRES_PASSWORD - self.POSTGRES_DB = dbname or self.POSTGRES_DB + self.username = username or os.environ.get("POSTGRES_USER", "test") + self.password = password or os.environ.get("POSTGRES_PASSWORD", "test") + self.dbname = dbname or os.environ.get("POSTGRES_DB", "test") self.port_to_expose = port self.driver = driver self.with_exposed_ports(self.port_to_expose) def _configure(self) -> None: - self.with_env("POSTGRES_USER", self.POSTGRES_USER) - self.with_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) - self.with_env("POSTGRES_DB", self.POSTGRES_DB) + self.with_env("POSTGRES_USER", self.username) + self.with_env("POSTGRES_PASSWORD", self.password) + self.with_env("POSTGRES_DB", self.dbname) def get_connection_url(self, host=None) -> str: return super()._create_connection_url( - dialect="postgresql+{}".format(self.driver), username=self.POSTGRES_USER, - password=self.POSTGRES_PASSWORD, db_name=self.POSTGRES_DB, host=host, + dialect="postgresql+{}".format(self.driver), username=self.username, + password=self.password, db_name=self.dbname, host=host, port=self.port_to_expose, ) From 23e93b2fb3e65b5d63dcaf591904f0f167d2de12 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:53:37 -0500 Subject: [PATCH 209/425] Remove static variables from `rabbitmq`. --- rabbitmq/testcontainers/rabbitmq/__init__.py | 24 ++++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/rabbitmq/testcontainers/rabbitmq/__init__.py b/rabbitmq/testcontainers/rabbitmq/__init__.py index 905fd95e5..ebdb96351 100644 --- a/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -23,11 +23,6 @@ class RabbitMqContainer(DockerContainer): ... connection = pika.BlockingConnection(rabbitmq.get_connection_params()) ... channel = connection.channel() """ - - RABBITMQ_NODE_PORT = os.environ.get("RABBITMQ_NODE_PORT", 5672) - RABBITMQ_DEFAULT_USER = os.environ.get("RABBITMQ_DEFAULT_USER", "guest") - RABBITMQ_DEFAULT_PASS = os.environ.get("RABBITMQ_DEFAULT_PASS", "guest") - def __init__(self, image: str = "rabbitmq:latest", port: Optional[int] = None, username: Optional[str] = None, password: Optional[str] = None, **kwargs) -> None: """Initialize the RabbitMQ test container. @@ -39,14 +34,14 @@ def __init__(self, image: str = "rabbitmq:latest", port: Optional[int] = None, password: RabbitMQ password. """ super(RabbitMqContainer, self).__init__(image=image, **kwargs) - self.RABBITMQ_NODE_PORT = port or int(self.RABBITMQ_NODE_PORT) - self.RABBITMQ_DEFAULT_USER = username or self.RABBITMQ_DEFAULT_USER - self.RABBITMQ_DEFAULT_PASS = password or self.RABBITMQ_DEFAULT_PASS + self.port = port or int(os.environ.get("RABBITMQ_NODE_PORT", 5672)) + self.username = username or os.environ.get("RABBITMQ_DEFAULT_USER", "guest") + self.password = password or os.environ.get("RABBITMQ_DEFAULT_PASS", "guest") - self.with_exposed_ports(self.RABBITMQ_NODE_PORT) - self.with_env("RABBITMQ_NODE_PORT", self.RABBITMQ_NODE_PORT) - self.with_env("RABBITMQ_DEFAULT_USER", self.RABBITMQ_DEFAULT_USER) - self.with_env("RABBITMQ_DEFAULT_PASS", self.RABBITMQ_DEFAULT_PASS) + self.with_exposed_ports(self.port) + self.with_env("RABBITMQ_NODE_PORT", self.port) + self.with_env("RABBITMQ_DEFAULT_USER", self.username) + self.with_env("RABBITMQ_DEFAULT_PASS", self.password) @wait_container_is_ready(pika.exceptions.IncompatibleProtocolError) def readiness_probe(self) -> bool: @@ -63,12 +58,11 @@ def get_connection_params(self) -> pika.ConnectionParameters: For more details see: https://pika.readthedocs.io/en/latest/modules/parameters.html """ - credentials = pika.PlainCredentials(username=self.RABBITMQ_DEFAULT_USER, - password=self.RABBITMQ_DEFAULT_PASS) + credentials = pika.PlainCredentials(username=self.username, password=self.password) return pika.ConnectionParameters( host=self.get_container_host_ip(), - port=self.get_exposed_port(self.RABBITMQ_NODE_PORT), + port=self.get_exposed_port(self.port), credentials=credentials, ) From 33014e47616157c11008560b0c5d4ee923fbe97e Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:57:11 -0500 Subject: [PATCH 210/425] Use f-strings throughout. --- compose/testcontainers/compose/__init__.py | 4 ++-- core/testcontainers/core/generic.py | 6 ++---- .../testcontainers/elasticsearch/__init__.py | 2 +- google/testcontainers/google/pubsub.py | 10 ++++------ kafka/testcontainers/kafka/__init__.py | 14 +++++++------- keycloak/testcontainers/keycloak/__init__.py | 6 +++--- localstack/testcontainers/localstack/__init__.py | 2 +- neo4j/testcontainers/neo4j/__init__.py | 6 +----- postgres/testcontainers/postgres/__init__.py | 2 +- 9 files changed, 22 insertions(+), 30 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 6f5f2109e..027dce281 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -36,13 +36,13 @@ class DockerCompose: host = compose.get_service_host("hub", 4444) port = compose.get_service_port("hub", 4444) driver = webdriver.Remote( - command_executor=("http://{}:{}/wd/hub".format(host,port)), + command_executor=(f"http://{host}:{port}/wd/hub"), desired_capabilities=CHROME, ) driver.get("http://automation-remarks.com") stdout, stderr = compose.get_logs() if stderr: - print("Errors\\n:{}".format(stderr)) + print(f"Errors\\n:{stderr}") .. code-block:: yaml diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index f295bc01a..6f6a1f3c2 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -44,11 +44,9 @@ def _create_connection_url(self, dialect: str, username: str, password: str, raise ContainerStartException("container has not been started") host = host or self.get_container_host_ip() port = self.get_exposed_port(port) - url = "{dialect}://{username}:{password}@{host}:{port}".format( - dialect=dialect, username=username, password=password, host=host, port=port - ) + url = f"{dialect}://{username}:{password}@{host}:{port}" if db_name: - url += '/' + db_name + url = f"{url}/{db_name}" return url def start(self) -> 'DbContainer': diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index b6436aa75..260154fc6 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -93,7 +93,7 @@ def _connect(self) -> None: def get_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) - return 'http://{}:{}'.format(host, port) + return f'http://{host}:{port}' def start(self) -> "ElasticSearchContainer": super().start() diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 8a75baa27..39a691368 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -42,14 +42,12 @@ def __init__(self, image: str = "google/cloud-sdk:emulators", project: str = "te self.project = project self.port = port self.with_exposed_ports(self.port) - self.with_command("gcloud beta emulators pubsub start --project=" - "{project} --host-port=0.0.0.0:{port}".format( - project=self.project, port=self.port, - )) + self.with_command( + f"gcloud beta emulators pubsub start --project={project} --host-port=0.0.0.0:{port}" + ) def get_pubsub_emulator_host(self) -> str: - return "{host}:{port}".format(host=self.get_container_host_ip(), - port=self.get_exposed_port(self.port)) + return f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" def _get_channel(self, channel: Optional[grpc.Channel] = None) -> grpc.Channel: if channel is None: diff --git a/kafka/testcontainers/kafka/__init__.py b/kafka/testcontainers/kafka/__init__.py index fa0d490ce..8f22af13f 100644 --- a/kafka/testcontainers/kafka/__init__.py +++ b/kafka/testcontainers/kafka/__init__.py @@ -30,7 +30,7 @@ def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: i super(KafkaContainer, self).__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(self.port_to_expose) - listeners = 'PLAINTEXT://0.0.0.0:{},BROKER://0.0.0.0:9092'.format(port_to_expose) + listeners = f'PLAINTEXT://0.0.0.0:{port_to_expose},BROKER://0.0.0.0:9092' self.with_env('KAFKA_LISTENERS', listeners) self.with_env('KAFKA_LISTENER_SECURITY_PROTOCOL_MAP', 'BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT') @@ -45,7 +45,7 @@ def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: i def get_bootstrap_server(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) - return '{}:{}'.format(host, port) + return f'{host}:{port}' @wait_container_is_ready(UnrecognizedBrokerVersion, NoBrokersAvailable, KafkaError, ValueError) def _connect(self) -> None: @@ -57,21 +57,21 @@ def _connect(self) -> None: def tc_start(self) -> None: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) - listeners = 'PLAINTEXT://{}:{},BROKER://$(hostname -i):9092'.format(host, port) + listeners = f'PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092' data = ( dedent( - """ + f""" #!/bin/bash echo 'clientPort=2181' > zookeeper.properties echo 'dataDir=/var/lib/zookeeper/data' >> zookeeper.properties echo 'dataLogDir=/var/lib/zookeeper/log' >> zookeeper.properties zookeeper-server-start zookeeper.properties & export KAFKA_ZOOKEEPER_CONNECT='localhost:2181' - export KAFKA_ADVERTISED_LISTENERS={} + export KAFKA_ADVERTISED_LISTENERS={listeners} . /etc/confluent/docker/bash-config /etc/confluent/docker/configure /etc/confluent/docker/launch - """.format(listeners) + """ ) .strip() .encode('utf-8') @@ -80,7 +80,7 @@ def tc_start(self) -> None: def start(self) -> "KafkaContainer": script = KafkaContainer.TC_START_SCRIPT - command = 'sh -c "while [ ! -f {} ]; do sleep 0.1; done; sh {}"'.format(script, script) + command = f'sh -c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' self.with_command(command) super().start() self.tc_start() diff --git a/keycloak/testcontainers/keycloak/__init__.py b/keycloak/testcontainers/keycloak/__init__.py index 9ed91b212..9787f0f2d 100644 --- a/keycloak/testcontainers/keycloak/__init__.py +++ b/keycloak/testcontainers/keycloak/__init__.py @@ -48,12 +48,12 @@ def _configure(self) -> None: def get_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port_to_expose) - return "http://{}:{}".format(host, port) + return f"http://{host}:{port}" @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) def _connect(self) -> None: url = self.get_url() - response = requests.get("{}/auth".format(url), timeout=1) + response = requests.get(f"{url}/auth", timeout=1) response.raise_for_status() def start(self) -> "KeycloakContainer": @@ -64,7 +64,7 @@ def start(self) -> "KeycloakContainer": def get_client(self, **kwargs) -> KeycloakAdmin: default_kwargs = dict( - server_url="{}/auth/".format(self.get_url()), + server_url=f"{self.get_url()}/auth/", username=self.username, password=self.password, realm_name="master", diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index 61c34490f..f733db9d9 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -62,7 +62,7 @@ def get_url(self) -> str: """ host = self.get_container_host_ip() port = self.get_exposed_port(self.edge_port) - return 'http://{}:{}'.format(host, port) + return f'http://{host}:{port}' def start(self, timeout: float = 60) -> "LocalStackContainer": super().start() diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index e7402644f..d85f9d8db 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -50,11 +50,7 @@ def _configure(self) -> None: self.with_env("NEO4J_AUTH", f"neo4j/{self.password}") def get_connection_url(self) -> str: - return "{dialect}://{host}:{port}".format( - dialect="bolt", - host=self.get_container_host_ip(), - port=self.get_exposed_port(self.bolt_port), - ) + return f"bolt://{self.get_container_host_ip()}:{self.get_exposed_port(self.bolt_port)}" @wait_container_is_ready() def _connect(self) -> None: diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index 26a283680..853d51846 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -60,7 +60,7 @@ def _configure(self) -> None: def get_connection_url(self, host=None) -> str: return super()._create_connection_url( - dialect="postgresql+{}".format(self.driver), username=self.username, + dialect=f"postgresql+{self.driver}", username=self.username, password=self.password, db_name=self.dbname, host=host, port=self.port_to_expose, ) From 95dc6ca03f3ae9dd3107d818f8356a5db1cde6fb Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 19:59:37 -0500 Subject: [PATCH 211/425] Fix typo in `ClickHouseContainer.__init__`. --- clickhouse/testcontainers/clickhouse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index 518eab079..2c0bdacf6 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -53,7 +53,7 @@ def __init__( raise ValueError("use `username` instead") self.username = username or os.environ.get("CLICKHOUSE_USER", "test") self.password = password or os.environ.get("CLICKHOUSE_PASSWORD", "test") - self.dbname = dbname or self.os.environ.get("CLICKHOUSE_DB", "test") + self.dbname = dbname or os.environ.get("CLICKHOUSE_DB", "test") self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) From 14761b1f487c20c1828b4769dc69e2e09621b959 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 20:02:33 -0500 Subject: [PATCH 212/425] Fix default username in `neo4j`. --- neo4j/testcontainers/neo4j/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index d85f9d8db..8c9146e69 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -40,7 +40,7 @@ class Neo4jContainer(DbContainer): def __init__(self, image: str = "neo4j:latest", *, bolt_port: int = 7687, password: Optional[str] = None, username: Optional[str] = None, **kwargs) -> None: super(Neo4jContainer, self).__init__(image, **kwargs) - self.username = username or os.environ.get("NEO4J_USER", "password") + self.username = username or os.environ.get("NEO4J_USER", "neo4j") self.password = password or os.environ.get("NEO4J_PASSWORD", "password") self.bolt_port = bolt_port self.with_exposed_ports(self.bolt_port) From cf6c585d8ec76baaf31c0186ab5cb4d7442e08c7 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 6 Jan 2023 20:13:47 -0500 Subject: [PATCH 213/425] Rename `port_to_expose` to `port` and use f-strings in tests. --- arangodb/testcontainers/arangodb/__init__.py | 13 ++++++++----- kafka/testcontainers/kafka/__init__.py | 16 +++++++++------- kafka/tests/test_kafka.py | 4 ++-- keycloak/tests/test_keycloak.py | 6 +++--- localstack/tests/test_localstack.py | 2 +- minio/testcontainers/minio/__init__.py | 18 ++++++++++-------- mongodb/testcontainers/mongodb/__init__.py | 14 ++++++++------ mongodb/tests/test_mongodb.py | 9 +++++---- neo4j/testcontainers/neo4j/__init__.py | 13 ++++++++----- nginx/testcontainers/nginx/__init__.py | 9 ++++++--- nginx/tests/test_nginx.py | 4 +--- .../testcontainers/opensearch/__init__.py | 13 ++++++++----- 12 files changed, 69 insertions(+), 52 deletions(-) diff --git a/arangodb/testcontainers/arangodb/__init__.py b/arangodb/testcontainers/arangodb/__init__.py index 4cb714c8f..95e58775a 100644 --- a/arangodb/testcontainers/arangodb/__init__.py +++ b/arangodb/testcontainers/arangodb/__init__.py @@ -36,15 +36,16 @@ class ArangoDbContainer(DbContainer): def __init__(self, image: str = "arangodb:latest", - port_to_expose: int = 8529, + port: int = 8529, arango_root_password: str = "passwd", arango_no_auth: typing.Optional[bool] = None, arango_random_root_password: typing.Optional[bool] = None, + port_to_expose: None = None, **kwargs) -> None: """ Args: image: Actual docker image/tag to pull. - port_to_expose: Port the container needs to expose. + port: Port the container needs to expose. arango_root_password: Start ArangoDB with the given password for root. Defaults to the environment variable `ARANGO_ROOT_PASSWORD` if `None`. arango_no_auth: Disable authentication completely. Defaults to the environment variable @@ -53,9 +54,11 @@ def __init__(self, the environment variable `ARANGO_NO_AUTH` if `None` or `False` if the environment variable is not available. """ + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super().__init__(image=image, **kwargs) - self.port_to_expose = port_to_expose - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) # See https://www.arangodb.com/docs/stable/deployment-single-instance-manual-start.html for # details. We convert to int then to bool because Arango uses the string literal "1" to @@ -77,7 +80,7 @@ def _configure(self) -> None: self.with_env("ARANGO_RANDOM_ROOT_PASSWORD", "1") def get_connection_url(self) -> str: - port = self.get_exposed_port(self.port_to_expose) + port = self.get_exposed_port(self.port) return f"http://{self.get_container_host_ip()}:{port}" def _connect(self) -> None: diff --git a/kafka/testcontainers/kafka/__init__.py b/kafka/testcontainers/kafka/__init__.py index 8f22af13f..2e905d31c 100644 --- a/kafka/testcontainers/kafka/__init__.py +++ b/kafka/testcontainers/kafka/__init__.py @@ -25,12 +25,14 @@ class KafkaContainer(DockerContainer): """ TC_START_SCRIPT = '/tc-start.sh' - def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: int = 9093, - **kwargs) -> None: + def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port: int = 9093, + port_to_expose: None = None, **kwargs) -> None: + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(KafkaContainer, self).__init__(image, **kwargs) - self.port_to_expose = port_to_expose - self.with_exposed_ports(self.port_to_expose) - listeners = f'PLAINTEXT://0.0.0.0:{port_to_expose},BROKER://0.0.0.0:9092' + self.port = port + self.with_exposed_ports(self.port) + listeners = f'PLAINTEXT://0.0.0.0:{self.port},BROKER://0.0.0.0:9092' self.with_env('KAFKA_LISTENERS', listeners) self.with_env('KAFKA_LISTENER_SECURITY_PROTOCOL_MAP', 'BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT') @@ -44,7 +46,7 @@ def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port_to_expose: i def get_bootstrap_server(self) -> str: host = self.get_container_host_ip() - port = self.get_exposed_port(self.port_to_expose) + port = self.get_exposed_port(self.port) return f'{host}:{port}' @wait_container_is_ready(UnrecognizedBrokerVersion, NoBrokersAvailable, KafkaError, ValueError) @@ -56,7 +58,7 @@ def _connect(self) -> None: def tc_start(self) -> None: host = self.get_container_host_ip() - port = self.get_exposed_port(self.port_to_expose) + port = self.get_exposed_port(self.port) listeners = f'PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092' data = ( dedent( diff --git a/kafka/tests/test_kafka.py b/kafka/tests/test_kafka.py index ca6b6710a..5ebe99296 100644 --- a/kafka/tests/test_kafka.py +++ b/kafka/tests/test_kafka.py @@ -8,8 +8,8 @@ def test_kafka_producer_consumer(): def test_kafka_producer_consumer_custom_port(): - with KafkaContainer(port_to_expose=9888) as container: - assert container.port_to_expose == 9888 + with KafkaContainer(port=9888) as container: + assert container.port == 9888 produce_and_consume_kafka_message(container) diff --git a/keycloak/tests/test_keycloak.py b/keycloak/tests/test_keycloak.py index 8e764130e..900ee0ddf 100644 --- a/keycloak/tests/test_keycloak.py +++ b/keycloak/tests/test_keycloak.py @@ -3,7 +3,7 @@ from testcontainers.keycloak import KeycloakContainer -@pytest.mark.parametrize(["version"], [("16.1.1", )]) -def test_docker_run_keycloak(version): - with KeycloakContainer('jboss/keycloak:{}'.format(version)) as kc: +@pytest.mark.parametrize("version", ["16.1.1"]) +def test_docker_run_keycloak(version: str): + with KeycloakContainer(f'jboss/keycloak:{version}') as kc: kc.get_client().users_count() diff --git a/localstack/tests/test_localstack.py b/localstack/tests/test_localstack.py index 8650a5a3c..5747a7da7 100644 --- a/localstack/tests/test_localstack.py +++ b/localstack/tests/test_localstack.py @@ -6,7 +6,7 @@ def test_docker_run_localstack(): with LocalStackContainer() as localstack: - resp = urllib.request.urlopen('{}/health'.format(localstack.get_url())) + resp = urllib.request.urlopen(f'{localstack.get_url()}/health') services = json.loads(resp.read().decode())['services'] # Check that all services are running diff --git a/minio/testcontainers/minio/__init__.py b/minio/testcontainers/minio/__init__.py index bdfb8963f..64b48cc3f 100644 --- a/minio/testcontainers/minio/__init__.py +++ b/minio/testcontainers/minio/__init__.py @@ -35,24 +35,26 @@ class MinioContainer(DockerContainer): """ def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", - port_to_expose: int = 9000, access_key: str = "minioadmin", - secret_key: str = "minioadmin", **kwargs) -> None: + port: int = 9000, access_key: str = "minioadmin", + secret_key: str = "minioadmin", port_to_expose: None = None, **kwargs) -> None: """ Args: image: Docker image to use for the MinIO container. - port_to_expose: Port to expose on the container. + port: Port to expose on the container. access_key: Access key for client connections. secret_key: Secret key for client connections. """ + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(MinioContainer, self).__init__(image, **kwargs) - self.port_to_expose = port_to_expose + self.port = port self.access_key = access_key self.secret_key = secret_key - self.with_exposed_ports(self.port_to_expose) + self.with_exposed_ports(self.port) self.with_env("MINIO_ACCESS_KEY", self.access_key) self.with_env("MINIO_SECRET_KEY", self.secret_key) - self.with_command(f"server /data --address :{self.port_to_expose}") + self.with_command(f"server /data --address :{self.port}") def get_client(self, **kwargs) -> Minio: """Returns a Minio client to connect to the container. @@ -62,7 +64,7 @@ def get_client(self, **kwargs) -> Minio: https://min.io/docs/minio/linux/developers/python/API.html """ host_ip = self.get_container_host_ip() - exposed_port = self.get_exposed_port(self.port_to_expose) + exposed_port = self.get_exposed_port(self.port) return Minio( f"{host_ip}:{exposed_port}", access_key=self.access_key, @@ -79,7 +81,7 @@ def get_config(self) -> dict: dict: {`endpoint`: str, `access_key`: str, `secret_key`: str} """ host_ip = self.get_container_host_ip() - exposed_port = self.get_exposed_port(self.port_to_expose) + exposed_port = self.get_exposed_port(self.port) return { "endpoint": f"{host_ip}:{exposed_port}", "access_key": self.access_key, diff --git a/mongodb/testcontainers/mongodb/__init__.py b/mongodb/testcontainers/mongodb/__init__.py index a3ac9cf2b..5200b566e 100644 --- a/mongodb/testcontainers/mongodb/__init__.py +++ b/mongodb/testcontainers/mongodb/__init__.py @@ -47,16 +47,18 @@ class MongoDbContainer(DbContainer): ... # Find the restaurant document ... cursor = db.restaurants.find({"borough": "Manhattan"}) """ - def __init__(self, image: str = "mongo:latest", port_to_expose: int = 27017, + def __init__(self, image: str = "mongo:latest", port: int = 27017, username: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, **kwargs) -> None: + dbname: Optional[str] = None, port_to_expose: None = None, **kwargs) -> None: + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(MongoDbContainer, self).__init__(image=image, **kwargs) self.username = username or os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") self.password = password or os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") self.dbname = dbname or os.environ.get("MONGO_DB", "test") - self.command = "mongo" - self.port_to_expose = port_to_expose - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) + self.with_command("mongo") def _configure(self) -> None: self.with_env("MONGO_INITDB_ROOT_USERNAME", self.username) @@ -68,7 +70,7 @@ def get_connection_url(self) -> str: dialect='mongodb', username=self.username, password=self.password, - port=self.port_to_expose, + port=self.port, ) @wait_container_is_ready() diff --git a/mongodb/tests/test_mongodb.py b/mongodb/tests/test_mongodb.py index a1e03e660..c778a0100 100644 --- a/mongodb/tests/test_mongodb.py +++ b/mongodb/tests/test_mongodb.py @@ -9,8 +9,9 @@ def test_docker_generic_db(): with DockerContainer("mongo:latest").with_bind_ports(27017, 27017) as mongo_container: def connect(): - return MongoClient("mongodb://{}:{}".format(mongo_container.get_container_host_ip(), - mongo_container.get_exposed_port(27017))) + host = mongo_container.get_container_host_ip() + port = mongo_container.get_exposed_port(27017) + return MongoClient(f"mongodb://{host}:{port}") db = wait_for(connect).primer result = db.restaurants.insert_one( @@ -55,8 +56,8 @@ def test_docker_run_mongodb(): def test_docker_run_mongodb_connect_without_credentials(): with MongoDbContainer() as mongo: - connection_url = "mongodb://{}:{}".format(mongo.get_container_host_ip(), - mongo.get_exposed_port(mongo.port_to_expose)) + connection_url = f"mongodb://{mongo.get_container_host_ip()}:" \ + f"{mongo.get_exposed_port(mongo.port)}" db = MongoClient(connection_url).test with pytest.raises(OperationFailure): db.restaurants.insert_one({}) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index 8c9146e69..c79c73ca3 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -37,20 +37,23 @@ class Neo4jContainer(DbContainer): ... result = session.run("MATCH (n) RETURN n LIMIT 1") ... record = result.single() """ - def __init__(self, image: str = "neo4j:latest", *, bolt_port: int = 7687, - password: Optional[str] = None, username: Optional[str] = None, **kwargs) -> None: + def __init__(self, image: str = "neo4j:latest", *, port: int = 7687, + password: Optional[str] = None, username: Optional[str] = None, + bolt_port: None = None, **kwargs) -> None: + if bolt_port: + raise ValueError("use `port` instead of `bolt_port`") super(Neo4jContainer, self).__init__(image, **kwargs) self.username = username or os.environ.get("NEO4J_USER", "neo4j") self.password = password or os.environ.get("NEO4J_PASSWORD", "password") - self.bolt_port = bolt_port - self.with_exposed_ports(self.bolt_port) + self.port = port + self.with_exposed_ports(self.port) self._driver = None def _configure(self) -> None: self.with_env("NEO4J_AUTH", f"neo4j/{self.password}") def get_connection_url(self) -> str: - return f"bolt://{self.get_container_host_ip()}:{self.get_exposed_port(self.bolt_port)}" + return f"bolt://{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" @wait_container_is_ready() def _connect(self) -> None: diff --git a/nginx/testcontainers/nginx/__init__.py b/nginx/testcontainers/nginx/__init__.py index 2cb8851cf..7ee28e378 100644 --- a/nginx/testcontainers/nginx/__init__.py +++ b/nginx/testcontainers/nginx/__init__.py @@ -14,7 +14,10 @@ class NginxContainer(DockerContainer): - def __init__(self, image: str = "nginx:latest", port_to_expose: int = 80, **kwargs) -> None: + def __init__(self, image: str = "nginx:latest", port: int = 80, port_to_expose: None = None, + **kwargs) -> None: + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(NginxContainer, self).__init__(image, **kwargs) - self.port_to_expose = port_to_expose - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) diff --git a/nginx/tests/test_nginx.py b/nginx/tests/test_nginx.py index cd4d68ca7..0d369bf71 100644 --- a/nginx/tests/test_nginx.py +++ b/nginx/tests/test_nginx.py @@ -6,9 +6,7 @@ def test_docker_run_nginx(): nginx_container = NginxContainer("nginx:1.13.8") with nginx_container as nginx: - port = nginx.port_to_expose - url = "http://{}:{}/".format(nginx.get_container_host_ip(), - nginx.get_exposed_port(port)) + url = f"http://{nginx.get_container_host_ip()}:{nginx.get_exposed_port(nginx.port)}/" r = requests.get(url) assert (r.status_code == 200) assert ('Welcome to nginx!' in r.text) diff --git a/opensearch/testcontainers/opensearch/__init__.py b/opensearch/testcontainers/opensearch/__init__.py index e8e690520..05b307947 100644 --- a/opensearch/testcontainers/opensearch/__init__.py +++ b/opensearch/testcontainers/opensearch/__init__.py @@ -29,18 +29,21 @@ class OpenSearchContainer(DockerContainer): """ def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", - port_to_expose: int = 9200, security_enabled: bool = False, **kwargs) -> None: + port: int = 9200, security_enabled: bool = False, port_to_expose: None = None, + **kwargs) -> None: """ Args: image: Docker image to use for the container. - port_to_expose: Port to expose on the container. + port: Port to expose on the container. security_enabled: :code:`False` disables the security plugin in OpenSearch. """ + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(OpenSearchContainer, self).__init__(image, **kwargs) - self.port_to_expose = port_to_expose + self.port = port self.security_enabled = security_enabled - self.with_exposed_ports(self.port_to_expose) + self.with_exposed_ports(self.port) self.with_env("discovery.type", "single-node") self.with_env("plugins.security.disabled", "false" if security_enabled else "true") if security_enabled: @@ -56,7 +59,7 @@ def get_config(self) -> dict: return { "host": self.get_container_host_ip(), - "port": self.get_exposed_port(self.port_to_expose), + "port": self.get_exposed_port(self.port), "username": "admin", "password": "admin", } From 3c28cddea898e419f092001c55895b10df95ccbe Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 17:47:34 -0500 Subject: [PATCH 214/425] Remove erroneous `with_command` statement. --- mongodb/testcontainers/mongodb/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mongodb/testcontainers/mongodb/__init__.py b/mongodb/testcontainers/mongodb/__init__.py index 5200b566e..d4d773850 100644 --- a/mongodb/testcontainers/mongodb/__init__.py +++ b/mongodb/testcontainers/mongodb/__init__.py @@ -58,7 +58,6 @@ def __init__(self, image: str = "mongo:latest", port: int = 27017, self.dbname = dbname or os.environ.get("MONGO_DB", "test") self.port = port self.with_exposed_ports(self.port) - self.with_command("mongo") def _configure(self) -> None: self.with_env("MONGO_INITDB_ROOT_USERNAME", self.username) From 89822f7ead9c961000e49cb7cf355f965fd49543 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 17:51:26 -0500 Subject: [PATCH 215/425] Rename `db_user` to `username` in tests. --- arangodb/tests/test_arangodb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arangodb/tests/test_arangodb.py b/arangodb/tests/test_arangodb.py index 574a9fd91..f5fd39153 100644 --- a/arangodb/tests/test_arangodb.py +++ b/arangodb/tests/test_arangodb.py @@ -9,7 +9,7 @@ ARANGODB_IMAGE_NAME = 'arangodb' -def arango_test_ops(arango_client, expeced_version, db_user='root', db_pass=''): +def arango_test_ops(arango_client, expeced_version, username='root', db_pass=''): """ Basic ArangoDB operations to test DB really up and running. """ @@ -17,14 +17,14 @@ def arango_test_ops(arango_client, expeced_version, db_user='root', db_pass=''): # Taken from https://github.com/ArangoDB-Community/python-arango/blob/main/README.md # Connect to "_system" database as root user. - sys_db = arango_client.db("_system", username=db_user, password=db_pass) + sys_db = arango_client.db("_system", username=username, password=db_pass) assert sys_db.version() == expeced_version # Create a new database named "test". sys_db.create_database("test") # Connect to "test" database as root user. - database = arango_client.db("test", username=db_user, password=db_pass) + database = arango_client.db("test", username=username, password=db_pass) # Create a new collection named "students". students = database.create_collection("students") From deebd05b68c19068c1cc07a69063f39bb5346c44 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 17:51:51 -0500 Subject: [PATCH 216/425] Remove unused function in docker api test. --- core/tests/test_new_docker_api.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/tests/test_new_docker_api.py b/core/tests/test_new_docker_api.py index f1bd3ad6d..22e69d19d 100644 --- a/core/tests/test_new_docker_api.py +++ b/core/tests/test_new_docker_api.py @@ -1,14 +1,8 @@ -import os from pathlib import Path from testcontainers.core.container import DockerContainer -def setup_module(m): - os.environ["MYSQL_USER"] = "demo" - os.environ["MYSQL_DATABASE"] = "custom_db" - - def test_docker_custom_image(): container = DockerContainer("mysql:5.7.17") container.with_exposed_ports(3306) From 44d9b3de91b0218978a35fde2835d235fd53ba49 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 18:02:35 -0500 Subject: [PATCH 217/425] Rename `db_name` to `dbname`. --- clickhouse/testcontainers/clickhouse/__init__.py | 2 +- core/testcontainers/core/generic.py | 8 +++++--- mssql/testcontainers/mssql/__init__.py | 2 +- mysql/testcontainers/mysql/__init__.py | 2 +- oracle/testcontainers/oracle/__init__.py | 2 +- postgres/testcontainers/postgres/__init__.py | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index 2c0bdacf6..fdc48cc84 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -72,7 +72,7 @@ def get_connection_url(self, host: Optional[str] = None) -> str: dialect="clickhouse", username=self.username, password=self.password, - db_name=self.dbname, + dbname=self.dbname, host=host, port=self.port_to_expose, ) diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 6f6a1f3c2..749636c14 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -39,14 +39,16 @@ def get_connection_url(self) -> str: def _create_connection_url(self, dialect: str, username: str, password: str, host: Optional[str] = None, port: Optional[int] = None, - db_name: Optional[str] = None) -> str: + dbname: Optional[str] = None, db_name: None = None) -> str: + if db_name: + raise ValueError("use `dbname` instead of `db_name`") if self._container is None: raise ContainerStartException("container has not been started") host = host or self.get_container_host_ip() port = self.get_exposed_port(port) url = f"{dialect}://{username}:{password}@{host}:{port}" - if db_name: - url = f"{url}/{db_name}" + if dbname: + url = f"{url}/{dbname}" return url def start(self) -> 'DbContainer': diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index 1e7d852b7..51a240e53 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -45,5 +45,5 @@ def _configure(self) -> None: def get_connection_url(self) -> str: return super()._create_connection_url( dialect=self.dialect, username=self.username, password=self.password, - db_name=self.dbname, port=self.port_to_expose + dbname=self.dbname, port=self.port_to_expose ) diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index b5efe7675..52431e5d6 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -74,5 +74,5 @@ def get_connection_url(self) -> str: return super()._create_connection_url(dialect="mysql+pymysql", username=self.username, password=self.password, - db_name=self.dbname, + dbname=self.dbname, port=self.port_to_expose) diff --git a/oracle/testcontainers/oracle/__init__.py b/oracle/testcontainers/oracle/__init__.py index b82a9c00a..3bd736076 100644 --- a/oracle/testcontainers/oracle/__init__.py +++ b/oracle/testcontainers/oracle/__init__.py @@ -27,7 +27,7 @@ def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) - def get_connection_url(self) -> str: return super()._create_connection_url( dialect="oracle", username="system", password="oracle", port=self.container_port, - db_name="xe" + dbname="xe" ) def _configure(self) -> None: diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index 853d51846..6f097e112 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -61,6 +61,6 @@ def _configure(self) -> None: def get_connection_url(self, host=None) -> str: return super()._create_connection_url( dialect=f"postgresql+{self.driver}", username=self.username, - password=self.password, db_name=self.dbname, host=host, + password=self.password, dbname=self.dbname, host=host, port=self.port_to_expose, ) From 7f08e44fea1d9ec5630ffc83aa6080341d9481c7 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 18:03:40 -0500 Subject: [PATCH 218/425] Remove uninformative module docstrings. --- compose/testcontainers/compose/__init__.py | 7 ------- google/testcontainers/google/__init__.py | 7 ------- selenium/testcontainers/selenium/__init__.py | 6 ------ 3 files changed, 20 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 027dce281..92e46c270 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -1,10 +1,3 @@ -""" -Docker Compose Support -====================== - -Allows to spin up services configured via :code:`docker-compose.yml`. -""" - import requests import subprocess from typing import Iterable, List, Optional, Tuple, Union diff --git a/google/testcontainers/google/__init__.py b/google/testcontainers/google/__init__.py index 0a6fa82e8..71665bea6 100644 --- a/google/testcontainers/google/__init__.py +++ b/google/testcontainers/google/__init__.py @@ -1,8 +1 @@ -""" -Google Cloud Emulators -====================== - -Allows to spin up google cloud emulators, such as PubSub. -""" - from .pubsub import PubSubContainer # noqa diff --git a/selenium/testcontainers/selenium/__init__.py b/selenium/testcontainers/selenium/__init__.py index db201157a..c4dbbc155 100644 --- a/selenium/testcontainers/selenium/__init__.py +++ b/selenium/testcontainers/selenium/__init__.py @@ -10,12 +10,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Selenium containers -=================== - -Allows to spin up selenium containers for testing with browsers. -""" from selenium import webdriver from testcontainers.core.container import DockerContainer From f5a16cbc64849c3db18412c5361010e71c91235a Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 18:04:13 -0500 Subject: [PATCH 219/425] Rename `port_to_expose` to `port` for Redis and Elasticsearch. --- .../testcontainers/elasticsearch/__init__.py | 7 ++++-- redis/testcontainers/redis/__init__.py | 22 +++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index 260154fc6..0e6c13bde 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -73,9 +73,12 @@ class ElasticSearchContainer(DockerContainer): '8.3.3' """ - def __init__(self, image="elasticsearch", port_to_expose=9200, **kwargs) -> None: + def __init__(self, image: str = "elasticsearch", port: int = 9200, port_to_expose: None = None, + **kwargs) -> None: + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(ElasticSearchContainer, self).__init__(image, **kwargs) - self.port_to_expose = port_to_expose + self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) self.with_env('transport.host', '127.0.0.1') self.with_env('http.host', '0.0.0.0') diff --git a/redis/testcontainers/redis/__init__.py b/redis/testcontainers/redis/__init__.py index 713dd5274..9192da2b0 100644 --- a/redis/testcontainers/redis/__init__.py +++ b/redis/testcontainers/redis/__init__.py @@ -14,24 +14,28 @@ import redis from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready +from typing import Optional class RedisContainer(DockerContainer): """ - Redis container. + Redis container. - Example: + Example: - .. doctest:: + .. doctest:: - >>> from testcontainers.redis import RedisContainer + >>> from testcontainers.redis import RedisContainer - >>> with RedisContainer() as redis_container: - ... redis_client = redis_container.get_client() - """ - def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs) -> None: + >>> with RedisContainer() as redis_container: + ... redis_client = redis_container.get_client() + """ + def __init__(self, image: str = "redis:latest", port: int = 6379, + password: Optional[str] = None, port_to_expose: None = None, **kwargs) -> None: + if port_to_expose: + raise ValueError("use `port` instead of `port_to_expose`") super(RedisContainer, self).__init__(image, **kwargs) - self.port_to_expose = port_to_expose + self.port_to_expose = port self.password = password self.with_exposed_ports(self.port_to_expose) if self.password: From 3add8cb092165cbdb2950f31d16e86500cfca8aa Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 7 Jan 2023 18:20:03 -0500 Subject: [PATCH 220/425] Use utility function to raise for deprecated parameters. --- arangodb/testcontainers/arangodb/__init__.py | 5 ++--- .../testcontainers/clickhouse/__init__.py | 18 ++++++------------ core/testcontainers/core/generic.py | 7 ++++--- core/testcontainers/core/utils.py | 9 +++++++++ .../testcontainers/elasticsearch/__init__.py | 7 +++---- kafka/testcontainers/kafka/__init__.py | 8 ++++---- minio/testcontainers/minio/__init__.py | 6 +++--- mongodb/testcontainers/mongodb/__init__.py | 6 +++--- mssql/testcontainers/mssql/__init__.py | 7 +++---- mysql/testcontainers/mysql/__init__.py | 17 ++++++----------- neo4j/testcontainers/neo4j/__init__.py | 7 +++---- nginx/testcontainers/nginx/__init__.py | 7 +++---- .../testcontainers/opensearch/__init__.py | 7 +++---- postgres/testcontainers/postgres/__init__.py | 7 +++---- redis/testcontainers/redis/__init__.py | 6 +++--- 15 files changed, 58 insertions(+), 66 deletions(-) diff --git a/arangodb/testcontainers/arangodb/__init__.py b/arangodb/testcontainers/arangodb/__init__.py index 95e58775a..f56f1eab7 100644 --- a/arangodb/testcontainers/arangodb/__init__.py +++ b/arangodb/testcontainers/arangodb/__init__.py @@ -4,6 +4,7 @@ from os import environ from testcontainers.core.config import TIMEOUT from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_for_logs import typing @@ -40,7 +41,6 @@ def __init__(self, arango_root_password: str = "passwd", arango_no_auth: typing.Optional[bool] = None, arango_random_root_password: typing.Optional[bool] = None, - port_to_expose: None = None, **kwargs) -> None: """ Args: @@ -54,8 +54,7 @@ def __init__(self, the environment variable `ARANGO_NO_AUTH` if `None` or `False` if the environment variable is not available. """ - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image=image, **kwargs) self.port = port self.with_exposed_ports(self.port) diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index fdc48cc84..ec762353c 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -17,6 +17,7 @@ from clickhouse_driver.errors import Error from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -39,18 +40,11 @@ class ClickHouseContainer(DbContainer): ... client.execute("select 'working'") [('working',)] """ - def __init__( - self, - image: str = "clickhouse/clickhouse-server:latest", - port: int = 9000, - username: Optional[str] = None, - password: Optional[str] = None, - dbname: Optional[str] = None, - user: None = None, - ) -> None: - super().__init__(image=image) - if user: - raise ValueError("use `username` instead") + 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") diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 749636c14..7f84ba6dd 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -14,6 +14,7 @@ from .container import DockerContainer from .exceptions import ContainerStartException +from .utils import raise_for_deprecated_parameter from .waiting_utils import wait_container_is_ready ADDITIONAL_TRANSIENT_ERRORS = [] @@ -39,9 +40,9 @@ def get_connection_url(self) -> str: def _create_connection_url(self, dialect: str, username: str, password: str, host: Optional[str] = None, port: Optional[int] = None, - dbname: Optional[str] = None, db_name: None = None) -> str: - if db_name: - raise ValueError("use `dbname` instead of `db_name`") + dbname: Optional[str] = None, **kwargs) -> str: + if raise_for_deprecated_parameter(kwargs, "db_name", "dbname"): + raise ValueError(f"unexpected arguments: {','.join(kwargs)}") if self._container is None: raise ContainerStartException("container has not been started") host = host or self.get_container_host_ip() diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index c1090b332..8664f9447 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -69,3 +69,12 @@ def default_gateway_ip() -> str: return ip_address.decode('utf-8').strip().strip('\n') except subprocess.SubprocessError: return None + + +def raise_for_deprecated_parameter(kwargs: dict, name: str, replacement: str) -> dict: + """ + Raise an error if a dictionary of keyword arguments contains a key and suggest the replacement. + """ + if kwargs.pop(name, None): + raise ValueError(f"use `{replacement}` instead of `{name}`") + return kwargs diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index 0e6c13bde..8f9402d8c 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -17,6 +17,7 @@ from urllib.error import URLError from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready _FALLBACK_VERSION = 8 @@ -73,10 +74,8 @@ class ElasticSearchContainer(DockerContainer): '8.3.3' """ - def __init__(self, image: str = "elasticsearch", port: int = 9200, port_to_expose: None = None, - **kwargs) -> None: - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + def __init__(self, image: str = "elasticsearch", port: int = 9200, **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(ElasticSearchContainer, self).__init__(image, **kwargs) self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) diff --git a/kafka/testcontainers/kafka/__init__.py b/kafka/testcontainers/kafka/__init__.py index 2e905d31c..49c362c20 100644 --- a/kafka/testcontainers/kafka/__init__.py +++ b/kafka/testcontainers/kafka/__init__.py @@ -7,6 +7,7 @@ from kafka.errors import KafkaError, UnrecognizedBrokerVersion, NoBrokersAvailable from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -25,10 +26,9 @@ class KafkaContainer(DockerContainer): """ TC_START_SCRIPT = '/tc-start.sh' - def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port: int = 9093, - port_to_expose: None = None, **kwargs) -> None: - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port: int = 9093, **kwargs) \ + -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(KafkaContainer, self).__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) diff --git a/minio/testcontainers/minio/__init__.py b/minio/testcontainers/minio/__init__.py index 64b48cc3f..87b91d942 100644 --- a/minio/testcontainers/minio/__init__.py +++ b/minio/testcontainers/minio/__init__.py @@ -2,6 +2,7 @@ from requests import ConnectionError, Response, get from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -36,7 +37,7 @@ class MinioContainer(DockerContainer): def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", port: int = 9000, access_key: str = "minioadmin", - secret_key: str = "minioadmin", port_to_expose: None = None, **kwargs) -> None: + secret_key: str = "minioadmin", **kwargs) -> None: """ Args: image: Docker image to use for the MinIO container. @@ -44,8 +45,7 @@ def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", access_key: Access key for client connections. secret_key: Secret key for client connections. """ - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(MinioContainer, self).__init__(image, **kwargs) self.port = port self.access_key = access_key diff --git a/mongodb/testcontainers/mongodb/__init__.py b/mongodb/testcontainers/mongodb/__init__.py index d4d773850..97db1a3e2 100644 --- a/mongodb/testcontainers/mongodb/__init__.py +++ b/mongodb/testcontainers/mongodb/__init__.py @@ -13,6 +13,7 @@ import os from pymongo import MongoClient from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready from typing import Optional @@ -49,9 +50,8 @@ class MongoDbContainer(DbContainer): """ def __init__(self, image: str = "mongo:latest", port: int = 27017, username: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, port_to_expose: None = None, **kwargs) -> None: - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + dbname: Optional[str] = None, **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(MongoDbContainer, self).__init__(image=image, **kwargs) self.username = username or os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") self.password = password or os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index 51a240e53..265dfb407 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -1,6 +1,7 @@ from os import environ from typing import Optional from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter class SqlServerContainer(DbContainer): @@ -22,11 +23,9 @@ class SqlServerContainer(DbContainer): def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", username: str = "SA", password: Optional[str] = None, port: int = 1433, - dbname: str = "tempdb", dialect: str = 'mssql+pymssql', user: None = None, - **kwargs) -> None: + dbname: str = "tempdb", dialect: str = 'mssql+pymssql', **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "user", "username") super(SqlServerContainer, self).__init__(image, **kwargs) - if user: - raise ValueError("use `username` instead") self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index 52431e5d6..e5f12451c 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -13,6 +13,7 @@ from os import environ from typing import Optional from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter class MySqlContainer(DbContainer): @@ -39,18 +40,12 @@ class MySqlContainer(DbContainer): """ def __init__(self, image: str = "mysql:latest", username: Optional[str] = None, root_password: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, port: int = 3306, MYSQL_USER: None = None, - MYSQL_ROOT_PASSWORD: None = None, MYSQL_PASSWORD: None = None, - MYSQL_DATABASE: None = None, **kwargs) -> None: + dbname: Optional[str] = None, port: int = 3306, **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "MYSQL_USER", "username") + raise_for_deprecated_parameter(kwargs, "MYSQL_ROOT_PASSWORD", "root_password") + raise_for_deprecated_parameter(kwargs, "MYSQL_PASSWORD", "password") + raise_for_deprecated_parameter(kwargs, "MYSQL_DATABASE", "dbname") super(MySqlContainer, self).__init__(image, **kwargs) - if MYSQL_USER: - raise ValueError("use `username` instead of `MYSQL_USER`") - if MYSQL_ROOT_PASSWORD: - raise ValueError("use `root_password` instead of `MYSQL_ROOT_PASSWORD`") - if MYSQL_PASSWORD: - raise ValueError("use `password` instead of `MYSQL_PASSWORD`") - if MYSQL_DATABASE: - raise ValueError("use `dbname` instead of `MYSQL_DATABASE`") self.port_to_expose = port self.with_exposed_ports(self.port_to_expose) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index c79c73ca3..700c674ce 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -17,6 +17,7 @@ from testcontainers.core.config import TIMEOUT from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs from typing import Optional @@ -38,10 +39,8 @@ class Neo4jContainer(DbContainer): ... record = result.single() """ def __init__(self, image: str = "neo4j:latest", *, port: int = 7687, - password: Optional[str] = None, username: Optional[str] = None, - bolt_port: None = None, **kwargs) -> None: - if bolt_port: - raise ValueError("use `port` instead of `bolt_port`") + password: Optional[str] = None, username: Optional[str] = None, **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "bolt_port", "port") super(Neo4jContainer, self).__init__(image, **kwargs) self.username = username or os.environ.get("NEO4J_USER", "neo4j") self.password = password or os.environ.get("NEO4J_PASSWORD", "password") diff --git a/nginx/testcontainers/nginx/__init__.py b/nginx/testcontainers/nginx/__init__.py index 7ee28e378..0226d700b 100644 --- a/nginx/testcontainers/nginx/__init__.py +++ b/nginx/testcontainers/nginx/__init__.py @@ -11,13 +11,12 @@ # License for the specific language governing permissions and limitations # under the License. from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter class NginxContainer(DockerContainer): - def __init__(self, image: str = "nginx:latest", port: int = 80, port_to_expose: None = None, - **kwargs) -> None: - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + def __init__(self, image: str = "nginx:latest", port: int = 80, **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(NginxContainer, self).__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) diff --git a/opensearch/testcontainers/opensearch/__init__.py b/opensearch/testcontainers/opensearch/__init__.py index 05b307947..0422b9361 100644 --- a/opensearch/testcontainers/opensearch/__init__.py +++ b/opensearch/testcontainers/opensearch/__init__.py @@ -2,6 +2,7 @@ from opensearchpy.exceptions import ConnectionError, TransportError from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -29,16 +30,14 @@ class OpenSearchContainer(DockerContainer): """ def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", - port: int = 9200, security_enabled: bool = False, port_to_expose: None = None, - **kwargs) -> None: + port: int = 9200, security_enabled: bool = False, **kwargs) -> None: """ Args: image: Docker image to use for the container. port: Port to expose on the container. security_enabled: :code:`False` disables the security plugin in OpenSearch. """ - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(OpenSearchContainer, self).__init__(image, **kwargs) self.port = port self.security_enabled = security_enabled diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index 6f097e112..fac042c94 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -13,6 +13,7 @@ import os from typing import Optional from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter class PostgresContainer(DbContainer): @@ -40,10 +41,8 @@ class PostgresContainer(DbContainer): """ def __init__(self, image: str = "postgres:latest", port: int = 5432, username: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, driver: str = "psycopg2", user: None = None, - **kwargs) -> None: - if user: - raise ValueError("use `username` instead of `user`") + dbname: Optional[str] = None, driver: str = "psycopg2", **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "user", "username") super(PostgresContainer, self).__init__(image=image, **kwargs) self.username = username or os.environ.get("POSTGRES_USER", "test") self.password = password or os.environ.get("POSTGRES_PASSWORD", "test") diff --git a/redis/testcontainers/redis/__init__.py b/redis/testcontainers/redis/__init__.py index 9192da2b0..df0281920 100644 --- a/redis/testcontainers/redis/__init__.py +++ b/redis/testcontainers/redis/__init__.py @@ -13,6 +13,7 @@ import redis from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready from typing import Optional @@ -31,9 +32,8 @@ class RedisContainer(DockerContainer): ... redis_client = redis_container.get_client() """ def __init__(self, image: str = "redis:latest", port: int = 6379, - password: Optional[str] = None, port_to_expose: None = None, **kwargs) -> None: - if port_to_expose: - raise ValueError("use `port` instead of `port_to_expose`") + password: Optional[str] = None, **kwargs) -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(RedisContainer, self).__init__(image, **kwargs) self.port_to_expose = port self.password = password From f881c80cd74764de46a1f96315eb4aa115fe3443 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 17 Feb 2023 23:10:17 -0500 Subject: [PATCH 221/425] Remove `ports_to_expose` from Azurite container. --- azurite/testcontainers/azurite/__init__.py | 45 +++++++++++----------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/azurite/testcontainers/azurite/__init__.py b/azurite/testcontainers/azurite/__init__.py index f31860578..6e6716501 100644 --- a/azurite/testcontainers/azurite/__init__.py +++ b/azurite/testcontainers/azurite/__init__.py @@ -12,43 +12,43 @@ # under the License. import os import socket -from typing import Iterable, Optional +from typing import Optional from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready class AzuriteContainer(DockerContainer): """ - The example below spins up an Azurite container and - shows an example to create a Blob service client with the container. The method - :code:`get_connection_string` can be used to create a client for Blob service, Queue service - and Table service. + The example below spins up an Azurite container and + shows an example to create a Blob service client with the container. The method + :code:`get_connection_string` can be used to create a client for Blob service, Queue service + and Table service. - Example: + Example: - .. doctest:: + .. doctest:: - >>> from testcontainers.azurite import AzuriteContainer - >>> from azure.storage.blob import BlobServiceClient + >>> from testcontainers.azurite import AzuriteContainer + >>> from azure.storage.blob import BlobServiceClient - >>> with AzuriteContainer() as azurite_container: - ... connection_string = azurite_container.get_connection_string() - ... client = BlobServiceClient.from_connection_string( - ... connection_string, - ... api_version="2019-12-12" - ... ) - """ + >>> with AzuriteContainer() as azurite_container: + ... connection_string = azurite_container.get_connection_string() + ... client = BlobServiceClient.from_connection_string( + ... connection_string, + ... api_version="2019-12-12" + ... ) + """ def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest", *, - ports_to_expose: Optional[Iterable[int]] = None, blob_service_port: int = 10_000, - queue_service_port: int = 10_001, table_service_port: int = 10_002, - account_name: Optional[str] = None, account_key: Optional[str] = None, **kwargs) \ + blob_service_port: int = 10_000, queue_service_port: int = 10_001, + table_service_port: int = 10_002, account_name: Optional[str] = None, + account_key: Optional[str] = None, **kwargs) \ -> None: """ Constructs an AzuriteContainer. Args: image: Expects an image with tag. - ports_to_expose: List with port numbers to expose. **kwargs: Keyword arguments passed to super class. """ super().__init__(image=image, **kwargs) @@ -58,13 +58,12 @@ def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest" "AZURITE_ACCOUNT_KEY", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/" "K1SZFPTOtr/KBHBeksoGMGw==") + raise_for_deprecated_parameter(kwargs, "ports_to_expose", "container.with_exposed_ports") self.blob_service_port = blob_service_port self.queue_service_port = queue_service_port self.table_service_port = table_service_port - if not ports_to_expose: - ports_to_expose = [blob_service_port, queue_service_port, table_service_port] - self.with_exposed_ports(*ports_to_expose) + self.with_exposed_ports(*blob_service_port, queue_service_port, table_service_port) self.with_env("AZURITE_ACCOUNTS", f"{self.account_name}:{self.account_key}") def get_connection_string(self) -> str: From 0a1b372228d8fb79778e39ce24671f29593c93b5 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 17 Feb 2023 23:11:25 -0500 Subject: [PATCH 222/425] Rename `port_to_expose` to `port`. --- clickhouse/testcontainers/clickhouse/__init__.py | 6 +++--- elasticsearch/testcontainers/elasticsearch/__init__.py | 6 +++--- keycloak/testcontainers/keycloak/__init__.py | 6 +++--- mssql/testcontainers/mssql/__init__.py | 6 +++--- mysql/testcontainers/mysql/__init__.py | 6 +++--- postgres/testcontainers/postgres/__init__.py | 6 +++--- redis/testcontainers/redis/__init__.py | 6 +++--- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/clickhouse/testcontainers/clickhouse/__init__.py index ec762353c..b78c509cd 100644 --- a/clickhouse/testcontainers/clickhouse/__init__.py +++ b/clickhouse/testcontainers/clickhouse/__init__.py @@ -48,8 +48,8 @@ def __init__(self, image: str = "clickhouse/clickhouse-server:latest", port: int 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.port_to_expose = port - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) @wait_container_is_ready(Error, EOFError) def _connect(self) -> None: @@ -68,5 +68,5 @@ def get_connection_url(self, host: Optional[str] = None) -> str: password=self.password, dbname=self.dbname, host=host, - port=self.port_to_expose, + port=self.port, ) diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/elasticsearch/testcontainers/elasticsearch/__init__.py index 8f9402d8c..ec9458330 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -77,8 +77,8 @@ class ElasticSearchContainer(DockerContainer): def __init__(self, image: str = "elasticsearch", port: int = 9200, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(ElasticSearchContainer, self).__init__(image, **kwargs) - self.port_to_expose = port - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) self.with_env('transport.host', '127.0.0.1') self.with_env('http.host', '0.0.0.0') @@ -94,7 +94,7 @@ def _connect(self) -> None: def get_url(self) -> str: host = self.get_container_host_ip() - port = self.get_exposed_port(self.port_to_expose) + port = self.get_exposed_port(self.port) return f'http://{host}:{port}' def start(self) -> "ElasticSearchContainer": diff --git a/keycloak/testcontainers/keycloak/__init__.py b/keycloak/testcontainers/keycloak/__init__.py index 9787f0f2d..aeb9a4c78 100644 --- a/keycloak/testcontainers/keycloak/__init__.py +++ b/keycloak/testcontainers/keycloak/__init__.py @@ -38,8 +38,8 @@ def __init__(self, image="jboss/keycloak:latest", username: Optional[str] = None super(KeycloakContainer, self).__init__(image=image) self.username = username or os.environ.get("KEYCLOAK_USER", "test") self.password = password or os.environ.get("KEYCLOAK_PASSWORD", "test") - self.port_to_expose = port - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) def _configure(self) -> None: self.with_env("KEYCLOAK_USER", self.username) @@ -47,7 +47,7 @@ def _configure(self) -> None: def get_url(self) -> str: host = self.get_container_host_ip() - port = self.get_exposed_port(self.port_to_expose) + port = self.get_exposed_port(self.port) return f"http://{host}:{port}" @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) diff --git a/mssql/testcontainers/mssql/__init__.py b/mssql/testcontainers/mssql/__init__.py index 265dfb407..9de6edf00 100644 --- a/mssql/testcontainers/mssql/__init__.py +++ b/mssql/testcontainers/mssql/__init__.py @@ -27,8 +27,8 @@ def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", raise_for_deprecated_parameter(kwargs, "user", "username") super(SqlServerContainer, self).__init__(image, **kwargs) - self.port_to_expose = port - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) self.password = password or environ.get("SQLSERVER_PASSWORD", "1Secure*Password1") self.username = username @@ -44,5 +44,5 @@ def _configure(self) -> None: def get_connection_url(self) -> str: return super()._create_connection_url( dialect=self.dialect, username=self.username, password=self.password, - dbname=self.dbname, port=self.port_to_expose + dbname=self.dbname, port=self.port ) diff --git a/mysql/testcontainers/mysql/__init__.py b/mysql/testcontainers/mysql/__init__.py index e5f12451c..6234540bc 100644 --- a/mysql/testcontainers/mysql/__init__.py +++ b/mysql/testcontainers/mysql/__init__.py @@ -47,8 +47,8 @@ def __init__(self, image: str = "mysql:latest", username: Optional[str] = None, raise_for_deprecated_parameter(kwargs, "MYSQL_DATABASE", "dbname") super(MySqlContainer, self).__init__(image, **kwargs) - self.port_to_expose = port - self.with_exposed_ports(self.port_to_expose) + self.port = port + self.with_exposed_ports(self.port) self.username = username or environ.get('MYSQL_USER', 'test') self.root_password = root_password or environ.get('MYSQL_ROOT_PASSWORD', 'test') self.password = password or environ.get('MYSQL_PASSWORD', 'test') @@ -70,4 +70,4 @@ def get_connection_url(self) -> str: username=self.username, password=self.password, dbname=self.dbname, - port=self.port_to_expose) + port=self.port) diff --git a/postgres/testcontainers/postgres/__init__.py b/postgres/testcontainers/postgres/__init__.py index fac042c94..85e0bac80 100644 --- a/postgres/testcontainers/postgres/__init__.py +++ b/postgres/testcontainers/postgres/__init__.py @@ -47,10 +47,10 @@ def __init__(self, image: str = "postgres:latest", port: int = 5432, self.username = username or os.environ.get("POSTGRES_USER", "test") self.password = password or os.environ.get("POSTGRES_PASSWORD", "test") self.dbname = dbname or os.environ.get("POSTGRES_DB", "test") - self.port_to_expose = port + self.port = port self.driver = driver - self.with_exposed_ports(self.port_to_expose) + self.with_exposed_ports(self.port) def _configure(self) -> None: self.with_env("POSTGRES_USER", self.username) @@ -61,5 +61,5 @@ def get_connection_url(self, host=None) -> str: return super()._create_connection_url( dialect=f"postgresql+{self.driver}", username=self.username, password=self.password, dbname=self.dbname, host=host, - port=self.port_to_expose, + port=self.port, ) diff --git a/redis/testcontainers/redis/__init__.py b/redis/testcontainers/redis/__init__.py index df0281920..12d473644 100644 --- a/redis/testcontainers/redis/__init__.py +++ b/redis/testcontainers/redis/__init__.py @@ -35,9 +35,9 @@ def __init__(self, image: str = "redis:latest", port: int = 6379, password: Optional[str] = None, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super(RedisContainer, self).__init__(image, **kwargs) - self.port_to_expose = port + self.port = port self.password = password - self.with_exposed_ports(self.port_to_expose) + self.with_exposed_ports(self.port) if self.password: self.with_command(f"redis-server --requirepass {self.password}") @@ -59,7 +59,7 @@ def get_client(self, **kwargs) -> redis.Redis: """ return redis.Redis( host=self.get_container_host_ip(), - port=self.get_exposed_port(self.port_to_expose), + port=self.get_exposed_port(self.port), password=self.password, **kwargs, ) From 6dc9b8d9f8e3007397288e31835f4bad464e0908 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 17 Feb 2023 23:11:42 -0500 Subject: [PATCH 223/425] Expose port configuration for selenium. --- selenium/testcontainers/selenium/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/selenium/testcontainers/selenium/__init__.py b/selenium/testcontainers/selenium/__init__.py index c4dbbc155..6b2070787 100644 --- a/selenium/testcontainers/selenium/__init__.py +++ b/selenium/testcontainers/selenium/__init__.py @@ -45,13 +45,14 @@ class BrowserWebDriverContainer(DockerContainer): You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ - def __init__(self, capabilities: str, image: Optional[str] = None, **kwargs) -> None: + def __init__(self, capabilities: str, image: Optional[str] = None, port: int = 4444, + vnc_port: int = 5900, **kwargs) -> None: self.capabilities = capabilities self.image = image or get_image_name(capabilities) - self.port_to_expose = 4444 - self.vnc_port_to_expose = 5900 + self.port = port + self.vnc_port = vnc_port super(BrowserWebDriverContainer, self).__init__(image=self.image, **kwargs) - self.with_exposed_ports(self.port_to_expose, self.vnc_port_to_expose) + self.with_exposed_ports(self.port, self.vnc_port) def _configure(self) -> None: self.with_env("no_proxy", "localhost") @@ -68,5 +69,5 @@ def get_driver(self) -> webdriver.Remote: def get_connection_url(self) -> str: ip = self.get_container_host_ip() - port = self.get_exposed_port(self.port_to_expose) + port = self.get_exposed_port(self.port) return f'http://{ip}:{port}/wd/hub' From 76a9bfa75fa225fe23b32b78611f26dc315dbf31 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 18 Feb 2023 12:47:07 -0500 Subject: [PATCH 224/425] Remove `db_` from arango tests so we can search for `db_name`. --- arangodb/tests/test_arangodb.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/arangodb/tests/test_arangodb.py b/arangodb/tests/test_arangodb.py index f5fd39153..7933c2fd1 100644 --- a/arangodb/tests/test_arangodb.py +++ b/arangodb/tests/test_arangodb.py @@ -9,7 +9,7 @@ ARANGODB_IMAGE_NAME = 'arangodb' -def arango_test_ops(arango_client, expeced_version, username='root', db_pass=''): +def arango_test_ops(arango_client, expeced_version, username='root', password=''): """ Basic ArangoDB operations to test DB really up and running. """ @@ -17,14 +17,14 @@ def arango_test_ops(arango_client, expeced_version, username='root', db_pass='') # Taken from https://github.com/ArangoDB-Community/python-arango/blob/main/README.md # Connect to "_system" database as root user. - sys_db = arango_client.db("_system", username=username, password=db_pass) + sys_db = arango_client.db("_system", username=username, password=password) assert sys_db.version() == expeced_version # Create a new database named "test". sys_db.create_database("test") # Connect to "test" database as root user. - database = arango_client.db("test", username=username, password=db_pass) + database = arango_client.db("test", username=username, password=password) # Create a new collection named "students". students = database.create_collection("students") @@ -50,7 +50,7 @@ def test_docker_run_arango(): """ image_version = '3.9.1' image = f'{ARANGODB_IMAGE_NAME}:{image_version}' - arango_db_root_password = 'passwd' + arango_root_password = 'passwd' with ArangoDbContainer(image) as arango: client = ArangoClient(hosts=arango.get_connection_url()) @@ -63,7 +63,7 @@ def test_docker_run_arango(): arango_test_ops( arango_client=client, expeced_version=image_version, - db_pass=arango_db_root_password) + password=arango_root_password) def test_docker_run_arango_without_auth(): @@ -79,7 +79,7 @@ def test_docker_run_arango_without_auth(): arango_test_ops( arango_client=client, expeced_version=image_version, - db_pass='') + password='') def test_docker_run_arango_older_version(): @@ -100,7 +100,7 @@ def test_docker_run_arango_older_version(): arango_test_ops( arango_client=client, expeced_version=image_version, - db_pass='') + password='') def test_docker_run_arango_random_root_password(): @@ -109,12 +109,12 @@ def test_docker_run_arango_random_root_password(): """ image_version = '3.9.1' image = f'{ARANGODB_IMAGE_NAME}:{image_version}' - arango_db_root_password = 'passwd' + arango_root_password = 'passwd' with ArangoDbContainer(image, arango_random_root_password=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) # Test invalid auth (we don't know the password in random mode) with pytest.raises(ServerVersionError): - sys_db = client.db("_system", username='root', password=arango_db_root_password) + sys_db = client.db("_system", username='root', password=arango_root_password) assert sys_db.version() == image_version From e2cc9968a21a0a3f406b95afadd6d44b6ba13c87 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 18 Feb 2023 14:40:27 -0500 Subject: [PATCH 225/425] Fix incorrect port unpacking. --- azurite/testcontainers/azurite/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurite/testcontainers/azurite/__init__.py b/azurite/testcontainers/azurite/__init__.py index 6e6716501..847775c63 100644 --- a/azurite/testcontainers/azurite/__init__.py +++ b/azurite/testcontainers/azurite/__init__.py @@ -63,7 +63,7 @@ def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest" self.queue_service_port = queue_service_port self.table_service_port = table_service_port - self.with_exposed_ports(*blob_service_port, queue_service_port, table_service_port) + self.with_exposed_ports(blob_service_port, queue_service_port, table_service_port) self.with_env("AZURITE_ACCOUNTS", f"{self.account_name}:{self.account_key}") def get_connection_string(self) -> str: From 9358dc89f38dbc2cc23a1e74134e016c6bf57c74 Mon Sep 17 00:00:00 2001 From: Vemund Santi Date: Thu, 9 Mar 2023 06:47:30 +0100 Subject: [PATCH 226/425] Add Python3.11 to build matrix and generate requirements --- .github/workflows/main.yml | 1 + Makefile | 2 +- meta/setup.py | 1 + requirements/3.11.txt | 422 +++++++++++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 requirements/3.11.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 15d79b937..be6d0bcd2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,6 +14,7 @@ jobs: - "3.8" - "3.9" - "3.10" + - "3.11" component: - arangodb - azurite diff --git a/Makefile b/Makefile index bd49c0cea..4eaf1cc45 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 +PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 3.11 PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) diff --git a/meta/setup.py b/meta/setup.py index 8ced316e1..2dfba3a2d 100644 --- a/meta/setup.py +++ b/meta/setup.py @@ -37,6 +37,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", diff --git a/requirements/3.11.txt b/requirements/3.11.txt new file mode 100644 index 000000000..b0ff13e91 --- /dev/null +++ b/requirements/3.11.txt @@ -0,0 +1,422 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --output-file=requirements/3.11.txt --resolver=backtracking requirements.in +# +-e file:meta + # via -r requirements.in +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium + # via -r requirements.in +alabaster==0.7.13 + # via sphinx +asn1crypto==1.5.1 + # via scramp +async-generator==1.10 + # via + # trio + # trio-websocket +async-timeout==4.0.2 + # via redis +attrs==22.2.0 + # via + # jsonschema + # outcome + # pytest + # trio +azure-core==1.26.3 + # via azure-storage-blob +azure-storage-blob==12.15.0 + # via testcontainers-azurite +babel==2.12.1 + # via sphinx +bcrypt==4.0.1 + # via paramiko +bleach==6.0.0 + # via readme-renderer +cachetools==5.3.0 + # via google-auth +certifi==2022.12.7 + # via + # minio + # opensearch-py + # requests + # selenium +cffi==1.15.1 + # via + # cryptography + # pynacl +charset-normalizer==3.1.0 + # via requests +clickhouse-driver==0.2.5 + # via testcontainers-clickhouse +codecov==2.1.12 + # via -r requirements.in +coverage[toml]==7.2.1 + # via + # codecov + # pytest-cov +cryptography==36.0.2 + # via + # -r requirements.in + # azure-storage-blob + # paramiko +cx-oracle==8.3.0 + # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak +distro==1.8.0 + # via docker-compose +dnspython==2.3.0 + # via pymongo +docker[ssh]==6.0.1 + # via + # docker-compose + # testcontainers-core +docker-compose==1.29.2 + # via testcontainers-compose +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.19 + # via + # readme-renderer + # sphinx +ecdsa==0.18.0 + # via python-jose +entrypoints==0.3 + # via flake8 +flake8==3.7.9 + # via -r requirements.in +google-api-core[grpc]==2.10.2 + # via google-cloud-pubsub +google-auth==2.16.2 + # via google-api-core +google-cloud-pubsub==1.7.2 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.58.0 + # via + # google-api-core + # grpc-google-iam-v1 + # grpcio-status +grpc-google-iam-v1==0.12.6 + # via google-cloud-pubsub +grpcio==1.51.3 + # via + # google-api-core + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +grpcio-status==1.48.2 + # via google-api-core +h11==0.14.0 + # via wsproto +idna==3.4 + # via + # requests + # trio +imagesize==1.4.1 + # via sphinx +importlib-metadata==6.0.0 + # via + # keyring + # twine +iniconfig==2.0.0 + # via pytest +isodate==0.6.1 + # via azure-storage-blob +jaraco-classes==3.2.3 + # via keyring +jinja2==3.1.2 + # via sphinx +jsonschema==3.2.0 + # via docker-compose +kafka-python==2.0.2 + # via testcontainers-kafka +keyring==23.13.1 + # via twine +markdown-it-py==2.2.0 + # via rich +markupsafe==2.1.2 + # via jinja2 +mccabe==0.6.1 + # via flake8 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.13 + # via testcontainers-minio +more-itertools==9.1.0 + # via jaraco-classes +neo4j==5.6.0 + # via testcontainers-neo4j +opensearch-py==2.2.0 + # via testcontainers-opensearch +outcome==1.2.0 + # via trio +packaging==23.0 + # via + # deprecation + # docker + # pytest + # sphinx +paramiko==3.0.0 + # via docker +pg8000==1.29.4 + # via -r requirements.in +pika==1.3.1 + # via testcontainers-rabbitmq +pkginfo==1.9.6 + # via twine +pluggy==1.0.0 + # via pytest +protobuf==3.20.3 + # via + # google-api-core + # google-cloud-pubsub + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +psycopg2-binary==2.9.5 + # via testcontainers-postgres +pyasn1==0.4.8 + # via + # pyasn1-modules + # python-jose + # rsa +pyasn1-modules==0.2.8 + # via google-auth +pycodestyle==2.5.0 + # via flake8 +pycparser==2.21 + # via cffi +pyflakes==2.1.1 + # via flake8 +pygments==2.14.0 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 + # via python-arango +pymongo==4.3.3 + # via testcontainers-mongodb +pymssql==2.2.7 + # via testcontainers-mssql +pymysql==1.0.2 + # via testcontainers-mysql +pynacl==1.5.0 + # via paramiko +pyrsistent==0.19.3 + # via jsonschema +pysocks==1.7.1 + # via urllib3 +pytest==7.2.2 + # via + # -r requirements.in + # pytest-cov +pytest-cov==4.0.0 + # via -r requirements.in +python-arango==7.5.7 + # via testcontainers-arangodb +python-dateutil==2.8.2 + # via + # opensearch-py + # pg8000 +python-dotenv==0.21.1 + # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==2.13.2 + # via testcontainers-keycloak +pytz==2022.7.1 + # via + # clickhouse-driver + # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal +pyyaml==5.4.1 + # via docker-compose +readme-renderer==37.3 + # via twine +redis==4.5.1 + # via testcontainers-redis +requests==2.28.2 + # via + # azure-core + # codecov + # docker + # docker-compose + # google-api-core + # opensearch-py + # python-arango + # python-keycloak + # requests-toolbelt + # sphinx + # twine +requests-toolbelt==0.10.1 + # via + # python-arango + # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==13.3.2 + # via twine +rsa==4.9 + # via + # google-auth + # python-jose +scramp==1.4.4 + # via pg8000 +selenium==4.8.2 + # via testcontainers-selenium +six==1.16.0 + # via + # azure-core + # bleach + # dockerpty + # ecdsa + # google-auth + # isodate + # jsonschema + # opensearch-py + # python-dateutil + # websocket-client +sniffio==1.3.0 + # via trio +snowballstemmer==2.2.0 + # via sphinx +sortedcontainers==2.4.0 + # via trio +sphinx==6.1.3 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.4 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.1 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sqlalchemy==2.0.5.post1 + # via + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres +texttable==1.6.7 + # via docker-compose +trio==0.22.0 + # via + # selenium + # trio-websocket +trio-websocket==0.9.2 + # via selenium +twine==4.0.2 + # via -r requirements.in +typing-extensions==4.5.0 + # via + # azure-core + # azure-storage-blob + # sqlalchemy +tzdata==2022.7 + # via pytz-deprecation-shim +tzlocal==4.2 + # via clickhouse-driver +urllib3[socks]==1.26.14 + # via + # docker + # minio + # opensearch-py + # python-arango + # python-keycloak + # requests + # selenium + # twine +webencodings==0.5.1 + # via bleach +websocket-client==0.59.0 + # via + # docker + # docker-compose +wheel==0.38.4 + # via -r requirements.in +wrapt==1.15.0 + # via testcontainers-core +wsproto==1.2.0 + # via trio-websocket +zipp==3.15.0 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# setuptools From c3c293189a775bd27dcddb2031164ae6f8fb1e30 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Thu, 6 Apr 2023 08:32:50 +0200 Subject: [PATCH 227/425] Fix doctest layout for Neo4j --- neo4j/testcontainers/neo4j/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index 264c03da2..72893792d 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -29,9 +29,9 @@ class Neo4jContainer(DbContainer): >>> from testcontainers.neo4j import Neo4jContainer - >>> with Neo4jContainer() as neo4j, \ - neo4j.get_driver() as driver, \ - driver.session() as session: + >>> with Neo4jContainer() as neo4j, \\ + ... neo4j.get_driver() as driver, \\ + ... driver.session() as session: ... result = session.run("MATCH (n) RETURN n LIMIT 1") ... record = result.single() """ From c281a8e34971658e07a5532e1d89b7b0e1cd6e34 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 11 Apr 2023 10:21:47 -0400 Subject: [PATCH 228/425] Change error messages to title case. --- core/testcontainers/core/docker_client.py | 4 ++-- core/testcontainers/core/generic.py | 2 +- core/testcontainers/core/utils.py | 2 +- core/testcontainers/core/waiting_utils.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index e2e4b8b2c..7d5bcf530 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -61,7 +61,7 @@ def port(self, container_id: str, port: int) -> int: """ port_mappings = self.client.api.port(container_id, port) if not port_mappings: - raise ConnectionError(f'port mapping for container {container_id} and port {port} is ' + raise ConnectionError(f'Port mapping for container {container_id} and port {port} is ' 'not available') return port_mappings[0]["HostPort"] @@ -71,7 +71,7 @@ def get_container(self, container_id: str) -> Container: """ containers = self.client.api.containers(filters={'id': container_id}) if not containers: - raise RuntimeError(f'could not get container with id {container_id}') + raise RuntimeError(f'Could not get container with id {container_id}') return containers[0] def bridge_ip(self, container_id: str) -> str: diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 7f84ba6dd..7faac273a 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -42,7 +42,7 @@ def _create_connection_url(self, dialect: str, username: str, password: str, host: Optional[str] = None, port: Optional[int] = None, dbname: Optional[str] = None, **kwargs) -> str: if raise_for_deprecated_parameter(kwargs, "db_name", "dbname"): - raise ValueError(f"unexpected arguments: {','.join(kwargs)}") + raise ValueError(f"Unexpected arguments: {','.join(kwargs)}") if self._container is None: raise ContainerStartException("container has not been started") host = host or self.get_container_host_ip() diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index 8664f9447..d8b288c7d 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -76,5 +76,5 @@ def raise_for_deprecated_parameter(kwargs: dict, name: str, replacement: str) -> Raise an error if a dictionary of keyword arguments contains a key and suggest the replacement. """ if kwargs.pop(name, None): - raise ValueError(f"use `{replacement}` instead of `{name}`") + raise ValueError(f"Use `{replacement}` instead of `{name}`") return kwargs diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 17c9f471d..26a88d847 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -102,6 +102,6 @@ def wait_for_logs(container: "DockerContainer", predicate: Union[Callable, str], if predicate(stdout) or predicate(stderr): return duration if timeout and duration > timeout: - raise TimeoutError("container did not emit logs satisfying predicate in %.3f seconds" - % timeout) + raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " + "seconds") time.sleep(interval) From 6c653f7360ff340a0843e175f93297067e356f77 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 11 Apr 2023 10:58:16 -0400 Subject: [PATCH 229/425] Xfail docker-in-docker test. --- core/tests/test_docker_in_docker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/tests/test_docker_in_docker.py b/core/tests/test_docker_in_docker.py index 66c8a4a96..b048a78ab 100644 --- a/core/tests/test_docker_in_docker.py +++ b/core/tests/test_docker_in_docker.py @@ -1,8 +1,10 @@ +import pytest from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient from testcontainers.core.waiting_utils import wait_for_logs +@pytest.mark.xfail(reason="https://github.com/docker/docker-py/issues/2717") def test_wait_for_logs_docker_in_docker(): # real dind isn't possible (AFAIK) in CI # forwarding the socket to a container port is at least somewhat the same From 1be54d4a21cd4725f9bc93a704d9be966ac09dfa Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 11 Apr 2023 10:58:22 -0400 Subject: [PATCH 230/425] Update requirements. --- requirements/3.10.txt | 101 +++++++++++++++++++---------------------- requirements/3.7.txt | 100 +++++++++++++++++++---------------------- requirements/3.8.txt | 102 ++++++++++++++++++++---------------------- requirements/3.9.txt | 101 +++++++++++++++++++---------------------- 4 files changed, 189 insertions(+), 215 deletions(-) diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 85903304c..b56a20adb 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -78,24 +78,19 @@ alabaster==0.7.13 asn1crypto==1.5.1 # via scramp async-generator==1.10 - # via - # trio - # trio-websocket + # via trio async-timeout==4.0.2 # via redis attrs==22.2.0 # via # jsonschema # outcome - # pytest # trio -azure-core==1.26.3 - # via - # azure-storage-blob - # msrest -azure-storage-blob==12.14.1 +azure-core==1.26.4 + # via azure-storage-blob +azure-storage-blob==12.15.0 # via testcontainers-azurite -babel==2.11.0 +babel==2.12.1 # via sphinx bcrypt==4.0.1 # via paramiko @@ -106,7 +101,6 @@ cachetools==5.3.0 certifi==2022.12.7 # via # minio - # msrest # opensearch-py # requests # selenium @@ -114,13 +108,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==3.0.1 +charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -coverage[toml]==7.1.0 +coverage[toml]==7.2.3 # via # codecov # pytest-cov @@ -132,6 +126,8 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak distro==1.8.0 # via docker-compose dnspython==2.3.0 @@ -154,19 +150,20 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.0 +exceptiongroup==1.1.1 # via # pytest # trio + # trio-websocket flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.16.0 +google-auth==2.17.2 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.58.0 +googleapis-common-protos[grpc]==1.59.0 # via # google-api-core # grpc-google-iam-v1 @@ -175,7 +172,7 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.51.1 +grpcio==1.53.0 # via # google-api-core # googleapis-common-protos @@ -191,14 +188,14 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.0.0 +importlib-metadata==6.3.0 # via # keyring # twine iniconfig==2.0.0 # via pytest isodate==0.6.1 - # via msrest + # via azure-storage-blob jaraco-classes==3.2.3 # via keyring jeepney==0.8.0 @@ -213,7 +210,7 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markdown-it-py==2.1.0 +markdown-it-py==2.2.0 # via rich markupsafe==2.1.2 # via jinja2 @@ -221,26 +218,23 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.13 +minio==7.1.14 # via testcontainers-minio -more-itertools==9.0.0 +more-itertools==9.1.0 # via jaraco-classes -msrest==0.7.1 - # via azure-storage-blob -neo4j==5.5.0 +neo4j==5.7.0 # via testcontainers-neo4j -oauthlib==3.2.2 - # via requests-oauthlib -opensearch-py==2.1.1 +opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==23.0 # via + # deprecation # docker # pytest # sphinx -paramiko==3.0.0 +paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in @@ -257,7 +251,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -psycopg2-binary==2.9.5 +psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 # via @@ -272,7 +266,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.14.0 +pygments==2.15.0 # via # readme-renderer # rich @@ -283,7 +277,7 @@ pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.2 +pymysql==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -291,25 +285,26 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.1 +pytest==7.3.0 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.6 +python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 - # via pg8000 + # via + # opensearch-py + # pg8000 python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.12.0 +python-keycloak==2.15.3 # via testcontainers-keycloak -pytz==2022.7.1 +pytz==2023.3 # via - # babel # clickhouse-driver # neo4j pytz-deprecation-shim==0.1.0.post0 @@ -318,7 +313,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.1 +redis==4.5.4 # via testcontainers-redis requests==2.28.2 # via @@ -327,24 +322,20 @@ requests==2.28.2 # docker # docker-compose # google-api-core - # msrest # opensearch-py # python-arango # python-keycloak - # requests-oauthlib # requests-toolbelt # sphinx # twine -requests-oauthlib==1.3.1 - # via msrest -requests-toolbelt==0.9.1 +requests-toolbelt==0.10.1 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.1 +rich==13.3.3 # via twine rsa==4.9 # via @@ -354,7 +345,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.0 +selenium==4.8.3 # via testcontainers-selenium six==1.16.0 # via @@ -365,6 +356,7 @@ six==1.16.0 # google-auth # isodate # jsonschema + # opensearch-py # python-dateutil # websocket-client sniffio==1.3.0 @@ -387,7 +379,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.3 +sqlalchemy==2.0.9 # via # testcontainers-mssql # testcontainers-mysql @@ -403,19 +395,20 @@ trio==0.22.0 # via # selenium # trio-websocket -trio-websocket==0.9.2 +trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in typing-extensions==4.5.0 # via # azure-core + # azure-storage-blob # sqlalchemy -tzdata==2022.7 +tzdata==2023.3 # via pytz-deprecation-shim -tzlocal==4.2 +tzlocal==4.3 # via clickhouse-driver -urllib3[socks]==1.26.14 +urllib3[socks]==1.26.15 # via # docker # minio @@ -431,13 +424,13 @@ websocket-client==0.59.0 # via # docker # docker-compose -wheel==0.38.4 +wheel==0.40.0 # via -r requirements.in -wrapt==1.14.1 +wrapt==1.15.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.13.0 +zipp==3.15.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 18dd9d49a..3d0119f6f 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -78,24 +78,19 @@ alabaster==0.7.13 asn1crypto==1.5.1 # via scramp async-generator==1.10 - # via - # trio - # trio-websocket + # via trio async-timeout==4.0.2 # via redis attrs==22.2.0 # via # jsonschema # outcome - # pytest # trio -azure-core==1.26.3 - # via - # azure-storage-blob - # msrest -azure-storage-blob==12.14.1 +azure-core==1.26.4 + # via azure-storage-blob +azure-storage-blob==12.15.0 # via testcontainers-azurite -babel==2.11.0 +babel==2.12.1 # via sphinx backports-zoneinfo==0.2.1 # via @@ -112,7 +107,6 @@ cachetools==5.3.0 certifi==2022.12.7 # via # minio - # msrest # opensearch-py # requests # selenium @@ -120,13 +114,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==3.0.1 +charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -coverage[toml]==7.1.0 +coverage[toml]==7.2.3 # via # codecov # pytest-cov @@ -138,6 +132,8 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak distro==1.8.0 # via docker-compose dnspython==2.3.0 @@ -160,19 +156,20 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.0 +exceptiongroup==1.1.1 # via # pytest # trio + # trio-websocket flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.16.0 +google-auth==2.17.2 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.58.0 +googleapis-common-protos[grpc]==1.59.0 # via # google-api-core # grpc-google-iam-v1 @@ -181,7 +178,7 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.51.1 +grpcio==1.53.0 # via # google-api-core # googleapis-common-protos @@ -197,7 +194,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.0.0 +importlib-metadata==6.3.0 # via # jsonschema # keyring @@ -209,12 +206,12 @@ importlib-metadata==6.0.0 # sphinx # sqlalchemy # twine -importlib-resources==5.10.2 +importlib-resources==5.12.0 # via keyring iniconfig==2.0.0 # via pytest isodate==0.6.1 - # via msrest + # via azure-storage-blob jaraco-classes==3.2.3 # via keyring jeepney==0.8.0 @@ -229,7 +226,7 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markdown-it-py==2.1.0 +markdown-it-py==2.2.0 # via rich markupsafe==2.1.2 # via jinja2 @@ -237,26 +234,23 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.13 +minio==7.1.14 # via testcontainers-minio -more-itertools==9.0.0 +more-itertools==9.1.0 # via jaraco-classes -msrest==0.7.1 - # via azure-storage-blob -neo4j==5.5.0 +neo4j==5.7.0 # via testcontainers-neo4j -oauthlib==3.2.2 - # via requests-oauthlib -opensearch-py==2.1.1 +opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==23.0 # via + # deprecation # docker # pytest # sphinx -paramiko==3.0.0 +paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in @@ -273,7 +267,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -psycopg2-binary==2.9.5 +psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 # via @@ -288,7 +282,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.14.0 +pygments==2.15.0 # via # readme-renderer # rich @@ -299,7 +293,7 @@ pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.2 +pymysql==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -307,7 +301,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.1 +pytest==7.3.0 # via # -r requirements.in # pytest-cov @@ -316,14 +310,16 @@ pytest-cov==4.0.0 python-arango==7.5.6 # via testcontainers-arangodb python-dateutil==2.8.2 - # via pg8000 + # via + # opensearch-py + # pg8000 python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.12.0 +python-keycloak==2.15.3 # via testcontainers-keycloak -pytz==2022.7.1 +pytz==2023.3 # via # babel # clickhouse-driver @@ -334,7 +330,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.1 +redis==4.5.4 # via testcontainers-redis requests==2.28.2 # via @@ -343,24 +339,20 @@ requests==2.28.2 # docker # docker-compose # google-api-core - # msrest # opensearch-py # python-arango # python-keycloak - # requests-oauthlib # requests-toolbelt # sphinx # twine -requests-oauthlib==1.3.1 - # via msrest -requests-toolbelt==0.9.1 +requests-toolbelt==0.10.1 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.1 +rich==13.3.3 # via twine rsa==4.9 # via @@ -370,7 +362,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.0 +selenium==4.8.3 # via testcontainers-selenium six==1.16.0 # via @@ -381,6 +373,7 @@ six==1.16.0 # google-auth # isodate # jsonschema + # opensearch-py # python-dateutil # websocket-client sniffio==1.3.0 @@ -403,7 +396,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.3 +sqlalchemy==2.0.9 # via # testcontainers-mssql # testcontainers-mysql @@ -419,7 +412,7 @@ trio==0.22.0 # via # selenium # trio-websocket -trio-websocket==0.9.2 +trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in @@ -427,17 +420,18 @@ typing-extensions==4.5.0 # via # async-timeout # azure-core + # azure-storage-blob # h11 # importlib-metadata # markdown-it-py # redis # rich # sqlalchemy -tzdata==2022.7 +tzdata==2023.3 # via pytz-deprecation-shim -tzlocal==4.2 +tzlocal==4.3 # via clickhouse-driver -urllib3[socks]==1.26.14 +urllib3[socks]==1.26.15 # via # docker # minio @@ -453,13 +447,13 @@ websocket-client==0.59.0 # via # docker # docker-compose -wheel==0.38.4 +wheel==0.40.0 # via -r requirements.in -wrapt==1.14.1 +wrapt==1.15.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.13.0 +zipp==3.15.0 # via # importlib-metadata # importlib-resources diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 79810f5ae..9c684faa9 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -78,24 +78,19 @@ alabaster==0.7.13 asn1crypto==1.5.1 # via scramp async-generator==1.10 - # via - # trio - # trio-websocket + # via trio async-timeout==4.0.2 # via redis attrs==22.2.0 # via # jsonschema # outcome - # pytest # trio -azure-core==1.26.3 - # via - # azure-storage-blob - # msrest -azure-storage-blob==12.14.1 +azure-core==1.26.4 + # via azure-storage-blob +azure-storage-blob==12.15.0 # via testcontainers-azurite -babel==2.11.0 +babel==2.12.1 # via sphinx backports-zoneinfo==0.2.1 # via @@ -110,7 +105,6 @@ cachetools==5.3.0 certifi==2022.12.7 # via # minio - # msrest # opensearch-py # requests # selenium @@ -118,13 +112,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==3.0.1 +charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -coverage[toml]==7.1.0 +coverage[toml]==7.2.3 # via # codecov # pytest-cov @@ -136,6 +130,8 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak distro==1.8.0 # via docker-compose dnspython==2.3.0 @@ -158,19 +154,20 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.0 +exceptiongroup==1.1.1 # via # pytest # trio + # trio-websocket flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.16.0 +google-auth==2.17.2 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.58.0 +googleapis-common-protos[grpc]==1.59.0 # via # google-api-core # grpc-google-iam-v1 @@ -179,7 +176,7 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.51.1 +grpcio==1.53.0 # via # google-api-core # googleapis-common-protos @@ -195,17 +192,17 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.0.0 +importlib-metadata==6.3.0 # via # keyring # sphinx # twine -importlib-resources==5.10.2 +importlib-resources==5.12.0 # via keyring iniconfig==2.0.0 # via pytest isodate==0.6.1 - # via msrest + # via azure-storage-blob jaraco-classes==3.2.3 # via keyring jeepney==0.8.0 @@ -220,7 +217,7 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markdown-it-py==2.1.0 +markdown-it-py==2.2.0 # via rich markupsafe==2.1.2 # via jinja2 @@ -228,26 +225,23 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.13 +minio==7.1.14 # via testcontainers-minio -more-itertools==9.0.0 +more-itertools==9.1.0 # via jaraco-classes -msrest==0.7.1 - # via azure-storage-blob -neo4j==5.5.0 +neo4j==5.7.0 # via testcontainers-neo4j -oauthlib==3.2.2 - # via requests-oauthlib -opensearch-py==2.1.1 +opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==23.0 # via + # deprecation # docker # pytest # sphinx -paramiko==3.0.0 +paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in @@ -264,7 +258,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -psycopg2-binary==2.9.5 +psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 # via @@ -279,7 +273,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.14.0 +pygments==2.15.0 # via # readme-renderer # rich @@ -290,7 +284,7 @@ pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.2 +pymysql==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -298,23 +292,25 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.1 +pytest==7.3.0 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.6 +python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 - # via pg8000 + # via + # opensearch-py + # pg8000 python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.12.0 +python-keycloak==2.15.3 # via testcontainers-keycloak -pytz==2022.7.1 +pytz==2023.3 # via # babel # clickhouse-driver @@ -325,7 +321,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.1 +redis==4.5.4 # via testcontainers-redis requests==2.28.2 # via @@ -334,24 +330,20 @@ requests==2.28.2 # docker # docker-compose # google-api-core - # msrest # opensearch-py # python-arango # python-keycloak - # requests-oauthlib # requests-toolbelt # sphinx # twine -requests-oauthlib==1.3.1 - # via msrest -requests-toolbelt==0.9.1 +requests-toolbelt==0.10.1 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.1 +rich==13.3.3 # via twine rsa==4.9 # via @@ -361,7 +353,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.0 +selenium==4.8.3 # via testcontainers-selenium six==1.16.0 # via @@ -372,6 +364,7 @@ six==1.16.0 # google-auth # isodate # jsonschema + # opensearch-py # python-dateutil # websocket-client sniffio==1.3.0 @@ -394,7 +387,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.3 +sqlalchemy==2.0.9 # via # testcontainers-mssql # testcontainers-mysql @@ -410,20 +403,21 @@ trio==0.22.0 # via # selenium # trio-websocket -trio-websocket==0.9.2 +trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in typing-extensions==4.5.0 # via # azure-core + # azure-storage-blob # rich # sqlalchemy -tzdata==2022.7 +tzdata==2023.3 # via pytz-deprecation-shim -tzlocal==4.2 +tzlocal==4.3 # via clickhouse-driver -urllib3[socks]==1.26.14 +urllib3[socks]==1.26.15 # via # docker # minio @@ -439,13 +433,13 @@ websocket-client==0.59.0 # via # docker # docker-compose -wheel==0.38.4 +wheel==0.40.0 # via -r requirements.in -wrapt==1.14.1 +wrapt==1.15.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.13.0 +zipp==3.15.0 # via # importlib-metadata # importlib-resources diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 7da5249fb..aa16500b9 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -78,24 +78,19 @@ alabaster==0.7.13 asn1crypto==1.5.1 # via scramp async-generator==1.10 - # via - # trio - # trio-websocket + # via trio async-timeout==4.0.2 # via redis attrs==22.2.0 # via # jsonschema # outcome - # pytest # trio -azure-core==1.26.3 - # via - # azure-storage-blob - # msrest -azure-storage-blob==12.14.1 +azure-core==1.26.4 + # via azure-storage-blob +azure-storage-blob==12.15.0 # via testcontainers-azurite -babel==2.11.0 +babel==2.12.1 # via sphinx bcrypt==4.0.1 # via paramiko @@ -106,7 +101,6 @@ cachetools==5.3.0 certifi==2022.12.7 # via # minio - # msrest # opensearch-py # requests # selenium @@ -114,13 +108,13 @@ cffi==1.15.1 # via # cryptography # pynacl -charset-normalizer==3.0.1 +charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse codecov==2.1.12 # via -r requirements.in -coverage[toml]==7.1.0 +coverage[toml]==7.2.3 # via # codecov # pytest-cov @@ -132,6 +126,8 @@ cryptography==36.0.2 # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak distro==1.8.0 # via docker-compose dnspython==2.3.0 @@ -154,19 +150,20 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.0 +exceptiongroup==1.1.1 # via # pytest # trio + # trio-websocket flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.16.0 +google-auth==2.17.2 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.58.0 +googleapis-common-protos[grpc]==1.59.0 # via # google-api-core # grpc-google-iam-v1 @@ -175,7 +172,7 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.51.1 +grpcio==1.53.0 # via # google-api-core # googleapis-common-protos @@ -191,7 +188,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.0.0 +importlib-metadata==6.3.0 # via # keyring # sphinx @@ -199,7 +196,7 @@ importlib-metadata==6.0.0 iniconfig==2.0.0 # via pytest isodate==0.6.1 - # via msrest + # via azure-storage-blob jaraco-classes==3.2.3 # via keyring jeepney==0.8.0 @@ -214,7 +211,7 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==23.13.1 # via twine -markdown-it-py==2.1.0 +markdown-it-py==2.2.0 # via rich markupsafe==2.1.2 # via jinja2 @@ -222,26 +219,23 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.13 +minio==7.1.14 # via testcontainers-minio -more-itertools==9.0.0 +more-itertools==9.1.0 # via jaraco-classes -msrest==0.7.1 - # via azure-storage-blob -neo4j==5.5.0 +neo4j==5.7.0 # via testcontainers-neo4j -oauthlib==3.2.2 - # via requests-oauthlib -opensearch-py==2.1.1 +opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio packaging==23.0 # via + # deprecation # docker # pytest # sphinx -paramiko==3.0.0 +paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in @@ -258,7 +252,7 @@ protobuf==3.20.3 # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -psycopg2-binary==2.9.5 +psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 # via @@ -273,7 +267,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.14.0 +pygments==2.15.0 # via # readme-renderer # rich @@ -284,7 +278,7 @@ pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.2 +pymysql==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -292,25 +286,26 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.1 +pytest==7.3.0 # via # -r requirements.in # pytest-cov pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.5.6 +python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 - # via pg8000 + # via + # opensearch-py + # pg8000 python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.12.0 +python-keycloak==2.15.3 # via testcontainers-keycloak -pytz==2022.7.1 +pytz==2023.3 # via - # babel # clickhouse-driver # neo4j pytz-deprecation-shim==0.1.0.post0 @@ -319,7 +314,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.1 +redis==4.5.4 # via testcontainers-redis requests==2.28.2 # via @@ -328,24 +323,20 @@ requests==2.28.2 # docker # docker-compose # google-api-core - # msrest # opensearch-py # python-arango # python-keycloak - # requests-oauthlib # requests-toolbelt # sphinx # twine -requests-oauthlib==1.3.1 - # via msrest -requests-toolbelt==0.9.1 +requests-toolbelt==0.10.1 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.1 +rich==13.3.3 # via twine rsa==4.9 # via @@ -355,7 +346,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.0 +selenium==4.8.3 # via testcontainers-selenium six==1.16.0 # via @@ -366,6 +357,7 @@ six==1.16.0 # google-auth # isodate # jsonschema + # opensearch-py # python-dateutil # websocket-client sniffio==1.3.0 @@ -388,7 +380,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.3 +sqlalchemy==2.0.9 # via # testcontainers-mssql # testcontainers-mysql @@ -404,19 +396,20 @@ trio==0.22.0 # via # selenium # trio-websocket -trio-websocket==0.9.2 +trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in typing-extensions==4.5.0 # via # azure-core + # azure-storage-blob # sqlalchemy -tzdata==2022.7 +tzdata==2023.3 # via pytz-deprecation-shim -tzlocal==4.2 +tzlocal==4.3 # via clickhouse-driver -urllib3[socks]==1.26.14 +urllib3[socks]==1.26.15 # via # docker # minio @@ -432,13 +425,13 @@ websocket-client==0.59.0 # via # docker # docker-compose -wheel==0.38.4 +wheel==0.40.0 # via -r requirements.in -wrapt==1.14.1 +wrapt==1.15.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.13.0 +zipp==3.15.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: From 703d97574c5d284a45af20a57a8f882571efdfcf Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 11 Apr 2023 11:24:48 -0400 Subject: [PATCH 231/425] Add missing colon to error message. --- core/testcontainers/core/waiting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 26a88d847..933a417c0 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -64,7 +64,7 @@ def wrapper(wrapped: Callable, instance: Any, args: Iterable, kwargs: Mapping) - time.sleep(config.SLEEP_TIME) exception = e raise TimeoutError( - f'Wait time ({config.TIMEOUT}s) exceeded for {wrapped.__name__}(args: {args}, kwargs ' + f'Wait time ({config.TIMEOUT}s) exceeded for {wrapped.__name__}(args: {args}, kwargs: ' f'{kwargs}). Exception: {exception}' ) From cfb2802bef9e706433cb87cf38047d88b092f130 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 11 Apr 2023 11:25:07 -0400 Subject: [PATCH 232/425] Allow positional port argument for `Neo4jContainer`. --- neo4j/testcontainers/neo4j/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neo4j/testcontainers/neo4j/__init__.py b/neo4j/testcontainers/neo4j/__init__.py index 21a78d802..cf76df501 100644 --- a/neo4j/testcontainers/neo4j/__init__.py +++ b/neo4j/testcontainers/neo4j/__init__.py @@ -38,7 +38,7 @@ class Neo4jContainer(DbContainer): ... result = session.run("MATCH (n) RETURN n LIMIT 1") ... record = result.single() """ - def __init__(self, image: str = "neo4j:latest", *, port: int = 7687, + def __init__(self, image: str = "neo4j:latest", port: int = 7687, password: Optional[str] = None, username: Optional[str] = None, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "bolt_port", "port") super(Neo4jContainer, self).__init__(image, **kwargs) From f5cc8f9bed418882b88fc08b4cf166e3b8da2ff5 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 14:39:17 -0400 Subject: [PATCH 233/425] Add workflow to build requirements. --- .github/workflows/requirements.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/requirements.yml diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml new file mode 100644 index 000000000..ac7804521 --- /dev/null +++ b/.github/workflows/requirements.yml @@ -0,0 +1,30 @@ +name: testcontainers requirements +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + requirements: + strategy: + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Update pip and install pip-tools + run: pip install --upgrade pip pip-tools + - name: Build requirements + run: | + rm requirements/${{ matrix.python-version }}.txt + pip-compile --resolver=backtracking -v --upgrade -o requirements/${{ matrix.python-version }}.txt From beb5c63c9e408e01c23c3424bb8b52a31494fe25 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 14:43:18 -0400 Subject: [PATCH 234/425] Remove codecov requirement. --- requirements.in | 1 - requirements/3.10.txt | 4 ---- requirements/3.11.txt | 4 ---- requirements/3.7.txt | 4 ---- requirements/3.8.txt | 4 ---- requirements/3.9.txt | 4 ---- 6 files changed, 21 deletions(-) diff --git a/requirements.in b/requirements.in index 0204fdb8e..e9e122610 100644 --- a/requirements.in +++ b/requirements.in @@ -21,7 +21,6 @@ -e file:rabbitmq -e file:redis -e file:selenium -codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pg8000 diff --git a/requirements/3.10.txt b/requirements/3.10.txt index b56a20adb..410db34a3 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -112,11 +112,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 - # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -318,7 +315,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.11.txt b/requirements/3.11.txt index b0ff13e91..812a888a7 100644 --- a/requirements/3.11.txt +++ b/requirements/3.11.txt @@ -115,11 +115,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 - # via -r requirements.in coverage[toml]==7.2.1 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -309,7 +306,6 @@ redis==4.5.1 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 3d0119f6f..38f8d222b 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -118,11 +118,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 - # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -335,7 +332,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 9c684faa9..ff16696cb 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -116,11 +116,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 - # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -326,7 +323,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.9.txt b/requirements/3.9.txt index aa16500b9..52bdafbcf 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -112,11 +112,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 - # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -319,7 +316,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core From 9792858261feccafb2ac87c3af53729a58df0890 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 14:46:09 -0400 Subject: [PATCH 235/425] Store requirements as build artifact. --- .github/workflows/requirements.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index ac7804521..e0596371b 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -28,3 +28,8 @@ jobs: run: | rm requirements/${{ matrix.python-version }}.txt pip-compile --resolver=backtracking -v --upgrade -o requirements/${{ matrix.python-version }}.txt + - name: Store requirements as artifact + uses: actions/upload-artifact@v3 + with: + name: requirements/${{ matrix.python-version }}.txt + path: requirements/${{ matrix.python-version }}.txt From a37fd500c210e8b25aa362c78a36c4285eeb3885 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 14:55:01 -0400 Subject: [PATCH 236/425] Remove slash from artifact name. --- .github/workflows/requirements.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index e0596371b..684447d4d 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -31,5 +31,5 @@ jobs: - name: Store requirements as artifact uses: actions/upload-artifact@v3 with: - name: requirements/${{ matrix.python-version }}.txt + name: requirements-${{ matrix.python-version }}.txt path: requirements/${{ matrix.python-version }}.txt From 07909ef2da92601f4ecb084f6d01ed0657f6ef55 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 15:35:21 -0400 Subject: [PATCH 237/425] Remove `codecov` requirement. --- requirements.in | 1 - requirements/3.10.txt | 3 --- requirements/3.11.txt | 3 --- requirements/3.7.txt | 4 ---- requirements/3.8.txt | 3 --- requirements/3.9.txt | 3 --- 6 files changed, 17 deletions(-) diff --git a/requirements.in b/requirements.in index 0204fdb8e..e9e122610 100644 --- a/requirements.in +++ b/requirements.in @@ -21,7 +21,6 @@ -e file:rabbitmq -e file:redis -e file:selenium -codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pg8000 diff --git a/requirements/3.10.txt b/requirements/3.10.txt index b56a20adb..a617d64c1 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -112,11 +112,9 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -318,7 +316,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.11.txt b/requirements/3.11.txt index b0ff13e91..4eb07f9ab 100644 --- a/requirements/3.11.txt +++ b/requirements/3.11.txt @@ -115,11 +115,9 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 # via -r requirements.in coverage[toml]==7.2.1 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -309,7 +307,6 @@ redis==4.5.1 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 3d0119f6f..38f8d222b 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -118,11 +118,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 - # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -335,7 +332,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 9c684faa9..e0b5adfdd 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -116,11 +116,9 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -326,7 +324,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core diff --git a/requirements/3.9.txt b/requirements/3.9.txt index aa16500b9..33e7a8220 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -112,11 +112,9 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse -codecov==2.1.12 # via -r requirements.in coverage[toml]==7.2.3 # via - # codecov # pytest-cov cryptography==36.0.2 # via @@ -319,7 +317,6 @@ redis==4.5.4 requests==2.28.2 # via # azure-core - # codecov # docker # docker-compose # google-api-core From 934c8d884ff513d3ede3b7faab2860f110799585 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 16:32:28 -0400 Subject: [PATCH 238/425] Update google-cloud-pubsub to major version 2. --- google/setup.py | 2 +- google/testcontainers/google/pubsub.py | 18 +++--- google/tests/test_google.py | 7 ++- requirements/3.10.txt | 22 ++++--- requirements/3.11.txt | 86 +++++++++++++++----------- requirements/3.7.txt | 21 ++++--- requirements/3.8.txt | 22 ++++--- requirements/3.9.txt | 22 ++++--- 8 files changed, 114 insertions(+), 86 deletions(-) diff --git a/google/setup.py b/google/setup.py index a772c93a9..10a1247dc 100644 --- a/google/setup.py +++ b/google/setup.py @@ -12,7 +12,7 @@ url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "testcontainers-core", - "google-cloud-pubsub < 2", + "google-cloud-pubsub>=2", ], python_requires=">=3.7", ) diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 39a691368..6a52b2a32 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -11,9 +11,10 @@ # License for the specific language governing permissions and limitations # under the License. from google.cloud import pubsub -import grpc -from typing import Optional +import os from testcontainers.core.container import DockerContainer +from typing import Type +from unittest.mock import patch class PubSubContainer(DockerContainer): @@ -49,15 +50,12 @@ def __init__(self, image: str = "google/cloud-sdk:emulators", project: str = "te def get_pubsub_emulator_host(self) -> str: return f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" - def _get_channel(self, channel: Optional[grpc.Channel] = None) -> grpc.Channel: - if channel is None: - return grpc.insecure_channel(target=self.get_pubsub_emulator_host()) - return channel + def _get_client(self, cls: Type, **kwargs) -> dict: + with patch.dict(os.environ, PUBSUB_EMULATOR_HOST=self.get_pubsub_emulator_host()): + return cls(**kwargs) def get_publisher_client(self, **kwargs) -> pubsub.PublisherClient: - kwargs['channel'] = self._get_channel(kwargs.get('channel')) - return pubsub.PublisherClient(**kwargs) + return self._get_client(pubsub.PublisherClient, **kwargs) def get_subscriber_client(self, **kwargs) -> pubsub.SubscriberClient: - kwargs['channel'] = self._get_channel(kwargs.get('channel')) - return pubsub.SubscriberClient(**kwargs) + return self._get_client(pubsub.SubscriberClient, **kwargs) diff --git a/google/tests/test_google.py b/google/tests/test_google.py index 48c920181..6fa506e26 100644 --- a/google/tests/test_google.py +++ b/google/tests/test_google.py @@ -4,18 +4,19 @@ def test_pubsub_container(): + pubsub: PubSubContainer with PubSubContainer() as pubsub: - wait_for_logs(pubsub, r"Server started, listening on \d+", timeout=10) + wait_for_logs(pubsub, r"Server started, listening on \d+", timeout=60) # Create a new topic publisher = pubsub.get_publisher_client() topic_path = publisher.topic_path(pubsub.project, "my-topic") - publisher.create_topic(topic_path) + publisher.create_topic(name=topic_path) # Create a subscription subscriber = pubsub.get_subscriber_client() subscription_path = subscriber.subscription_path(pubsub.project, "my-subscription") - subscriber.create_subscription(subscription_path, topic_path) + subscriber.create_subscription(name=subscription_path, topic=topic_path) # Publish a message publisher.publish(topic_path, b"Hello world!") diff --git a/requirements/3.10.txt b/requirements/3.10.txt index a617d64c1..b4283ec77 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -112,10 +112,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse - # via -r requirements.in coverage[toml]==7.2.3 - # via - # pytest-cov + # via pytest-cov cryptography==36.0.2 # via # -r requirements.in @@ -159,7 +157,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.2 # via google-api-core -google-cloud-pubsub==1.7.2 +google-cloud-pubsub==2.16.0 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -173,11 +171,14 @@ grpc-google-iam-v1==0.12.6 grpcio==1.53.0 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.2 - # via google-api-core +grpcio-status==1.53.0 + # via + # google-api-core + # google-cloud-pubsub h11==0.14.0 # via wsproto idna==3.4 @@ -226,7 +227,7 @@ opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==23.0 +packaging==23.1 # via # deprecation # docker @@ -242,13 +243,16 @@ pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.3 +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.1 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status + # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 @@ -332,7 +336,7 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.3 +rich==13.3.4 # via twine rsa==4.9 # via diff --git a/requirements/3.11.txt b/requirements/3.11.txt index 4eb07f9ab..8a8f30206 100644 --- a/requirements/3.11.txt +++ b/requirements/3.11.txt @@ -78,18 +78,15 @@ alabaster==0.7.13 asn1crypto==1.5.1 # via scramp async-generator==1.10 - # via - # trio - # trio-websocket + # via trio async-timeout==4.0.2 # via redis attrs==22.2.0 # via # jsonschema # outcome - # pytest # trio -azure-core==1.26.3 +azure-core==1.26.4 # via azure-storage-blob azure-storage-blob==12.15.0 # via testcontainers-azurite @@ -115,15 +112,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse - # via -r requirements.in -coverage[toml]==7.2.1 - # via - # pytest-cov +coverage[toml]==7.2.3 + # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 @@ -150,29 +146,36 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.1.1 + # via trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.10.2 +google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.16.2 +google-auth==2.17.2 # via google-api-core -google-cloud-pubsub==1.7.2 +google-cloud-pubsub==2.16.0 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.58.0 +googleapis-common-protos[grpc]==1.59.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status +greenlet==2.0.2 + # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.51.3 +grpcio==1.53.0 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.2 - # via google-api-core +grpcio-status==1.53.0 + # via + # google-api-core + # google-cloud-pubsub h11==0.14.0 # via wsproto idna==3.4 @@ -181,7 +184,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.0.0 +importlib-metadata==6.3.0 # via # keyring # twine @@ -191,6 +194,10 @@ isodate==0.6.1 # via azure-storage-blob jaraco-classes==3.2.3 # via keyring +jeepney==0.8.0 + # via + # keyring + # secretstorage jinja2==3.1.2 # via sphinx jsonschema==3.2.0 @@ -207,23 +214,23 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.13 +minio==7.1.14 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.6.0 +neo4j==5.7.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==23.0 +packaging==23.1 # via # deprecation # docker # pytest # sphinx -paramiko==3.0.0 +paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in @@ -233,14 +240,17 @@ pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.3 +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.1 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -psycopg2-binary==2.9.5 + # proto-plus +psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 # via @@ -255,7 +265,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.14.0 +pygments==2.15.0 # via # readme-renderer # rich @@ -266,7 +276,7 @@ pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.2 +pymysql==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -274,7 +284,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.2.2 +pytest==7.3.0 # via # -r requirements.in # pytest-cov @@ -290,9 +300,9 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.13.2 +python-keycloak==2.15.3 # via testcontainers-keycloak -pytz==2022.7.1 +pytz==2023.3 # via # clickhouse-driver # neo4j @@ -302,7 +312,7 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.1 +redis==4.5.4 # via testcontainers-redis requests==2.28.2 # via @@ -323,7 +333,7 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.2 +rich==13.3.4 # via twine rsa==4.9 # via @@ -331,7 +341,9 @@ rsa==4.9 # python-jose scramp==1.4.4 # via pg8000 -selenium==4.8.2 +secretstorage==3.3.3 + # via keyring +selenium==4.8.3 # via testcontainers-selenium six==1.16.0 # via @@ -365,7 +377,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.5.post1 +sqlalchemy==2.0.9 # via # testcontainers-mssql # testcontainers-mysql @@ -377,7 +389,7 @@ trio==0.22.0 # via # selenium # trio-websocket -trio-websocket==0.9.2 +trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in @@ -386,11 +398,11 @@ typing-extensions==4.5.0 # azure-core # azure-storage-blob # sqlalchemy -tzdata==2022.7 +tzdata==2023.3 # via pytz-deprecation-shim -tzlocal==4.2 +tzlocal==4.3 # via clickhouse-driver -urllib3[socks]==1.26.14 +urllib3[socks]==1.26.15 # via # docker # minio @@ -406,7 +418,7 @@ websocket-client==0.59.0 # via # docker # docker-compose -wheel==0.38.4 +wheel==0.40.0 # via -r requirements.in wrapt==1.15.0 # via testcontainers-core diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 38f8d222b..f4741af6c 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -119,8 +119,7 @@ charset-normalizer==3.1.0 clickhouse-driver==0.2.5 # via testcontainers-clickhouse coverage[toml]==7.2.3 - # via - # pytest-cov + # via pytest-cov cryptography==36.0.2 # via # -r requirements.in @@ -164,7 +163,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.2 # via google-api-core -google-cloud-pubsub==1.7.2 +google-cloud-pubsub==2.16.0 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -178,11 +177,14 @@ grpc-google-iam-v1==0.12.6 grpcio==1.53.0 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.2 - # via google-api-core +grpcio-status==1.53.0 + # via + # google-api-core + # google-cloud-pubsub h11==0.14.0 # via wsproto idna==3.4 @@ -241,7 +243,7 @@ opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==23.0 +packaging==23.1 # via # deprecation # docker @@ -257,13 +259,16 @@ pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.3 +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.1 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status + # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 @@ -348,7 +353,7 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.3 +rich==13.3.4 # via twine rsa==4.9 # via diff --git a/requirements/3.8.txt b/requirements/3.8.txt index e0b5adfdd..2f4c341dc 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -116,10 +116,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse - # via -r requirements.in coverage[toml]==7.2.3 - # via - # pytest-cov + # via pytest-cov cryptography==36.0.2 # via # -r requirements.in @@ -163,7 +161,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.2 # via google-api-core -google-cloud-pubsub==1.7.2 +google-cloud-pubsub==2.16.0 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -177,11 +175,14 @@ grpc-google-iam-v1==0.12.6 grpcio==1.53.0 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.2 - # via google-api-core +grpcio-status==1.53.0 + # via + # google-api-core + # google-cloud-pubsub h11==0.14.0 # via wsproto idna==3.4 @@ -233,7 +234,7 @@ opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==23.0 +packaging==23.1 # via # deprecation # docker @@ -249,13 +250,16 @@ pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.3 +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.1 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status + # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 @@ -340,7 +344,7 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.3 +rich==13.3.4 # via twine rsa==4.9 # via diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 33e7a8220..7f2941e4e 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -112,10 +112,8 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.5 # via testcontainers-clickhouse - # via -r requirements.in coverage[toml]==7.2.3 - # via - # pytest-cov + # via pytest-cov cryptography==36.0.2 # via # -r requirements.in @@ -159,7 +157,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.2 # via google-api-core -google-cloud-pubsub==1.7.2 +google-cloud-pubsub==2.16.0 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -173,11 +171,14 @@ grpc-google-iam-v1==0.12.6 grpcio==1.53.0 # via # google-api-core + # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.2 - # via google-api-core +grpcio-status==1.53.0 + # via + # google-api-core + # google-cloud-pubsub h11==0.14.0 # via wsproto idna==3.4 @@ -227,7 +228,7 @@ opensearch-py==2.2.0 # via testcontainers-opensearch outcome==1.2.0 # via trio -packaging==23.0 +packaging==23.1 # via # deprecation # docker @@ -243,13 +244,16 @@ pkginfo==1.9.6 # via twine pluggy==1.0.0 # via pytest -protobuf==3.20.3 +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.1 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status + # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres pyasn1==0.4.8 @@ -333,7 +337,7 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.3 +rich==13.3.4 # via twine rsa==4.9 # via From db513f6fc443f5bb31b7586ad06862e8de8e7ae4 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 17:17:33 -0400 Subject: [PATCH 239/425] Add missing, likely failing doctests. --- compose/testcontainers/compose/__init__.py | 27 ++++++++++--------- google/testcontainers/google/pubsub.py | 11 ++++---- .../testcontainers/localstack/__init__.py | 10 ++++--- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 92e46c270..9191bfcc4 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -23,19 +23,20 @@ class DockerCompose: .. doctest:: - compose_filename = ["docker-compose-1.yml", "docker-compose-2.yml"] - with DockerCompose("/home/project", compose_file_name=compose_file_name, pull=True) as \ - compose: - host = compose.get_service_host("hub", 4444) - port = compose.get_service_port("hub", 4444) - driver = webdriver.Remote( - command_executor=(f"http://{host}:{port}/wd/hub"), - desired_capabilities=CHROME, - ) - driver.get("http://automation-remarks.com") - stdout, stderr = compose.get_logs() - if stderr: - print(f"Errors\\n:{stderr}") + >>> compose_filename = ["docker-compose-1.yml", "docker-compose-2.yml"] + >>> compose = DockerCompose("/home/project", compose_file_name=compose_file_name, + ... pull=True) + ... with compose: + ... host = compose.get_service_host("hub", 4444) + ... port = compose.get_service_port("hub", 4444) + ... driver = webdriver.Remote( + ... command_executor=(f"http://{host}:{port}/wd/hub"), + ... desired_capabilities=CHROME, + ... ) + ... driver.get("http://automation-remarks.com") + ... stdout, stderr = compose.get_logs() + ... if stderr: + ... print(f"Errors\\n:{stderr}") .. code-block:: yaml diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 6a52b2a32..57cc318db 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -30,12 +30,11 @@ class PubSubContainer(DockerContainer): .. doctest:: - def test_docker_run_pubsub(): - config = PubSubContainer('google/cloud-sdk:emulators') - with config as pubsub: - publisher = pubsub.get_publisher() - topic_path = publisher.topic_path(pubsub.project, "my-topic") - topic = publisher.create_topic(topic_path) + >>> config = PubSubContainer('google/cloud-sdk:emulators') + >>> with config as pubsub: + ... publisher = pubsub.get_publisher() + ... topic_path = publisher.topic_path(pubsub.project, "my-topic") + ... topic = publisher.create_topic(name=topic_path) """ def __init__(self, image: str = "google/cloud-sdk:emulators", project: str = "test-project", port: int = 8432, **kwargs) -> None: diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index f733db9d9..1af555bb6 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -32,9 +32,13 @@ class LocalStackContainer(DockerContainer): The endpoint can be used to create a client with the boto3 library: .. doctest:: - dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) - scan_result = dynamo_client.scan(TableName='foo') - # Do something with the scan result + >>> from testcontainers.localstack import LocalStackContainer + + >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: + ... localstack.with_services("dynamodb", "lambda") + ... dynamo_endpoint = localstack.get_url() + ... dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) + ... scan_result = dynamo_client.scan(TableName='foo') """ def __init__(self, image: str = 'localstack/localstack:0.11.4', edge_port: int = 4566, **kwargs) -> None: From 3785934d374657c17a35c43f301f329e0871ee57 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Wed, 12 Apr 2023 18:06:22 -0400 Subject: [PATCH 240/425] Add missing import statement to pubsub doctest. --- google/testcontainers/google/pubsub.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 57cc318db..8e186a331 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -30,6 +30,8 @@ class PubSubContainer(DockerContainer): .. doctest:: + >>> from testcontainers.google import PubSubContainer + >>> config = PubSubContainer('google/cloud-sdk:emulators') >>> with config as pubsub: ... publisher = pubsub.get_publisher() From 5ac5f9f3c12ec7cddc4971f6b4136fd1ab70f6c6 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 13 Apr 2023 08:48:55 -0400 Subject: [PATCH 241/425] Fix pubsub doctest. --- google/testcontainers/google/pubsub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/testcontainers/google/pubsub.py b/google/testcontainers/google/pubsub.py index 8e186a331..bdce91c64 100644 --- a/google/testcontainers/google/pubsub.py +++ b/google/testcontainers/google/pubsub.py @@ -32,9 +32,9 @@ class PubSubContainer(DockerContainer): >>> from testcontainers.google import PubSubContainer - >>> config = PubSubContainer('google/cloud-sdk:emulators') + >>> config = PubSubContainer() >>> with config as pubsub: - ... publisher = pubsub.get_publisher() + ... publisher = pubsub.get_publisher_client() ... topic_path = publisher.topic_path(pubsub.project, "my-topic") ... topic = publisher.create_topic(name=topic_path) """ From af154257d0960e36bc2a8ae12ec712eec7c62d67 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 13 Apr 2023 09:12:50 -0400 Subject: [PATCH 242/425] Fix compose doctest. --- compose/testcontainers/compose/__init__.py | 37 ++++++---------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 9191bfcc4..d50047ff1 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -23,39 +23,20 @@ class DockerCompose: .. doctest:: - >>> compose_filename = ["docker-compose-1.yml", "docker-compose-2.yml"] - >>> compose = DockerCompose("/home/project", compose_file_name=compose_file_name, + >>> from testcontainers.compose import DockerCompose + + >>> compose = DockerCompose("compose/tests", compose_file_name="docker-compose-4.yml", ... pull=True) - ... with compose: - ... host = compose.get_service_host("hub", 4444) - ... port = compose.get_service_port("hub", 4444) - ... driver = webdriver.Remote( - ... command_executor=(f"http://{host}:{port}/wd/hub"), - ... desired_capabilities=CHROME, - ... ) - ... driver.get("http://automation-remarks.com") + >>> with compose: ... stdout, stderr = compose.get_logs() - ... if stderr: - ... print(f"Errors\\n:{stderr}") + >>> b"Hello from Docker!" in stdout + True .. code-block:: yaml - hub: - image: selenium/hub - ports: - - "4444:4444" - firefox: - image: selenium/node-firefox - links: - - hub - expose: - - "5555" - chrome: - image: selenium/node-chrome - links: - - hub - expose: - - "5555" + services: + hello-world: + image: "hello-world" """ def __init__( self, From dda2b4523bd2c4e94fa4ce5b5115245354457724 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 13 Apr 2023 09:29:24 -0400 Subject: [PATCH 243/425] Drop lambda service in localstack doctests. --- localstack/testcontainers/localstack/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index 1af555bb6..41a074deb 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -25,17 +25,16 @@ class LocalStackContainer(DockerContainer): >>> from testcontainers.localstack import LocalStackContainer >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: - ... localstack.with_services("dynamodb", "lambda") ... dynamo_endpoint = localstack.get_url() The endpoint can be used to create a client with the boto3 library: + .. doctest:: >>> from testcontainers.localstack import LocalStackContainer >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: - ... localstack.with_services("dynamodb", "lambda") ... dynamo_endpoint = localstack.get_url() ... dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) ... scan_result = dynamo_client.scan(TableName='foo') From 2ea48692dbd99d9903bcb813b9bc4414ffc2dbb8 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 13 Apr 2023 10:12:18 -0400 Subject: [PATCH 244/425] Add boto3 for localstack doctests. --- .../testcontainers/localstack/__init__.py | 1 + requirements.in | 1 + requirements/3.10.txt | 20 ++++++++++++++++--- requirements/3.11.txt | 20 ++++++++++++++++--- requirements/3.7.txt | 20 ++++++++++++++++--- requirements/3.8.txt | 20 ++++++++++++++++--- requirements/3.9.txt | 20 ++++++++++++++++--- 7 files changed, 87 insertions(+), 15 deletions(-) diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index 41a074deb..65202b87f 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -32,6 +32,7 @@ class LocalStackContainer(DockerContainer): .. doctest:: + >>> import boto3 >>> from testcontainers.localstack import LocalStackContainer >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: diff --git a/requirements.in b/requirements.in index e9e122610..ad14d4f0f 100644 --- a/requirements.in +++ b/requirements.in @@ -21,6 +21,7 @@ -e file:rabbitmq -e file:redis -e file:selenium +boto3 # Required for localstack doctest. cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pg8000 diff --git a/requirements/3.10.txt b/requirements/3.10.txt index b4283ec77..0dffd8cdd 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -88,7 +88,7 @@ attrs==22.2.0 # trio azure-core==1.26.4 # via azure-storage-blob -azure-storage-blob==12.15.0 +azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx @@ -96,6 +96,12 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer +boto3==1.26.112 + # via -r requirements.in +botocore==1.29.112 + # via + # boto3 + # s3transfer cachetools==5.3.0 # via google-auth certifi==2022.12.7 @@ -155,7 +161,7 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.2 +google-auth==2.17.3 # via google-api-core google-cloud-pubsub==2.16.0 # via testcontainers-gcp @@ -203,6 +209,10 @@ jeepney==0.8.0 # secretstorage jinja2==3.1.2 # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 @@ -245,7 +255,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.1 +protobuf==4.22.3 # via # google-api-core # google-cloud-pubsub @@ -297,6 +307,7 @@ python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 # via + # botocore # opensearch-py # pg8000 python-dotenv==0.21.1 @@ -342,6 +353,8 @@ rsa==4.9 # via # google-auth # python-jose +s3transfer==0.6.0 + # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 @@ -411,6 +424,7 @@ tzlocal==4.3 # via clickhouse-driver urllib3[socks]==1.26.15 # via + # botocore # docker # minio # opensearch-py diff --git a/requirements/3.11.txt b/requirements/3.11.txt index 8a8f30206..1b058180b 100644 --- a/requirements/3.11.txt +++ b/requirements/3.11.txt @@ -88,7 +88,7 @@ attrs==22.2.0 # trio azure-core==1.26.4 # via azure-storage-blob -azure-storage-blob==12.15.0 +azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx @@ -96,6 +96,12 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer +boto3==1.26.112 + # via -r requirements.in +botocore==1.29.112 + # via + # boto3 + # s3transfer cachetools==5.3.0 # via google-auth certifi==2022.12.7 @@ -152,7 +158,7 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.2 +google-auth==2.17.3 # via google-api-core google-cloud-pubsub==2.16.0 # via testcontainers-gcp @@ -200,6 +206,10 @@ jeepney==0.8.0 # secretstorage jinja2==3.1.2 # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 @@ -242,7 +252,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.1 +protobuf==4.22.3 # via # google-api-core # google-cloud-pubsub @@ -294,6 +304,7 @@ python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 # via + # botocore # opensearch-py # pg8000 python-dotenv==0.21.1 @@ -339,6 +350,8 @@ rsa==4.9 # via # google-auth # python-jose +s3transfer==0.6.0 + # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 @@ -404,6 +417,7 @@ tzlocal==4.3 # via clickhouse-driver urllib3[socks]==1.26.15 # via + # botocore # docker # minio # opensearch-py diff --git a/requirements/3.7.txt b/requirements/3.7.txt index f4741af6c..48ae31d4c 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -88,7 +88,7 @@ attrs==22.2.0 # trio azure-core==1.26.4 # via azure-storage-blob -azure-storage-blob==12.15.0 +azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx @@ -100,6 +100,12 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer +boto3==1.26.112 + # via -r requirements.in +botocore==1.29.112 + # via + # boto3 + # s3transfer cached-property==1.5.2 # via docker-compose cachetools==5.3.0 @@ -161,7 +167,7 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.2 +google-auth==2.17.3 # via google-api-core google-cloud-pubsub==2.16.0 # via testcontainers-gcp @@ -219,6 +225,10 @@ jeepney==0.8.0 # secretstorage jinja2==3.1.2 # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 @@ -261,7 +271,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.1 +protobuf==4.22.3 # via # google-api-core # google-cloud-pubsub @@ -313,6 +323,7 @@ python-arango==7.5.6 # via testcontainers-arangodb python-dateutil==2.8.2 # via + # botocore # opensearch-py # pg8000 python-dotenv==0.21.1 @@ -359,6 +370,8 @@ rsa==4.9 # via # google-auth # python-jose +s3transfer==0.6.0 + # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 @@ -434,6 +447,7 @@ tzlocal==4.3 # via clickhouse-driver urllib3[socks]==1.26.15 # via + # botocore # docker # minio # opensearch-py diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 2f4c341dc..ad2bb2878 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -88,7 +88,7 @@ attrs==22.2.0 # trio azure-core==1.26.4 # via azure-storage-blob -azure-storage-blob==12.15.0 +azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx @@ -100,6 +100,12 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer +boto3==1.26.112 + # via -r requirements.in +botocore==1.29.112 + # via + # boto3 + # s3transfer cachetools==5.3.0 # via google-auth certifi==2022.12.7 @@ -159,7 +165,7 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.2 +google-auth==2.17.3 # via google-api-core google-cloud-pubsub==2.16.0 # via testcontainers-gcp @@ -210,6 +216,10 @@ jeepney==0.8.0 # secretstorage jinja2==3.1.2 # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 @@ -252,7 +262,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.1 +protobuf==4.22.3 # via # google-api-core # google-cloud-pubsub @@ -304,6 +314,7 @@ python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 # via + # botocore # opensearch-py # pg8000 python-dotenv==0.21.1 @@ -350,6 +361,8 @@ rsa==4.9 # via # google-auth # python-jose +s3transfer==0.6.0 + # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 @@ -420,6 +433,7 @@ tzlocal==4.3 # via clickhouse-driver urllib3[socks]==1.26.15 # via + # botocore # docker # minio # opensearch-py diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 7f2941e4e..40314bccc 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -88,7 +88,7 @@ attrs==22.2.0 # trio azure-core==1.26.4 # via azure-storage-blob -azure-storage-blob==12.15.0 +azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx @@ -96,6 +96,12 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer +boto3==1.26.112 + # via -r requirements.in +botocore==1.29.112 + # via + # boto3 + # s3transfer cachetools==5.3.0 # via google-auth certifi==2022.12.7 @@ -155,7 +161,7 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.2 +google-auth==2.17.3 # via google-api-core google-cloud-pubsub==2.16.0 # via testcontainers-gcp @@ -204,6 +210,10 @@ jeepney==0.8.0 # secretstorage jinja2==3.1.2 # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore jsonschema==3.2.0 # via docker-compose kafka-python==2.0.2 @@ -246,7 +256,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.1 +protobuf==4.22.3 # via # google-api-core # google-cloud-pubsub @@ -298,6 +308,7 @@ python-arango==7.5.7 # via testcontainers-arangodb python-dateutil==2.8.2 # via + # botocore # opensearch-py # pg8000 python-dotenv==0.21.1 @@ -343,6 +354,8 @@ rsa==4.9 # via # google-auth # python-jose +s3transfer==0.6.0 + # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 @@ -412,6 +425,7 @@ tzlocal==4.3 # via clickhouse-driver urllib3[socks]==1.26.15 # via + # botocore # docker # minio # opensearch-py From 908b840feba2dc305d54085230eb5e8c74864343 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 13 Apr 2023 10:19:51 -0400 Subject: [PATCH 245/425] Add region name to boto client call. --- localstack/testcontainers/localstack/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index 65202b87f..70ae085d2 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -37,7 +37,8 @@ class LocalStackContainer(DockerContainer): >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: ... dynamo_endpoint = localstack.get_url() - ... dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint) + ... dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint, + ... region_name="us-west-1") ... scan_result = dynamo_client.scan(TableName='foo') """ def __init__(self, image: str = 'localstack/localstack:0.11.4', edge_port: int = 4566, From 62d75626a66ff43de13d8eb4c44ef4101ad1bafd Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 13 Apr 2023 12:00:07 -0400 Subject: [PATCH 246/425] Add `get_client` to `LocalStackContainer` and fix doctests. --- localstack/setup.py | 1 + .../testcontainers/localstack/__init__.py | 44 +++++++++++-------- localstack/tests/test_localstack.py | 13 +++++- requirements.in | 1 - requirements/3.10.txt | 2 +- requirements/3.11.txt | 2 +- requirements/3.7.txt | 2 +- requirements/3.8.txt | 2 +- requirements/3.9.txt | 2 +- 9 files changed, 43 insertions(+), 26 deletions(-) diff --git a/localstack/setup.py b/localstack/setup.py index 649104a71..5dbb93f31 100644 --- a/localstack/setup.py +++ b/localstack/setup.py @@ -11,6 +11,7 @@ long_description_content_type="text/x-rst", url="https://github.com/testcontainers/testcontainers-python", install_requires=[ + "boto3", "testcontainers-core", ], python_requires=">=3.7", diff --git a/localstack/testcontainers/localstack/__init__.py b/localstack/testcontainers/localstack/__init__.py index 70ae085d2..470d78a00 100644 --- a/localstack/testcontainers/localstack/__init__.py +++ b/localstack/testcontainers/localstack/__init__.py @@ -10,8 +10,12 @@ # 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 boto3 +import functools as ft +import os from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.core.container import DockerContainer +from typing import Any, Optional class LocalStackContainer(DockerContainer): @@ -24,28 +28,21 @@ class LocalStackContainer(DockerContainer): >>> from testcontainers.localstack import LocalStackContainer - >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: - ... dynamo_endpoint = localstack.get_url() - - - The endpoint can be used to create a client with the boto3 library: - - .. doctest:: - - >>> import boto3 - >>> from testcontainers.localstack import LocalStackContainer - - >>> with LocalStackContainer(image="localstack/localstack:0.11.4") as localstack: - ... dynamo_endpoint = localstack.get_url() - ... dynamo_client = boto3.client("dynamodb", endpoint_url=dynamo_endpoint, - ... region_name="us-west-1") - ... scan_result = dynamo_client.scan(TableName='foo') + >>> with LocalStackContainer(image="localstack/localstack:2.0.1") as localstack: + ... dynamo_client = localstack.get_client("dynamodb") + ... tables = dynamo_client.list_tables() + >>> tables + {'TableNames': [], ...} """ - def __init__(self, image: str = 'localstack/localstack:0.11.4', edge_port: int = 4566, - **kwargs) -> None: + def __init__(self, image: str = 'localstack/localstack:2.0.1', edge_port: int = 4566, + region_name: Optional[str] = None, **kwargs) -> None: super(LocalStackContainer, self).__init__(image, **kwargs) self.edge_port = edge_port + self.region_name = region_name or os.environ.get("AWS_DEFAULT_REGION", "us-west-1") self.with_exposed_ports(self.edge_port) + self.with_env("AWS_DEFAULT_REGION", self.region_name) + self.with_env("AWS_ACCESS_KEY_ID", "testcontainers-localstack") + self.with_env("AWS_SECRET_ACCESS_KEY", "testcontainers-localstack") def with_services(self, *services) -> "LocalStackContainer": """ @@ -69,6 +66,17 @@ def get_url(self) -> str: port = self.get_exposed_port(self.edge_port) return f'http://{host}:{port}' + @ft.wraps(boto3.client) + def get_client(self, name, **kwargs) -> Any: + kwargs_ = { + "endpoint_url": self.get_url(), + "region_name": self.region_name, + "aws_access_key_id": "testcontainers-localstack", + "aws_secret_access_key": "testcontainers-localstack", + } + kwargs_.update(kwargs) + return boto3.client(name, **kwargs_) + def start(self, timeout: float = 60) -> "LocalStackContainer": super().start() wait_for_logs(self, r'Ready\.\n', timeout=timeout) diff --git a/localstack/tests/test_localstack.py b/localstack/tests/test_localstack.py index 5747a7da7..f587c41db 100644 --- a/localstack/tests/test_localstack.py +++ b/localstack/tests/test_localstack.py @@ -10,6 +10,15 @@ def test_docker_run_localstack(): services = json.loads(resp.read().decode())['services'] # Check that all services are running - assert all(value == 'running' for value in services.values()) + assert all(value == 'available' for value in services.values()) # Check that some of the services keys - assert all(test_service in services.keys() for test_service in ['dynamodb', 'sns', 'sqs']) + assert all(test_service in services for test_service in ['dynamodb', 'sns', 'sqs']) + + +def test_localstack_boto3(): + from testcontainers.localstack import LocalStackContainer + + with LocalStackContainer(image="localstack/localstack:2.0.1") as localstack: + dynamo_client = localstack.get_client("dynamodb") + tables = dynamo_client.list_tables() + assert tables["TableNames"] == [] diff --git a/requirements.in b/requirements.in index ad14d4f0f..e9e122610 100644 --- a/requirements.in +++ b/requirements.in @@ -21,7 +21,6 @@ -e file:rabbitmq -e file:redis -e file:selenium -boto3 # Required for localstack doctest. cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pg8000 diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 0dffd8cdd..28c7b8fa3 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -97,7 +97,7 @@ bcrypt==4.0.1 bleach==6.0.0 # via readme-renderer boto3==1.26.112 - # via -r requirements.in + # via testcontainers-localstack botocore==1.29.112 # via # boto3 diff --git a/requirements/3.11.txt b/requirements/3.11.txt index 1b058180b..b5d4295b1 100644 --- a/requirements/3.11.txt +++ b/requirements/3.11.txt @@ -97,7 +97,7 @@ bcrypt==4.0.1 bleach==6.0.0 # via readme-renderer boto3==1.26.112 - # via -r requirements.in + # via testcontainers-localstack botocore==1.29.112 # via # boto3 diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 48ae31d4c..d8fb75830 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -101,7 +101,7 @@ bcrypt==4.0.1 bleach==6.0.0 # via readme-renderer boto3==1.26.112 - # via -r requirements.in + # via testcontainers-localstack botocore==1.29.112 # via # boto3 diff --git a/requirements/3.8.txt b/requirements/3.8.txt index ad2bb2878..03ad3d3e3 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -101,7 +101,7 @@ bcrypt==4.0.1 bleach==6.0.0 # via readme-renderer boto3==1.26.112 - # via -r requirements.in + # via testcontainers-localstack botocore==1.29.112 # via # boto3 diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 40314bccc..6a0d03ae0 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -97,7 +97,7 @@ bcrypt==4.0.1 bleach==6.0.0 # via readme-renderer boto3==1.26.112 - # via -r requirements.in + # via testcontainers-localstack botocore==1.29.112 # via # boto3 From f61dcda8bd7ea329cd3c836b6d6e2f0bd990335d Mon Sep 17 00:00:00 2001 From: Balint Bartha Date: Wed, 19 Apr 2023 13:37:51 +0200 Subject: [PATCH 247/425] feat(compose): allow running specific services in compose --- compose/testcontainers/compose/__init__.py | 13 ++++++++++--- compose/tests/test_docker_compose.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index d50047ff1..ac7d5fe36 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -1,9 +1,10 @@ -import requests import subprocess from typing import Iterable, List, Optional, Tuple, Union -from testcontainers.core.waiting_utils import wait_container_is_ready +import requests + from testcontainers.core.exceptions import NoSuchPortExposed +from testcontainers.core.waiting_utils import wait_container_is_ready class DockerCompose: @@ -38,19 +39,23 @@ class DockerCompose: hello-world: image: "hello-world" """ + def __init__( self, filepath: str, compose_file_name: Union[str, Iterable] = "docker-compose.yml", pull: bool = False, build: bool = False, - env_file: Optional[str] = None) -> None: + env_file: Optional[str] = None, + services: Optional[List[str]] = None + ) -> None: self.filepath = filepath self.compose_file_names = [compose_file_name] if isinstance(compose_file_name, str) else \ list(compose_file_name) self.pull = pull self.build = build self.env_file = env_file + self.services = services def __enter__(self) -> "DockerCompose": self.start() @@ -84,6 +89,8 @@ def start(self) -> None: up_cmd = self.docker_compose_command() + ['up', '-d'] if self.build: up_cmd.append('--build') + if self.services: + up_cmd.extend(self.services) self._call_command(cmd=up_cmd) diff --git a/compose/tests/test_docker_compose.py b/compose/tests/test_docker_compose.py index 00f231e68..619870b55 100644 --- a/compose/tests/test_docker_compose.py +++ b/compose/tests/test_docker_compose.py @@ -8,7 +8,6 @@ from testcontainers.core.exceptions import NoSuchPortExposed from testcontainers.core.waiting_utils import wait_for_logs - ROOT = os.path.dirname(__file__) @@ -40,6 +39,19 @@ def test_can_build_images_before_spawning_service_via_compose(): assert "--build" in docker_compose_cmd +def test_can_run_specific_services(): + with patch.object(DockerCompose, "_call_command") as call_mock: + with DockerCompose(ROOT, services=["hub", "firefox"]) as compose: + ... + + assert compose.services + docker_compose_cmd = call_mock.call_args_list[0][1]["cmd"] + services_at_the_end = docker_compose_cmd[-2:] + assert "firefox" in services_at_the_end + assert "hub" in services_at_the_end + assert "chrome" not in docker_compose_cmd + + def test_can_throw_exception_if_no_port_exposed(): with DockerCompose(ROOT) as compose: with pytest.raises(NoSuchPortExposed): From 96e7a14131bdc3bf8327a885f08bad5ceb092b74 Mon Sep 17 00:00:00 2001 From: Balint Bartha Date: Fri, 28 Apr 2023 10:24:48 +0200 Subject: [PATCH 248/425] test(compose): improve compose tests when running specific services --- compose/tests/test_docker_compose.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/compose/tests/test_docker_compose.py b/compose/tests/test_docker_compose.py index 619870b55..88d5f9a22 100644 --- a/compose/tests/test_docker_compose.py +++ b/compose/tests/test_docker_compose.py @@ -39,7 +39,7 @@ def test_can_build_images_before_spawning_service_via_compose(): assert "--build" in docker_compose_cmd -def test_can_run_specific_services(): +def test_can_specify_services(): with patch.object(DockerCompose, "_call_command") as call_mock: with DockerCompose(ROOT, services=["hub", "firefox"]) as compose: ... @@ -52,6 +52,25 @@ def test_can_run_specific_services(): assert "chrome" not in docker_compose_cmd +@pytest.mark.parametrize("should_run_hub", [ + [True], + [False], +]) +def test_can_run_specific_services(should_run_hub: bool): + # compose V2 will improve this test by being able to assert that "firefox" also has started and exited + services = ["firefox"] + if should_run_hub: + services.append("hub") + + with DockerCompose(ROOT, services=services) as compose: + if should_run_hub: + assert compose.get_service_host("hub", 4444) + assert compose.get_service_port("hub", 4444) + else: + with pytest.raises(NoSuchPortExposed): + assert compose.get_service_host("hub", 4444) + + def test_can_throw_exception_if_no_port_exposed(): with DockerCompose(ROOT) as compose: with pytest.raises(NoSuchPortExposed): From 252ab7208c26fd645ab1d59acc9f587bbb631030 Mon Sep 17 00:00:00 2001 From: Balint Bartha Date: Fri, 28 Apr 2023 10:27:13 +0200 Subject: [PATCH 249/425] chore(compose): add docstring for new service arg --- compose/testcontainers/compose/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index ac7d5fe36..32221d9b2 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -17,6 +17,7 @@ class DockerCompose: pull: Pull images before launching environment. build: Build images referenced in the configuration file. env_file: Path to an env file containing environment variables to pass to docker compose. + services: The list of services to be run when starting this DockerCompose. Example: From 9c6871f57348d9b74dc64f5d7f03d05e0b05a4cf Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Thu, 4 May 2023 17:43:24 -0400 Subject: [PATCH 250/425] Temporary fix for docker/docker-py#3113. --- core/setup.py | 1 + requirements/3.10.txt | 44 +++++++++++++++++++++---------------------- requirements/3.11.txt | 44 +++++++++++++++++++++---------------------- requirements/3.7.txt | 43 +++++++++++++++++++++--------------------- requirements/3.8.txt | 44 +++++++++++++++++++++---------------------- requirements/3.9.txt | 44 +++++++++++++++++++++---------------------- 6 files changed, 111 insertions(+), 109 deletions(-) diff --git a/core/setup.py b/core/setup.py index 0270c6de8..ef7c87f07 100644 --- a/core/setup.py +++ b/core/setup.py @@ -12,6 +12,7 @@ url="https://github.com/testcontainers/testcontainers-python", install_requires=[ "docker>=4.0.0", + "urllib3<2.0", # https://github.com/docker/docker-py/issues/3113#issuecomment-1533389349 "wrapt", ], python_requires=">=3.7", diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 28c7b8fa3..f1a64157f 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -81,7 +81,7 @@ async-generator==1.10 # via trio async-timeout==4.0.2 # via redis -attrs==22.2.0 +attrs==23.1.0 # via # jsonschema # outcome @@ -96,9 +96,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.112 +boto3==1.26.127 # via testcontainers-localstack -botocore==1.29.112 +botocore==1.29.127 # via # boto3 # s3transfer @@ -116,9 +116,9 @@ cffi==1.15.1 # pynacl charset-normalizer==3.1.0 # via requests -clickhouse-driver==0.2.5 +clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.3 +coverage[toml]==7.2.5 # via pytest-cov cryptography==36.0.2 # via @@ -174,14 +174,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.53.0 +grpcio==1.54.0 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.53.0 +grpcio-status==1.54.0 # via # google-api-core # google-cloud-pubsub @@ -193,7 +193,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.3.0 +importlib-metadata==6.6.0 # via # keyring # twine @@ -231,7 +231,7 @@ minio==7.1.14 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.7.0 +neo4j==5.8.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -255,7 +255,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.3 +protobuf==4.22.4 # via # google-api-core # google-cloud-pubsub @@ -265,12 +265,12 @@ protobuf==4.22.3 # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres -pyasn1==0.4.8 +pyasn1==0.5.0 # via # pyasn1-modules # python-jose # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.3.0 # via google-auth pycodestyle==2.5.0 # via flake8 @@ -278,7 +278,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.15.0 +pygments==2.15.1 # via # readme-renderer # rich @@ -297,7 +297,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.0 +pytest==7.3.1 # via # -r requirements.in # pytest-cov @@ -314,7 +314,7 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.15.3 +python-keycloak==2.16.1 # via testcontainers-keycloak pytz==2023.3 # via @@ -328,7 +328,7 @@ readme-renderer==37.3 # via twine redis==4.5.4 # via testcontainers-redis -requests==2.28.2 +requests==2.30.0 # via # azure-core # docker @@ -347,19 +347,19 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.4 +rich==13.3.5 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.0 +s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.3 +selenium==4.9.0 # via testcontainers-selenium six==1.16.0 # via @@ -379,7 +379,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.3 +sphinx==7.0.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -393,7 +393,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.9 +sqlalchemy==2.0.12 # via # testcontainers-mssql # testcontainers-mysql @@ -429,9 +429,9 @@ urllib3[socks]==1.26.15 # minio # opensearch-py # python-arango - # python-keycloak # requests # selenium + # testcontainers-core # twine webencodings==0.5.1 # via bleach diff --git a/requirements/3.11.txt b/requirements/3.11.txt index b5d4295b1..cbd63b4a8 100644 --- a/requirements/3.11.txt +++ b/requirements/3.11.txt @@ -81,7 +81,7 @@ async-generator==1.10 # via trio async-timeout==4.0.2 # via redis -attrs==22.2.0 +attrs==23.1.0 # via # jsonschema # outcome @@ -96,9 +96,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.112 +boto3==1.26.127 # via testcontainers-localstack -botocore==1.29.112 +botocore==1.29.127 # via # boto3 # s3transfer @@ -116,9 +116,9 @@ cffi==1.15.1 # pynacl charset-normalizer==3.1.0 # via requests -clickhouse-driver==0.2.5 +clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.3 +coverage[toml]==7.2.5 # via pytest-cov cryptography==36.0.2 # via @@ -171,14 +171,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.53.0 +grpcio==1.54.0 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.53.0 +grpcio-status==1.54.0 # via # google-api-core # google-cloud-pubsub @@ -190,7 +190,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.3.0 +importlib-metadata==6.6.0 # via # keyring # twine @@ -228,7 +228,7 @@ minio==7.1.14 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.7.0 +neo4j==5.8.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -252,7 +252,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.3 +protobuf==4.22.4 # via # google-api-core # google-cloud-pubsub @@ -262,12 +262,12 @@ protobuf==4.22.3 # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres -pyasn1==0.4.8 +pyasn1==0.5.0 # via # pyasn1-modules # python-jose # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.3.0 # via google-auth pycodestyle==2.5.0 # via flake8 @@ -275,7 +275,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.15.0 +pygments==2.15.1 # via # readme-renderer # rich @@ -294,7 +294,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.0 +pytest==7.3.1 # via # -r requirements.in # pytest-cov @@ -311,7 +311,7 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.15.3 +python-keycloak==2.16.1 # via testcontainers-keycloak pytz==2023.3 # via @@ -325,7 +325,7 @@ readme-renderer==37.3 # via twine redis==4.5.4 # via testcontainers-redis -requests==2.28.2 +requests==2.30.0 # via # azure-core # docker @@ -344,19 +344,19 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.4 +rich==13.3.5 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.0 +s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.3 +selenium==4.9.0 # via testcontainers-selenium six==1.16.0 # via @@ -376,7 +376,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.3 +sphinx==7.0.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -390,7 +390,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.9 +sqlalchemy==2.0.12 # via # testcontainers-mssql # testcontainers-mysql @@ -422,9 +422,9 @@ urllib3[socks]==1.26.15 # minio # opensearch-py # python-arango - # python-keycloak # requests # selenium + # testcontainers-core # twine webencodings==0.5.1 # via bleach diff --git a/requirements/3.7.txt b/requirements/3.7.txt index d8fb75830..a19856f04 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -81,7 +81,7 @@ async-generator==1.10 # via trio async-timeout==4.0.2 # via redis -attrs==22.2.0 +attrs==23.1.0 # via # jsonschema # outcome @@ -100,9 +100,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.112 +boto3==1.26.127 # via testcontainers-localstack -botocore==1.29.112 +botocore==1.29.127 # via # boto3 # s3transfer @@ -122,9 +122,9 @@ cffi==1.15.1 # pynacl charset-normalizer==3.1.0 # via requests -clickhouse-driver==0.2.5 +clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.3 +coverage[toml]==7.2.5 # via pytest-cov cryptography==36.0.2 # via @@ -180,14 +180,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.53.0 +grpcio==1.54.0 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.53.0 +grpcio-status==1.54.0 # via # google-api-core # google-cloud-pubsub @@ -199,8 +199,9 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.3.0 +importlib-metadata==6.6.0 # via + # attrs # jsonschema # keyring # pg8000 @@ -247,7 +248,7 @@ minio==7.1.14 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.7.0 +neo4j==5.8.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -271,7 +272,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.3 +protobuf==4.22.4 # via # google-api-core # google-cloud-pubsub @@ -281,12 +282,12 @@ protobuf==4.22.3 # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres -pyasn1==0.4.8 +pyasn1==0.5.0 # via # pyasn1-modules # python-jose # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.3.0 # via google-auth pycodestyle==2.5.0 # via flake8 @@ -294,7 +295,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.15.0 +pygments==2.15.1 # via # readme-renderer # rich @@ -313,7 +314,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.0 +pytest==7.3.1 # via # -r requirements.in # pytest-cov @@ -330,7 +331,7 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.15.3 +python-keycloak==2.16.1 # via testcontainers-keycloak pytz==2023.3 # via @@ -345,7 +346,7 @@ readme-renderer==37.3 # via twine redis==4.5.4 # via testcontainers-redis -requests==2.28.2 +requests==2.30.0 # via # azure-core # docker @@ -364,19 +365,19 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.4 +rich==13.3.5 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.0 +s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.3 +selenium==4.9.0 # via testcontainers-selenium six==1.16.0 # via @@ -410,7 +411,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.9 +sqlalchemy==2.0.12 # via # testcontainers-mssql # testcontainers-mysql @@ -452,9 +453,9 @@ urllib3[socks]==1.26.15 # minio # opensearch-py # python-arango - # python-keycloak # requests # selenium + # testcontainers-core # twine webencodings==0.5.1 # via bleach diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 03ad3d3e3..530e0fd64 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -81,7 +81,7 @@ async-generator==1.10 # via trio async-timeout==4.0.2 # via redis -attrs==22.2.0 +attrs==23.1.0 # via # jsonschema # outcome @@ -100,9 +100,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.112 +boto3==1.26.127 # via testcontainers-localstack -botocore==1.29.112 +botocore==1.29.127 # via # boto3 # s3transfer @@ -120,9 +120,9 @@ cffi==1.15.1 # pynacl charset-normalizer==3.1.0 # via requests -clickhouse-driver==0.2.5 +clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.3 +coverage[toml]==7.2.5 # via pytest-cov cryptography==36.0.2 # via @@ -178,14 +178,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.53.0 +grpcio==1.54.0 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.53.0 +grpcio-status==1.54.0 # via # google-api-core # google-cloud-pubsub @@ -197,7 +197,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.3.0 +importlib-metadata==6.6.0 # via # keyring # sphinx @@ -238,7 +238,7 @@ minio==7.1.14 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.7.0 +neo4j==5.8.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -262,7 +262,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.3 +protobuf==4.22.4 # via # google-api-core # google-cloud-pubsub @@ -272,12 +272,12 @@ protobuf==4.22.3 # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres -pyasn1==0.4.8 +pyasn1==0.5.0 # via # pyasn1-modules # python-jose # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.3.0 # via google-auth pycodestyle==2.5.0 # via flake8 @@ -285,7 +285,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.15.0 +pygments==2.15.1 # via # readme-renderer # rich @@ -304,7 +304,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.0 +pytest==7.3.1 # via # -r requirements.in # pytest-cov @@ -321,7 +321,7 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.15.3 +python-keycloak==2.16.1 # via testcontainers-keycloak pytz==2023.3 # via @@ -336,7 +336,7 @@ readme-renderer==37.3 # via twine redis==4.5.4 # via testcontainers-redis -requests==2.28.2 +requests==2.30.0 # via # azure-core # docker @@ -355,19 +355,19 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.4 +rich==13.3.5 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.0 +s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.3 +selenium==4.9.0 # via testcontainers-selenium six==1.16.0 # via @@ -387,7 +387,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.3 +sphinx==7.0.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -401,7 +401,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.9 +sqlalchemy==2.0.12 # via # testcontainers-mssql # testcontainers-mysql @@ -438,9 +438,9 @@ urllib3[socks]==1.26.15 # minio # opensearch-py # python-arango - # python-keycloak # requests # selenium + # testcontainers-core # twine webencodings==0.5.1 # via bleach diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 6a0d03ae0..5009c1f7a 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -81,7 +81,7 @@ async-generator==1.10 # via trio async-timeout==4.0.2 # via redis -attrs==22.2.0 +attrs==23.1.0 # via # jsonschema # outcome @@ -96,9 +96,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.112 +boto3==1.26.127 # via testcontainers-localstack -botocore==1.29.112 +botocore==1.29.127 # via # boto3 # s3transfer @@ -116,9 +116,9 @@ cffi==1.15.1 # pynacl charset-normalizer==3.1.0 # via requests -clickhouse-driver==0.2.5 +clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.3 +coverage[toml]==7.2.5 # via pytest-cov cryptography==36.0.2 # via @@ -174,14 +174,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.53.0 +grpcio==1.54.0 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.53.0 +grpcio-status==1.54.0 # via # google-api-core # google-cloud-pubsub @@ -193,7 +193,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.3.0 +importlib-metadata==6.6.0 # via # keyring # sphinx @@ -232,7 +232,7 @@ minio==7.1.14 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.7.0 +neo4j==5.8.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -256,7 +256,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.3 +protobuf==4.22.4 # via # google-api-core # google-cloud-pubsub @@ -266,12 +266,12 @@ protobuf==4.22.3 # proto-plus psycopg2-binary==2.9.6 # via testcontainers-postgres -pyasn1==0.4.8 +pyasn1==0.5.0 # via # pyasn1-modules # python-jose # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.3.0 # via google-auth pycodestyle==2.5.0 # via flake8 @@ -279,7 +279,7 @@ pycparser==2.21 # via cffi pyflakes==2.1.1 # via flake8 -pygments==2.15.0 +pygments==2.15.1 # via # readme-renderer # rich @@ -298,7 +298,7 @@ pyrsistent==0.19.3 # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.0 +pytest==7.3.1 # via # -r requirements.in # pytest-cov @@ -315,7 +315,7 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.15.3 +python-keycloak==2.16.1 # via testcontainers-keycloak pytz==2023.3 # via @@ -329,7 +329,7 @@ readme-renderer==37.3 # via twine redis==4.5.4 # via testcontainers-redis -requests==2.28.2 +requests==2.30.0 # via # azure-core # docker @@ -348,19 +348,19 @@ requests-toolbelt==0.10.1 # twine rfc3986==2.0.0 # via twine -rich==13.3.4 +rich==13.3.5 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.0 +s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.8.3 +selenium==4.9.0 # via testcontainers-selenium six==1.16.0 # via @@ -380,7 +380,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==6.1.3 +sphinx==7.0.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -394,7 +394,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.9 +sqlalchemy==2.0.12 # via # testcontainers-mssql # testcontainers-mysql @@ -430,9 +430,9 @@ urllib3[socks]==1.26.15 # minio # opensearch-py # python-arango - # python-keycloak # requests # selenium + # testcontainers-core # twine webencodings==0.5.1 # via bleach From 427c9b841c2f6f516ec6cb74d5bd2839cb1939f4 Mon Sep 17 00:00:00 2001 From: Balint Bartha Date: Fri, 5 May 2023 15:25:21 +0200 Subject: [PATCH 251/425] fix: test linting issue --- compose/tests/test_docker_compose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/tests/test_docker_compose.py b/compose/tests/test_docker_compose.py index 88d5f9a22..da611f1c8 100644 --- a/compose/tests/test_docker_compose.py +++ b/compose/tests/test_docker_compose.py @@ -57,7 +57,7 @@ def test_can_specify_services(): [False], ]) def test_can_run_specific_services(should_run_hub: bool): - # compose V2 will improve this test by being able to assert that "firefox" also has started and exited + # compose V2 will improve this test by being able to assert that "firefox" also started/exited services = ["firefox"] if should_run_hub: services.append("hub") From 9217f58e21aea2efa423bdc08b184a05f57d1959 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Fri, 5 May 2023 17:57:26 -0400 Subject: [PATCH 252/425] Raise error on unknown marker. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4eaf1cc45..155847a6d 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ ${DISTRIBUTIONS} : %/dist : %/setup.py # Targets to run the test suite for each package. tests : ${TESTS} ${TESTS} : %/tests : - pytest -svx --cov-report=term-missing --cov=testcontainers.$* --tb=short $*/tests + pytest -svx --cov-report=term-missing --cov=testcontainers.$* --tb=short --strict-markers $*/tests # Targets to lint the code. lint : ${LINT} From 6211c427de06a6d3d1556e2686a08da98958bb05 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 12:52:40 -0400 Subject: [PATCH 253/425] Rename master to main. --- .github/PULL_REQUEST_TEMPLATE/new_container.md | 4 ++-- .github/workflows/docs.yml | 4 ++-- .github/workflows/main.yml | 6 +++--- .github/workflows/requirements.yml | 4 ++-- README.rst | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index dcdff17b4..9a3359eb5 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -4,5 +4,5 @@ You have implemented a new container and would like to contribute it? Great! Her - [ ] Implement the new feature (typically in `__init__.py`) and corresponding tests. - [ ] Add a line `-e file:[feature name]` to `requirements.in` and run `make requirements`. This command will find any new requirements and generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Then run `pip install -r requirements/[your python version].txt` to install the new requirements. - [ ] Update the feature `README.rst` and add it to the table of contents (`toctree` directive) in the top-level `README.rst`. -- [ ] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `master` branch. -- [ ] Rebase your development branch on `master` (or merge `master` into your development branch). +- [ ] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `main` branch. +- [ ] Rebase your development branch on `main` (or merge `main` into your development branch). diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 893fa5f9d..0188d340a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,9 +1,9 @@ name: testcontainers documentation on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: build: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index be6d0bcd2..0d139b121 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,9 +1,9 @@ name: testcontainers packages on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: build: @@ -76,7 +76,7 @@ jobs: - name: Upload the package to pypi if: > github.event_name == 'push' - && github.ref == 'refs/heads/master' + && github.ref == 'refs/heads/main' && github.repository_owner == 'testcontainers' && matrix.python-version == '3.10' env: diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index 684447d4d..72ee2bb1a 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -1,9 +1,9 @@ name: testcontainers requirements on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: requirements: diff --git a/README.rst b/README.rst index 081456879..65b094764 100644 --- a/README.rst +++ b/README.rst @@ -114,5 +114,5 @@ You want to contribute a new feature or container? Great! You can do that in six 2. Implement the new feature (typically in :code:`__init__.py`) and corresponding tests. 3. Add a line :code:`-e file:[feature name]` to :code:`requirements.in` and run :code:`make requirements`. This command will find any new requirements and generate lock files to ensure reproducible builds (see the `pip-tools `__ documentation for details). Then run :code:`pip install -r requirements/[your python version].txt` to install the new requirements. 4. Update the feature :code:`README.rst` and add it to the table of contents (:code:`toctree` directive) in the top-level :code:`README.rst`. -5. Add a line :code:`[feature name]` to the list of components in the GitHub Action workflow in :code:`.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the :code:`master` branch. -6. Rebase your development branch on :code:`master` (or merge :code:`master` into your development branch). +5. Add a line :code:`[feature name]` to the list of components in the GitHub Action workflow in :code:`.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the :code:`main` branch. +6. Rebase your development branch on :code:`main` (or merge :code:`main` into your development branch). From 725725f6a36befcfd4c65f10c2390eb83613b36c Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 13:41:46 -0400 Subject: [PATCH 254/425] Build requirements for `windows-latest` machine. --- .github/workflows/requirements.yml | 33 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index 72ee2bb1a..ffc70ed14 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -9,27 +9,34 @@ jobs: requirements: strategy: matrix: - python-version: - - "3.7" - - "3.8" - - "3.9" - - "3.10" - - "3.11" - runs-on: ubuntu-latest + runtime: + - machine: ubuntu-latest + python-version: "3.7" + - machine: ubuntu-latest + python-version: "3.8" + - machine: ubuntu-latest + python-version: "3.9" + - machine: ubuntu-latest + python-version: "3.10" + - machine: ubuntu-latest + python-version: "3.11" + - machine: windows-latest + python-version: "3.10" + runs-on: ${{ matrix.runtime.machine }} steps: - uses: actions/checkout@v3 - - name: Setup python ${{ matrix.python-version }} + - name: Setup python ${{ matrix.runtime.python-version }} uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.runtime.python-version }} - name: Update pip and install pip-tools run: pip install --upgrade pip pip-tools - name: Build requirements run: | - rm requirements/${{ matrix.python-version }}.txt - pip-compile --resolver=backtracking -v --upgrade -o requirements/${{ matrix.python-version }}.txt + rm -f requirements.txt + pip-compile --resolver=backtracking -v --upgrade -o requirements.txt - name: Store requirements as artifact uses: actions/upload-artifact@v3 with: - name: requirements-${{ matrix.python-version }}.txt - path: requirements/${{ matrix.python-version }}.txt + name: requirements-${{ matrix.runtime.machine }}-${{ matrix.runtime.python-version }}.txt + path: requirements.txt From 9763a93a9fe59c1704c76a0e9370c3ef4b7ee9c5 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 13:43:41 -0400 Subject: [PATCH 255/425] Remove unnecessary `rm` from requirements build. --- .github/workflows/requirements.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index ffc70ed14..14a4b5d9f 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -32,9 +32,7 @@ jobs: - name: Update pip and install pip-tools run: pip install --upgrade pip pip-tools - name: Build requirements - run: | - rm -f requirements.txt - pip-compile --resolver=backtracking -v --upgrade -o requirements.txt + run: pip-compile --resolver=backtracking -v --upgrade -o requirements.txt - name: Store requirements as artifact uses: actions/upload-artifact@v3 with: From 4522913f91443e534d927d8f88ffd616ed3f411d Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 13:51:38 -0400 Subject: [PATCH 256/425] Build requirements for `macos-latest` machine. --- .github/workflows/requirements.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index 14a4b5d9f..5250f7fae 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -22,6 +22,8 @@ jobs: python-version: "3.11" - machine: windows-latest python-version: "3.10" + - machine: macos-latest + python-version: "3.10" runs-on: ${{ matrix.runtime.machine }} steps: - uses: actions/checkout@v3 From bd97c8a5c836eac4bff05f362f809cee76cc3951 Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 13:58:01 -0400 Subject: [PATCH 257/425] Use `runtime` (including machine and python version) matrix to run tests. --- .github/workflows/main.yml | 74 ++++++++++--------- Makefile | 2 +- .../{3.10.txt => ubuntu-latest-3.10.txt} | 0 .../{3.11.txt => ubuntu-latest-3.11.txt} | 0 .../{3.7.txt => ubuntu-latest-3.7.txt} | 0 .../{3.8.txt => ubuntu-latest-3.8.txt} | 0 .../{3.9.txt => ubuntu-latest-3.9.txt} | 0 7 files changed, 41 insertions(+), 35 deletions(-) rename requirements/{3.10.txt => ubuntu-latest-3.10.txt} (100%) rename requirements/{3.11.txt => ubuntu-latest-3.11.txt} (100%) rename requirements/{3.7.txt => ubuntu-latest-3.7.txt} (100%) rename requirements/{3.8.txt => ubuntu-latest-3.8.txt} (100%) rename requirements/{3.9.txt => ubuntu-latest-3.9.txt} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d139b121..c15866963 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,49 +9,54 @@ jobs: build: strategy: matrix: - python-version: - - "3.7" - - "3.8" - - "3.9" - - "3.10" - - "3.11" + runtime: + - machine: ubuntu-latest + python-version: "3.7" + - machine: ubuntu-latest + python-version: "3.8" + - machine: ubuntu-latest + python-version: "3.9" + - machine: ubuntu-latest + python-version: "3.10" + - machine: ubuntu-latest + python-version: "3.11" component: - - arangodb - - azurite - - clickhouse - - compose - - core - - elasticsearch - - google - - kafka - - keycloak - - localstack - - meta - - minio - - mongodb - - mssql - - mysql - - neo4j - - nginx - - opensearch - - oracle - - postgres - - rabbitmq - - redis - - selenium - runs-on: ubuntu-latest + - arangodb + - azurite + - clickhouse + - compose + - core + - elasticsearch + - google + - kafka + - keycloak + - localstack + - meta + - minio + - mongodb + - mssql + - mysql + - neo4j + - nginx + - opensearch + - oracle + - postgres + - rabbitmq + - redis + - selenium + runs-on: ${{ matrix.runtime.machine }} steps: - uses: actions/checkout@v3 - - name: Setup python ${{ matrix.python-version }} + - name: Setup python ${{ matrix.runtime.python-version }} uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.runtime.python-version }} cache: pip cache-dependency-path: ${{ format('requirements/{0}.txt', matrix.python-version) }} - name: Install Python dependencies run: | pip install --upgrade pip - pip install -r requirements/${{ matrix.python-version }}.txt + pip install -r requirements/${{ matrix.runtime.machine }}-${{ matrix.runtime.python-version }}.txt - name: Run docker diagnostics if: matrix.component == 'core' run: | @@ -78,7 +83,8 @@ jobs: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository_owner == 'testcontainers' - && matrix.python-version == '3.10' + && matrix.runtime.python-version == '3.10' + && matrix.runtime.machine == 'ubuntu-latest' env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} diff --git a/Makefile b/Makefile index 155847a6d..5e9fa1818 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 3.11 PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} -REQUIREMENTS = $(addprefix requirements/,${PYTHON_VERSIONS:=.txt}) +REQUIREMENTS = $(addprefix requirements/ubuntu-latest-,${PYTHON_VERSIONS:=.txt}) RUN = docker run --rm -it # Get all directories that contain a setup.py and get the directory name. PACKAGES = $(subst /,,$(dir $(wildcard */setup.py))) diff --git a/requirements/3.10.txt b/requirements/ubuntu-latest-3.10.txt similarity index 100% rename from requirements/3.10.txt rename to requirements/ubuntu-latest-3.10.txt diff --git a/requirements/3.11.txt b/requirements/ubuntu-latest-3.11.txt similarity index 100% rename from requirements/3.11.txt rename to requirements/ubuntu-latest-3.11.txt diff --git a/requirements/3.7.txt b/requirements/ubuntu-latest-3.7.txt similarity index 100% rename from requirements/3.7.txt rename to requirements/ubuntu-latest-3.7.txt diff --git a/requirements/3.8.txt b/requirements/ubuntu-latest-3.8.txt similarity index 100% rename from requirements/3.8.txt rename to requirements/ubuntu-latest-3.8.txt diff --git a/requirements/3.9.txt b/requirements/ubuntu-latest-3.9.txt similarity index 100% rename from requirements/3.9.txt rename to requirements/ubuntu-latest-3.9.txt From 920699c7eb130c88570098fc25aa0041a6f489dc Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 14:12:12 -0400 Subject: [PATCH 258/425] Fix requirement paths. --- .github/workflows/docs.yml | 4 ++-- .github/workflows/main.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0188d340a..266d60dd2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,10 +15,10 @@ jobs: with: python-version: "3.10" cache: pip - cache-dependency-path: requirements/3.10.txt + cache-dependency-path: requirements/ubuntu-latest-3.10.txt - name: Install Python dependencies run: | pip install --upgrade pip - pip install -r requirements/3.10.txt + pip install -r requirements/ubuntu-latest-3.10.txt - name: Build documentation run: make docs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c15866963..84b8ca101 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,7 +52,7 @@ jobs: with: python-version: ${{ matrix.runtime.python-version }} cache: pip - cache-dependency-path: ${{ format('requirements/{0}.txt', matrix.python-version) }} + cache-dependency-path: ${{ format('requirements/{0}-{1}.txt', matrix.runtime.machine, matrix.runtime.python-version) }} - name: Install Python dependencies run: | pip install --upgrade pip From bd81fc75884ec701781a5d6052c6f63debf04a2f Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Sat, 6 May 2023 15:46:07 -0400 Subject: [PATCH 259/425] Add script to fetch requirements from GitHub Action artifacts. --- .../PULL_REQUEST_TEMPLATE/new_container.md | 2 +- .github/workflows/requirements.yml | 1 + .gitignore | 1 + Makefile | 10 +- README.rst | 9 +- get_requirements.py | 94 ++++ requirements/macos-latest-3.10.txt | 445 +++++++++++++++++ requirements/ubuntu-latest-3.10.txt | 12 +- requirements/ubuntu-latest-3.11.txt | 12 +- requirements/ubuntu-latest-3.7.txt | 12 +- requirements/ubuntu-latest-3.8.txt | 12 +- requirements/ubuntu-latest-3.9.txt | 12 +- requirements/windows-latest-3.10.txt | 457 ++++++++++++++++++ 13 files changed, 1031 insertions(+), 48 deletions(-) create mode 100644 get_requirements.py create mode 100644 requirements/macos-latest-3.10.txt create mode 100644 requirements/windows-latest-3.10.txt diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index 9a3359eb5..29b8190d4 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -2,7 +2,7 @@ You have implemented a new container and would like to contribute it? Great! Her - [ ] Create a new feature directory and populate it with the package structure [described in the documentation](https://testcontainers-python.readthedocs.io/en/latest/#package-structure). Copying one of the existing features is likely the best way to get started. - [ ] Implement the new feature (typically in `__init__.py`) and corresponding tests. -- [ ] Add a line `-e file:[feature name]` to `requirements.in` and run `make requirements`. This command will find any new requirements and generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Then run `pip install -r requirements/[your python version].txt` to install the new requirements. - [ ] Update the feature `README.rst` and add it to the table of contents (`toctree` directive) in the top-level `README.rst`. - [ ] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `main` branch. - [ ] Rebase your development branch on `main` (or merge `main` into your development branch). +- [ ] Add a line `-e file:[feature name]` to `requirements.in` and open a pull request. Opening a pull request will automatically generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Finally, run `python get_requirements.py --pr=[your PR number]` to fetch the updated requirement files (the build needs to have succeeded). diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml index 5250f7fae..72c41f302 100644 --- a/.github/workflows/requirements.yml +++ b/.github/workflows/requirements.yml @@ -8,6 +8,7 @@ on: jobs: requirements: strategy: + fail-fast: false matrix: runtime: - machine: ubuntu-latest diff --git a/.gitignore b/.gitignore index a2c626860..3da297de6 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ venv .DS_Store .python-version .env +.github-token diff --git a/Makefile b/Makefile index 5e9fa1818..501172c97 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 3.11 PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} -REQUIREMENTS = $(addprefix requirements/ubuntu-latest-,${PYTHON_VERSIONS:=.txt}) RUN = docker run --rm -it # Get all directories that contain a setup.py and get the directory name. PACKAGES = $(subst /,,$(dir $(wildcard */setup.py))) @@ -43,7 +42,7 @@ ${UPLOAD} : %/upload : fi # Targets to build docker images -image: requirements/${PYTHON_VERSION}.txt +image: requirements/ubunut-latest-${PYTHON_VERSION}.txt docker build --build-arg version=${PYTHON_VERSION} -t ${IMAGE} . # Targets to run tests in docker containers @@ -63,13 +62,6 @@ doctest : ${DOCTESTS} ${DOCTESTS} : %/doctest : sphinx-build -b doctest -c doctests $* docs/_build -# Targets to build requirement files -requirements : ${REQUIREMENTS} -${REQUIREMENTS} : requirements/%.txt : requirements.in */setup.py - mkdir -p $(dir $@) - ${RUN} -w /workspace -v `pwd`:/workspace --platform=linux/amd64 python:$* bash -c \ - "pip install pip-tools && pip-compile --resolver=backtracking -v --upgrade -o $@ $<" - # Remove any generated files. clean : rm -rf docs/_build diff --git a/README.rst b/README.rst index 65b094764..f490e76b6 100644 --- a/README.rst +++ b/README.rst @@ -108,11 +108,4 @@ Testcontainers is a collection of `implicit namespace packages `__ documentation for details). Then run :code:`pip install -r requirements/[your python version].txt` to install the new requirements. -4. Update the feature :code:`README.rst` and add it to the table of contents (:code:`toctree` directive) in the top-level :code:`README.rst`. -5. Add a line :code:`[feature name]` to the list of components in the GitHub Action workflow in :code:`.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the :code:`main` branch. -6. Rebase your development branch on :code:`main` (or merge :code:`main` into your development branch). +You want to contribute a new feature or container? Great! You can do that in six steps as outlined `here __`. diff --git a/get_requirements.py b/get_requirements.py new file mode 100644 index 000000000..b1ebc6db1 --- /dev/null +++ b/get_requirements.py @@ -0,0 +1,94 @@ +import argparse +import io +import pathlib +import requests +import shutil +import tempfile +import zipfile + + +def __main__() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--owner", default="testcontainers") + parser.add_argument("--repo", default="testcontainers-python") + parser.add_argument("--run", help="GitHub Action run id") + parser.add_argument("--pr", help="GitHub PR number") + parser.add_argument("--branch", default="main") + parser.add_argument("--token", help="GitHub autentication token") + args = parser.parse_args() + + # Get an access token. + if args.token: + token = args.token + elif (path := pathlib.Path(".github-token")).is_file(): + token = path.read_text().strip() + else: + token = input("we need a GitHub access token to fetch the requirements; please visit " + "https://github.com/settings/tokens/new, create a token with `public_repo` " + "scope, and paste it here: ").strip() + cache = input("do you want to cache the token in a `.github-token` file [Ny]? ") + if cache.lower().startswith("y"): + path.write_text(token) + + headers = { + "Authorization": f"Bearer {token}", + } + base_url = f"https://api.github.com/repos/{args.owner}/{args.repo}" + + if args.run: # Run id was specified. + run = args.run + elif args.pr: # PR was specified, let's get the most recent run id. + print(f"fetching most recent commit for PR #{args.pr}") + response = requests.get(f"{base_url}/pulls/{args.pr}", headers=headers) + response.raise_for_status() + response = response.json() + head_sha = response["head"]["sha"] + else: # Nothing was specified, let's get the most recent run id on the main branch. + print(f"fetching most recent commit for branch `{args.branch}`") + response = requests.get(f"{base_url}/branches/{args.branch}", headers=headers) + response.raise_for_status() + response = response.json() + head_sha = response["commit"]["sha"] + + # List all completed runs and find the one that generated the requirements. + response = requests.get(f"{base_url}/actions/runs", headers=headers, params={ + "head_sha": head_sha, + "status": "success", + }) + response.raise_for_status() + response = response.json() + + # Get the requirements run. + runs = [run for run in response["workflow_runs"] if + run["path"].endswith("requirements.yml")] + if len(runs) != 1: + raise RuntimeError(f"could not identify unique workflow run: {runs}") + run = runs[0]["id"] + + # Get all the artifacts. + print(f"fetching artifacts for run {run} ...") + url = f"{base_url}/actions/runs/{run}/artifacts" + response = requests.get(url, headers=headers) + response.raise_for_status() + response = response.json() + artifacts = response["artifacts"] + print(f"discovered {len(artifacts)} artifacts") + + # Get the content for each artifact and save it. + for artifact in artifacts: + name: str = artifact["name"] + name = name.removeprefix("requirements-") + print(f"fetching artifact {name} ...") + response = requests.get(artifact["archive_download_url"], headers=headers) + response.raise_for_status() + with zipfile.ZipFile(io.BytesIO(response.content)) as zip, \ + tempfile.TemporaryDirectory() as tempdir: + zip.extract("requirements.txt", tempdir) + shutil.move(pathlib.Path(tempdir) / "requirements.txt", + pathlib.Path("requirements") / name) + + print("done") + + +if __name__ == "__main__": + __main__() diff --git a/requirements/macos-latest-3.10.txt b/requirements/macos-latest-3.10.txt new file mode 100644 index 000000000..429a054fd --- /dev/null +++ b/requirements/macos-latest-3.10.txt @@ -0,0 +1,445 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements.txt --resolver=backtracking +# +-e file:meta + # via -r requirements.in +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium + # via -r requirements.in +alabaster==0.7.13 + # via sphinx +asn1crypto==1.5.1 + # via scramp +async-generator==1.10 + # via trio +async-timeout==4.0.2 + # via redis +attrs==23.1.0 + # via + # jsonschema + # outcome + # trio +azure-core==1.26.4 + # via azure-storage-blob +azure-storage-blob==12.16.0 + # via testcontainers-azurite +babel==2.12.1 + # via sphinx +bcrypt==4.0.1 + # via paramiko +bleach==6.0.0 + # via readme-renderer +boto3==1.26.129 + # via testcontainers-localstack +botocore==1.29.129 + # via + # boto3 + # s3transfer +cachetools==5.3.0 + # via google-auth +certifi==2022.12.7 + # via + # minio + # opensearch-py + # requests + # selenium +cffi==1.15.1 + # via + # cryptography + # pynacl +charset-normalizer==3.1.0 + # via requests +clickhouse-driver==0.2.6 + # via testcontainers-clickhouse +coverage[toml]==7.2.5 + # via pytest-cov +cryptography==36.0.2 + # via + # -r requirements.in + # azure-storage-blob + # paramiko +cx-oracle==8.3.0 + # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak +distro==1.8.0 + # via docker-compose +dnspython==2.3.0 + # via pymongo +docker[ssh]==6.1.0 + # via + # docker-compose + # testcontainers-core +docker-compose==1.29.2 + # via testcontainers-compose +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.19 + # via + # readme-renderer + # sphinx +ecdsa==0.18.0 + # via python-jose +entrypoints==0.3 + # via flake8 +exceptiongroup==1.1.1 + # via + # pytest + # trio + # trio-websocket +flake8==3.7.9 + # via -r requirements.in +google-api-core[grpc]==2.11.0 + # via google-cloud-pubsub +google-auth==2.17.3 + # via google-api-core +google-cloud-pubsub==2.16.1 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.59.0 + # via + # google-api-core + # grpc-google-iam-v1 + # grpcio-status +greenlet==2.0.2 + # via sqlalchemy +grpc-google-iam-v1==0.12.6 + # via google-cloud-pubsub +grpcio==1.54.0 + # via + # google-api-core + # google-cloud-pubsub + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +grpcio-status==1.54.0 + # via + # google-api-core + # google-cloud-pubsub +h11==0.14.0 + # via wsproto +idna==3.4 + # via + # requests + # trio +imagesize==1.4.1 + # via sphinx +importlib-metadata==6.6.0 + # via + # keyring + # twine +iniconfig==2.0.0 + # via pytest +isodate==0.6.1 + # via azure-storage-blob +jaraco-classes==3.2.3 + # via keyring +jinja2==3.1.2 + # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore +jsonschema==3.2.0 + # via docker-compose +kafka-python==2.0.2 + # via testcontainers-kafka +keyring==23.13.1 + # via twine +markdown-it-py==2.2.0 + # via rich +markupsafe==2.1.2 + # via jinja2 +mccabe==0.6.1 + # via flake8 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.14 + # via testcontainers-minio +more-itertools==9.1.0 + # via jaraco-classes +neo4j==5.8.0 + # via testcontainers-neo4j +opensearch-py==2.2.0 + # via testcontainers-opensearch +outcome==1.2.0 + # via trio +packaging==23.1 + # via + # deprecation + # docker + # pytest + # sphinx +paramiko==3.1.0 + # via docker +pg8000==1.29.4 + # via -r requirements.in +pika==1.3.2 + # via testcontainers-rabbitmq +pkginfo==1.9.6 + # via twine +pluggy==1.0.0 + # via pytest +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.4 + # via + # google-api-core + # google-cloud-pubsub + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status + # proto-plus +psycopg2-binary==2.9.6 + # via testcontainers-postgres +pyasn1==0.5.0 + # via + # pyasn1-modules + # python-jose + # rsa +pyasn1-modules==0.3.0 + # via google-auth +pycodestyle==2.5.0 + # via flake8 +pycparser==2.21 + # via cffi +pyflakes==2.1.1 + # via flake8 +pygments==2.15.1 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 + # via python-arango +pymongo==4.3.3 + # via testcontainers-mongodb +pymssql==2.2.7 + # via testcontainers-mssql +pymysql==1.0.3 + # via testcontainers-mysql +pynacl==1.5.0 + # via paramiko +pyrsistent==0.19.3 + # via jsonschema +pysocks==1.7.1 + # via urllib3 +pytest==7.3.1 + # via + # -r requirements.in + # pytest-cov +pytest-cov==4.0.0 + # via -r requirements.in +python-arango==7.5.7 + # via testcontainers-arangodb +python-dateutil==2.8.2 + # via + # botocore + # opensearch-py + # pg8000 +python-dotenv==0.21.1 + # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==2.16.1 + # via testcontainers-keycloak +pytz==2023.3 + # via + # clickhouse-driver + # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal +pyyaml==5.4.1 + # via docker-compose +readme-renderer==37.3 + # via twine +redis==4.5.4 + # via testcontainers-redis +requests==2.30.0 + # via + # azure-core + # docker + # docker-compose + # google-api-core + # opensearch-py + # python-arango + # python-keycloak + # requests-toolbelt + # sphinx + # twine +requests-toolbelt==0.10.1 + # via + # python-arango + # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==13.3.5 + # via twine +rsa==4.9 + # via + # google-auth + # python-jose +s3transfer==0.6.1 + # via boto3 +scramp==1.4.4 + # via pg8000 +selenium==4.9.0 + # via testcontainers-selenium +six==1.16.0 + # via + # azure-core + # bleach + # dockerpty + # ecdsa + # google-auth + # isodate + # jsonschema + # opensearch-py + # python-dateutil + # websocket-client +sniffio==1.3.0 + # via trio +snowballstemmer==2.2.0 + # via sphinx +sortedcontainers==2.4.0 + # via trio +sphinx==7.0.0 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.4 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.1 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sqlalchemy==2.0.12 + # via + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres +texttable==1.6.7 + # via docker-compose +tomli==2.0.1 + # via + # coverage + # pytest +trio==0.22.0 + # via + # selenium + # trio-websocket +trio-websocket==0.10.2 + # via selenium +twine==4.0.2 + # via -r requirements.in +typing-extensions==4.5.0 + # via + # azure-core + # azure-storage-blob + # sqlalchemy +tzdata==2023.3 + # via pytz-deprecation-shim +tzlocal==4.3 + # via clickhouse-driver +urllib3[socks]==1.26.15 + # via + # botocore + # docker + # minio + # opensearch-py + # python-arango + # requests + # selenium + # testcontainers-core + # twine +webencodings==0.5.1 + # via bleach +websocket-client==0.59.0 + # via + # docker + # docker-compose +wheel==0.40.0 + # via -r requirements.in +wrapt==1.15.0 + # via testcontainers-core +wsproto==1.2.0 + # via trio-websocket +zipp==3.15.0 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/requirements/ubuntu-latest-3.10.txt b/requirements/ubuntu-latest-3.10.txt index f1a64157f..ce6573279 100644 --- a/requirements/ubuntu-latest-3.10.txt +++ b/requirements/ubuntu-latest-3.10.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/3.10.txt --resolver=backtracking requirements.in +# pip-compile --output-file=requirements.txt --resolver=backtracking # -e file:meta # via -r requirements.in @@ -96,9 +96,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.127 +boto3==1.26.129 # via testcontainers-localstack -botocore==1.29.127 +botocore==1.29.129 # via # boto3 # s3transfer @@ -134,7 +134,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.0.1 +docker[ssh]==6.1.0 # via # docker-compose # testcontainers-core @@ -163,7 +163,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.3 # via google-api-core -google-cloud-pubsub==2.16.0 +google-cloud-pubsub==2.16.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -247,7 +247,7 @@ paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in -pika==1.3.1 +pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine diff --git a/requirements/ubuntu-latest-3.11.txt b/requirements/ubuntu-latest-3.11.txt index cbd63b4a8..cac9cd549 100644 --- a/requirements/ubuntu-latest-3.11.txt +++ b/requirements/ubuntu-latest-3.11.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --output-file=requirements/3.11.txt --resolver=backtracking requirements.in +# pip-compile --output-file=requirements.txt --resolver=backtracking # -e file:meta # via -r requirements.in @@ -96,9 +96,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.127 +boto3==1.26.129 # via testcontainers-localstack -botocore==1.29.127 +botocore==1.29.129 # via # boto3 # s3transfer @@ -134,7 +134,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.0.1 +docker[ssh]==6.1.0 # via # docker-compose # testcontainers-core @@ -160,7 +160,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.3 # via google-api-core -google-cloud-pubsub==2.16.0 +google-cloud-pubsub==2.16.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -244,7 +244,7 @@ paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in -pika==1.3.1 +pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine diff --git a/requirements/ubuntu-latest-3.7.txt b/requirements/ubuntu-latest-3.7.txt index a19856f04..fbe67cf4f 100644 --- a/requirements/ubuntu-latest-3.7.txt +++ b/requirements/ubuntu-latest-3.7.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --output-file=requirements/3.7.txt --resolver=backtracking requirements.in +# pip-compile --output-file=requirements.txt --resolver=backtracking # -e file:meta # via -r requirements.in @@ -100,9 +100,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.127 +boto3==1.26.129 # via testcontainers-localstack -botocore==1.29.127 +botocore==1.29.129 # via # boto3 # s3transfer @@ -140,7 +140,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.0.1 +docker[ssh]==6.1.0 # via # docker-compose # testcontainers-core @@ -169,7 +169,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.3 # via google-api-core -google-cloud-pubsub==2.16.0 +google-cloud-pubsub==2.16.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -264,7 +264,7 @@ paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in -pika==1.3.1 +pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine diff --git a/requirements/ubuntu-latest-3.8.txt b/requirements/ubuntu-latest-3.8.txt index 530e0fd64..70a86aa02 100644 --- a/requirements/ubuntu-latest-3.8.txt +++ b/requirements/ubuntu-latest-3.8.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --output-file=requirements/3.8.txt --resolver=backtracking requirements.in +# pip-compile --output-file=requirements.txt --resolver=backtracking # -e file:meta # via -r requirements.in @@ -100,9 +100,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.127 +boto3==1.26.129 # via testcontainers-localstack -botocore==1.29.127 +botocore==1.29.129 # via # boto3 # s3transfer @@ -138,7 +138,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.0.1 +docker[ssh]==6.1.0 # via # docker-compose # testcontainers-core @@ -167,7 +167,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.3 # via google-api-core -google-cloud-pubsub==2.16.0 +google-cloud-pubsub==2.16.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -254,7 +254,7 @@ paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in -pika==1.3.1 +pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine diff --git a/requirements/ubuntu-latest-3.9.txt b/requirements/ubuntu-latest-3.9.txt index 5009c1f7a..a1e4dd7fa 100644 --- a/requirements/ubuntu-latest-3.9.txt +++ b/requirements/ubuntu-latest-3.9.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --output-file=requirements/3.9.txt --resolver=backtracking requirements.in +# pip-compile --output-file=requirements.txt --resolver=backtracking # -e file:meta # via -r requirements.in @@ -96,9 +96,9 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.127 +boto3==1.26.129 # via testcontainers-localstack -botocore==1.29.127 +botocore==1.29.129 # via # boto3 # s3transfer @@ -134,7 +134,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.0.1 +docker[ssh]==6.1.0 # via # docker-compose # testcontainers-core @@ -163,7 +163,7 @@ google-api-core[grpc]==2.11.0 # via google-cloud-pubsub google-auth==2.17.3 # via google-api-core -google-cloud-pubsub==2.16.0 +google-cloud-pubsub==2.16.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -248,7 +248,7 @@ paramiko==3.1.0 # via docker pg8000==1.29.4 # via -r requirements.in -pika==1.3.1 +pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine diff --git a/requirements/windows-latest-3.10.txt b/requirements/windows-latest-3.10.txt new file mode 100644 index 000000000..c567ecf4c --- /dev/null +++ b/requirements/windows-latest-3.10.txt @@ -0,0 +1,457 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements.txt --resolver=backtracking +# +-e file:meta + # via -r requirements.in +-e file:arangodb + # via -r requirements.in +-e file:azurite + # via -r requirements.in +-e file:clickhouse + # via -r requirements.in +-e file:compose + # via -r requirements.in +-e file:core + # via + # -r requirements.in + # testcontainers + # testcontainers-arangodb + # testcontainers-azurite + # testcontainers-clickhouse + # testcontainers-compose + # testcontainers-elasticsearch + # testcontainers-gcp + # testcontainers-kafka + # testcontainers-keycloak + # testcontainers-localstack + # testcontainers-minio + # testcontainers-mongodb + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-neo4j + # testcontainers-nginx + # testcontainers-opensearch + # testcontainers-oracle + # testcontainers-postgres + # testcontainers-rabbitmq + # testcontainers-redis + # testcontainers-selenium +-e file:elasticsearch + # via -r requirements.in +-e file:google + # via -r requirements.in +-e file:kafka + # via -r requirements.in +-e file:keycloak + # via -r requirements.in +-e file:localstack + # via -r requirements.in +-e file:minio + # via -r requirements.in +-e file:mongodb + # via -r requirements.in +-e file:mssql + # via -r requirements.in +-e file:mysql + # via -r requirements.in +-e file:neo4j + # via -r requirements.in +-e file:nginx + # via -r requirements.in +-e file:opensearch + # via -r requirements.in +-e file:oracle + # via -r requirements.in +-e file:postgres + # via -r requirements.in +-e file:rabbitmq + # via -r requirements.in +-e file:redis + # via -r requirements.in +-e file:selenium + # via -r requirements.in +alabaster==0.7.13 + # via sphinx +asn1crypto==1.5.1 + # via scramp +async-generator==1.10 + # via trio +async-timeout==4.0.2 + # via redis +attrs==23.1.0 + # via + # jsonschema + # outcome + # trio +azure-core==1.26.4 + # via azure-storage-blob +azure-storage-blob==12.16.0 + # via testcontainers-azurite +babel==2.12.1 + # via sphinx +bcrypt==4.0.1 + # via paramiko +bleach==6.0.0 + # via readme-renderer +boto3==1.26.129 + # via testcontainers-localstack +botocore==1.29.129 + # via + # boto3 + # s3transfer +cachetools==5.3.0 + # via google-auth +certifi==2022.12.7 + # via + # minio + # opensearch-py + # requests + # selenium +cffi==1.15.1 + # via + # cryptography + # pynacl + # trio +charset-normalizer==3.1.0 + # via requests +clickhouse-driver==0.2.6 + # via testcontainers-clickhouse +colorama==0.4.6 + # via + # docker-compose + # pytest + # sphinx +coverage[toml]==7.2.5 + # via pytest-cov +cryptography==36.0.2 + # via + # -r requirements.in + # azure-storage-blob + # paramiko +cx-oracle==8.3.0 + # via testcontainers-oracle +deprecation==2.1.0 + # via python-keycloak +distro==1.8.0 + # via docker-compose +dnspython==2.3.0 + # via pymongo +docker[ssh]==6.1.0 + # via + # docker-compose + # testcontainers-core +docker-compose==1.29.2 + # via testcontainers-compose +dockerpty==0.4.1 + # via docker-compose +docopt==0.6.2 + # via docker-compose +docutils==0.19 + # via + # readme-renderer + # sphinx +ecdsa==0.18.0 + # via python-jose +entrypoints==0.3 + # via flake8 +exceptiongroup==1.1.1 + # via + # pytest + # trio + # trio-websocket +flake8==3.7.9 + # via -r requirements.in +google-api-core[grpc]==2.11.0 + # via google-cloud-pubsub +google-auth==2.17.3 + # via google-api-core +google-cloud-pubsub==2.16.1 + # via testcontainers-gcp +googleapis-common-protos[grpc]==1.59.0 + # via + # google-api-core + # grpc-google-iam-v1 + # grpcio-status +greenlet==2.0.2 + # via sqlalchemy +grpc-google-iam-v1==0.12.6 + # via google-cloud-pubsub +grpcio==1.54.0 + # via + # google-api-core + # google-cloud-pubsub + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status +grpcio-status==1.54.0 + # via + # google-api-core + # google-cloud-pubsub +h11==0.14.0 + # via wsproto +idna==3.4 + # via + # requests + # trio +imagesize==1.4.1 + # via sphinx +importlib-metadata==6.6.0 + # via + # keyring + # twine +iniconfig==2.0.0 + # via pytest +isodate==0.6.1 + # via azure-storage-blob +jaraco-classes==3.2.3 + # via keyring +jinja2==3.1.2 + # via sphinx +jmespath==1.0.1 + # via + # boto3 + # botocore +jsonschema==3.2.0 + # via docker-compose +kafka-python==2.0.2 + # via testcontainers-kafka +keyring==23.13.1 + # via twine +markdown-it-py==2.2.0 + # via rich +markupsafe==2.1.2 + # via jinja2 +mccabe==0.6.1 + # via flake8 +mdurl==0.1.2 + # via markdown-it-py +minio==7.1.14 + # via testcontainers-minio +more-itertools==9.1.0 + # via jaraco-classes +neo4j==5.8.0 + # via testcontainers-neo4j +opensearch-py==2.2.0 + # via testcontainers-opensearch +outcome==1.2.0 + # via trio +packaging==23.1 + # via + # deprecation + # docker + # pytest + # sphinx +paramiko==3.1.0 + # via docker +pg8000==1.29.4 + # via -r requirements.in +pika==1.3.2 + # via testcontainers-rabbitmq +pkginfo==1.9.6 + # via twine +pluggy==1.0.0 + # via pytest +proto-plus==1.22.2 + # via google-cloud-pubsub +protobuf==4.22.4 + # via + # google-api-core + # google-cloud-pubsub + # googleapis-common-protos + # grpc-google-iam-v1 + # grpcio-status + # proto-plus +psycopg2-binary==2.9.6 + # via testcontainers-postgres +pyasn1==0.5.0 + # via + # pyasn1-modules + # python-jose + # rsa +pyasn1-modules==0.3.0 + # via google-auth +pycodestyle==2.5.0 + # via flake8 +pycparser==2.21 + # via cffi +pyflakes==2.1.1 + # via flake8 +pygments==2.15.1 + # via + # readme-renderer + # rich + # sphinx +pyjwt==2.6.0 + # via python-arango +pymongo==4.3.3 + # via testcontainers-mongodb +pymssql==2.2.7 + # via testcontainers-mssql +pymysql==1.0.3 + # via testcontainers-mysql +pynacl==1.5.0 + # via paramiko +pyrsistent==0.19.3 + # via jsonschema +pysocks==1.7.1 + # via urllib3 +pytest==7.3.1 + # via + # -r requirements.in + # pytest-cov +pytest-cov==4.0.0 + # via -r requirements.in +python-arango==7.5.7 + # via testcontainers-arangodb +python-dateutil==2.8.2 + # via + # botocore + # opensearch-py + # pg8000 +python-dotenv==0.21.1 + # via docker-compose +python-jose==3.3.0 + # via python-keycloak +python-keycloak==2.16.1 + # via testcontainers-keycloak +pytz==2023.3 + # via + # clickhouse-driver + # neo4j +pytz-deprecation-shim==0.1.0.post0 + # via tzlocal +pywin32==306 + # via docker +pywin32-ctypes==0.2.0 + # via keyring +pyyaml==5.4.1 + # via docker-compose +readme-renderer==37.3 + # via twine +redis==4.5.4 + # via testcontainers-redis +requests==2.30.0 + # via + # azure-core + # docker + # docker-compose + # google-api-core + # opensearch-py + # python-arango + # python-keycloak + # requests-toolbelt + # sphinx + # twine +requests-toolbelt==0.10.1 + # via + # python-arango + # python-keycloak + # twine +rfc3986==2.0.0 + # via twine +rich==13.3.5 + # via twine +rsa==4.9 + # via + # google-auth + # python-jose +s3transfer==0.6.1 + # via boto3 +scramp==1.4.4 + # via pg8000 +selenium==4.9.0 + # via testcontainers-selenium +six==1.16.0 + # via + # azure-core + # bleach + # dockerpty + # ecdsa + # google-auth + # isodate + # jsonschema + # opensearch-py + # python-dateutil + # websocket-client +sniffio==1.3.0 + # via trio +snowballstemmer==2.2.0 + # via sphinx +sortedcontainers==2.4.0 + # via trio +sphinx==7.0.0 + # via -r requirements.in +sphinxcontrib-applehelp==1.0.4 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.1 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sqlalchemy==2.0.12 + # via + # testcontainers-mssql + # testcontainers-mysql + # testcontainers-oracle + # testcontainers-postgres +texttable==1.6.7 + # via docker-compose +tomli==2.0.1 + # via + # coverage + # pytest +trio==0.22.0 + # via + # selenium + # trio-websocket +trio-websocket==0.10.2 + # via selenium +twine==4.0.2 + # via -r requirements.in +typing-extensions==4.5.0 + # via + # azure-core + # azure-storage-blob + # sqlalchemy +tzdata==2023.3 + # via + # pytz-deprecation-shim + # tzlocal +tzlocal==4.3 + # via clickhouse-driver +urllib3[socks]==1.26.15 + # via + # botocore + # docker + # minio + # opensearch-py + # python-arango + # requests + # selenium + # testcontainers-core + # twine +webencodings==0.5.1 + # via bleach +websocket-client==0.59.0 + # via + # docker + # docker-compose +wheel==0.40.0 + # via -r requirements.in +wrapt==1.15.0 + # via testcontainers-core +wsproto==1.2.0 + # via trio-websocket +zipp==3.15.0 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# setuptools From f3c01154a679a25da21a37a1f81050d3294f385c Mon Sep 17 00:00:00 2001 From: joejoe2 <39475537+joejoe2@users.noreply.github.com> Date: Tue, 9 May 2023 10:35:21 +0800 Subject: [PATCH 260/425] fix typing in the doc wait_for_logs --- core/testcontainers/core/waiting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 933a417c0..d177d2b69 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -83,7 +83,7 @@ def wait_for_logs(container: "DockerContainer", predicate: Union[Callable, str], Args: container: Container whose logs to wait for. - predicate: Predicate that should be satisfied by the logs. If a string, the it is used as + predicate: Predicate that should be satisfied by the logs. If a string, then it is used as the pattern for a multiline regular expression search. timeout: Number of seconds to wait for the predicate to be satisfied. Defaults to wait indefinitely. From 9651c1036e34f9b1422dfe3fcc124365c85326ad Mon Sep 17 00:00:00 2001 From: Balint Bartha <104063957+balint-backmaker@users.noreply.github.com> Date: Tue, 9 May 2023 08:58:49 +0200 Subject: [PATCH 261/425] Update compose/testcontainers/compose/__init__.py follow docstring recommendation from @tillahoffmann Co-authored-by: Till Hoffmann --- compose/testcontainers/compose/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py index 32221d9b2..3a785a3c0 100644 --- a/compose/testcontainers/compose/__init__.py +++ b/compose/testcontainers/compose/__init__.py @@ -17,7 +17,7 @@ class DockerCompose: pull: Pull images before launching environment. build: Build images referenced in the configuration file. env_file: Path to an env file containing environment variables to pass to docker compose. - services: The list of services to be run when starting this DockerCompose. + services: List of services to start. Example: From 1f88d341755120457e3882fb70e545b0e5e47e1d Mon Sep 17 00:00:00 2001 From: Till Hoffmann Date: Tue, 9 May 2023 09:59:17 -0400 Subject: [PATCH 262/425] Improve error message for missing requirements (cf. #319). --- get_requirements.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/get_requirements.py b/get_requirements.py index b1ebc6db1..549f05efd 100644 --- a/get_requirements.py +++ b/get_requirements.py @@ -23,10 +23,10 @@ def __main__() -> None: elif (path := pathlib.Path(".github-token")).is_file(): token = path.read_text().strip() else: - token = input("we need a GitHub access token to fetch the requirements; please visit " + token = input("We need a GitHub access token to fetch the requirements. Please visit " "https://github.com/settings/tokens/new, create a token with `public_repo` " "scope, and paste it here: ").strip() - cache = input("do you want to cache the token in a `.github-token` file [Ny]? ") + cache = input("Do you want to cache the token in a `.github-token` file [Ny]? ") if cache.lower().startswith("y"): path.write_text(token) @@ -38,13 +38,13 @@ def __main__() -> None: if args.run: # Run id was specified. run = args.run elif args.pr: # PR was specified, let's get the most recent run id. - print(f"fetching most recent commit for PR #{args.pr}") + print(f"Fetching most recent commit for PR #{args.pr}.") response = requests.get(f"{base_url}/pulls/{args.pr}", headers=headers) response.raise_for_status() response = response.json() head_sha = response["head"]["sha"] else: # Nothing was specified, let's get the most recent run id on the main branch. - print(f"fetching most recent commit for branch `{args.branch}`") + print(f"Fetching most recent commit for branch `{args.branch}`.") response = requests.get(f"{base_url}/branches/{args.branch}", headers=headers) response.raise_for_status() response = response.json() @@ -61,8 +61,12 @@ def __main__() -> None: # Get the requirements run. runs = [run for run in response["workflow_runs"] if run["path"].endswith("requirements.yml")] + if not runs: + raise RuntimeError("Could not find a workflow. Has the GitHub Action run completed? If you" + "are a first-time contributor, a contributor has to approve your changes" + "before Actions can run.") if len(runs) != 1: - raise RuntimeError(f"could not identify unique workflow run: {runs}") + raise RuntimeError(f"Could not identify unique workflow run: {runs}") run = runs[0]["id"] # Get all the artifacts. @@ -72,13 +76,13 @@ def __main__() -> None: response.raise_for_status() response = response.json() artifacts = response["artifacts"] - print(f"discovered {len(artifacts)} artifacts") + print(f"Discovered {len(artifacts)} artifacts.") # Get the content for each artifact and save it. for artifact in artifacts: name: str = artifact["name"] name = name.removeprefix("requirements-") - print(f"fetching artifact {name} ...") + print(f"Fetching artifact {name} ...") response = requests.get(artifact["archive_download_url"], headers=headers) response.raise_for_status() with zipfile.ZipFile(io.BytesIO(response.content)) as zip, \ @@ -87,7 +91,7 @@ def __main__() -> None: shutil.move(pathlib.Path(tempdir) / "requirements.txt", pathlib.Path("requirements") / name) - print("done") + print("Done.") if __name__ == "__main__": From 6fcde680e745a668ba709f4bd1ddfafbea53e03c Mon Sep 17 00:00:00 2001 From: Victor Ananyev Date: Sat, 11 Mar 2023 14:01:46 +0100 Subject: [PATCH 263/425] testcontainers-mysql requires pymysql with cryptogrpahy --- mysql/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql/setup.py b/mysql/setup.py index 4ca79d715..c09182e4c 100644 --- a/mysql/setup.py +++ b/mysql/setup.py @@ -13,7 +13,7 @@ install_requires=[ "testcontainers-core", "sqlalchemy", - "pymysql" + "pymysql[rsa]" ], python_requires=">=3.7", ) From 32cba7aa92f13ef66bbf62bcd97d120a447fdcb7 Mon Sep 17 00:00:00 2001 From: Victor Ananyev Date: Sat, 11 Mar 2023 14:14:02 +0100 Subject: [PATCH 264/425] add test for mysql8 --- mysql/tests/test_mysql.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mysql/tests/test_mysql.py b/mysql/tests/test_mysql.py index c4e48d1ff..274207b92 100644 --- a/mysql/tests/test_mysql.py +++ b/mysql/tests/test_mysql.py @@ -17,6 +17,17 @@ def test_docker_run_mysql(): assert row[0].startswith('5.7.17') +@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') +def test_docker_run_mysql_8(): + config = MySqlContainer('mysql:8') + with config as mysql: + engine = sqlalchemy.create_engine(mysql.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("select version()")) + for row in result: + assert row[0].startswith('8') + + def test_docker_run_mariadb(): with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: engine = sqlalchemy.create_engine(mariadb.get_connection_url()) From b9a203c3c87eeb2554554a51588ffd0dde4a70fc Mon Sep 17 00:00:00 2001 From: Victor Ananyev Date: Mon, 12 Jun 2023 16:40:56 +0200 Subject: [PATCH 265/425] got requirements from CI --- requirements/macos-latest-3.10.txt | 73 +++++++++++++------------- requirements/ubuntu-latest-3.10.txt | 73 +++++++++++++------------- requirements/ubuntu-latest-3.11.txt | 75 +++++++++++++-------------- requirements/ubuntu-latest-3.7.txt | 71 ++++++++++++------------- requirements/ubuntu-latest-3.8.txt | 77 +++++++++++++--------------- requirements/ubuntu-latest-3.9.txt | 73 +++++++++++++------------- requirements/windows-latest-3.10.txt | 75 +++++++++++++-------------- 7 files changed, 252 insertions(+), 265 deletions(-) diff --git a/requirements/macos-latest-3.10.txt b/requirements/macos-latest-3.10.txt index 429a054fd..bffd3237d 100644 --- a/requirements/macos-latest-3.10.txt +++ b/requirements/macos-latest-3.10.txt @@ -86,7 +86,7 @@ attrs==23.1.0 # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite @@ -96,15 +96,15 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -118,13 +118,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 @@ -133,7 +134,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -143,7 +144,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.19 +docutils==0.20.1 # via # readme-renderer # sphinx @@ -160,9 +161,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -173,14 +174,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -195,6 +196,7 @@ imagesize==1.4.1 importlib-metadata==6.6.0 # via # keyring + # python-arango # twine iniconfig==2.0.0 # via pytest @@ -216,17 +218,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -238,9 +240,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -250,7 +252,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -278,13 +280,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -296,9 +298,9 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.7 +python-arango==7.5.8 # via testcontainers-arangodb python-dateutil==2.8.2 # via @@ -309,21 +311,19 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -335,14 +335,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -352,7 +352,7 @@ s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -372,7 +372,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.0 +sphinx==7.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -386,7 +386,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -406,19 +406,18 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # azure-core # azure-storage-blob # sqlalchemy -tzdata==2023.3 - # via pytz-deprecation-shim -tzlocal==4.3 +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango diff --git a/requirements/ubuntu-latest-3.10.txt b/requirements/ubuntu-latest-3.10.txt index ce6573279..5a06928f1 100644 --- a/requirements/ubuntu-latest-3.10.txt +++ b/requirements/ubuntu-latest-3.10.txt @@ -86,7 +86,7 @@ attrs==23.1.0 # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite @@ -96,15 +96,15 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -118,13 +118,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle @@ -134,7 +135,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -144,7 +145,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.19 +docutils==0.20.1 # via # readme-renderer # sphinx @@ -161,9 +162,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -174,14 +175,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -196,6 +197,7 @@ imagesize==1.4.1 importlib-metadata==6.6.0 # via # keyring + # python-arango # twine iniconfig==2.0.0 # via pytest @@ -221,17 +223,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -243,9 +245,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -255,7 +257,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -283,13 +285,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -301,9 +303,9 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.7 +python-arango==7.5.8 # via testcontainers-arangodb python-dateutil==2.8.2 # via @@ -314,21 +316,19 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -340,14 +340,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -359,7 +359,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -379,7 +379,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.0 +sphinx==7.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -393,7 +393,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -413,19 +413,18 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # azure-core # azure-storage-blob # sqlalchemy -tzdata==2023.3 - # via pytz-deprecation-shim -tzlocal==4.3 +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango diff --git a/requirements/ubuntu-latest-3.11.txt b/requirements/ubuntu-latest-3.11.txt index cac9cd549..107b67427 100644 --- a/requirements/ubuntu-latest-3.11.txt +++ b/requirements/ubuntu-latest-3.11.txt @@ -79,14 +79,12 @@ asn1crypto==1.5.1 # via scramp async-generator==1.10 # via trio -async-timeout==4.0.2 - # via redis attrs==23.1.0 # via # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite @@ -96,15 +94,15 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -118,13 +116,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle @@ -134,7 +133,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -144,7 +143,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.19 +docutils==0.20.1 # via # readme-renderer # sphinx @@ -158,9 +157,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -171,14 +170,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -193,6 +192,7 @@ imagesize==1.4.1 importlib-metadata==6.6.0 # via # keyring + # python-arango # twine iniconfig==2.0.0 # via pytest @@ -218,17 +218,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -240,9 +240,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -252,7 +252,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -280,13 +280,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -298,9 +298,9 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.7 +python-arango==7.5.8 # via testcontainers-arangodb python-dateutil==2.8.2 # via @@ -311,21 +311,19 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -337,14 +335,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -356,7 +354,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -376,7 +374,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.0 +sphinx==7.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -390,7 +388,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -406,19 +404,18 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # azure-core # azure-storage-blob # sqlalchemy -tzdata==2023.3 - # via pytz-deprecation-shim -tzlocal==4.3 +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango diff --git a/requirements/ubuntu-latest-3.7.txt b/requirements/ubuntu-latest-3.7.txt index fbe67cf4f..b7197ef2e 100644 --- a/requirements/ubuntu-latest-3.7.txt +++ b/requirements/ubuntu-latest-3.7.txt @@ -86,31 +86,29 @@ attrs==23.1.0 # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx backports-zoneinfo==0.2.1 - # via - # pytz-deprecation-shim - # tzlocal + # via tzlocal bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer cached-property==1.5.2 # via docker-compose -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -124,13 +122,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle @@ -140,7 +139,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -167,9 +166,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -180,14 +179,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -238,17 +237,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -260,9 +259,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -272,7 +271,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -300,13 +299,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -318,7 +317,7 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in python-arango==7.5.6 # via testcontainers-arangodb @@ -331,22 +330,20 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # babel # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -358,14 +355,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -377,7 +374,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -411,7 +408,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -431,7 +428,7 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # async-timeout # azure-core @@ -439,17 +436,17 @@ typing-extensions==4.5.0 # h11 # importlib-metadata # markdown-it-py + # pyjwt # redis # rich # sqlalchemy -tzdata==2023.3 - # via pytz-deprecation-shim -tzlocal==4.3 +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango diff --git a/requirements/ubuntu-latest-3.8.txt b/requirements/ubuntu-latest-3.8.txt index 70a86aa02..58cabaaba 100644 --- a/requirements/ubuntu-latest-3.8.txt +++ b/requirements/ubuntu-latest-3.8.txt @@ -86,29 +86,27 @@ attrs==23.1.0 # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite babel==2.12.1 # via sphinx backports-zoneinfo==0.2.1 - # via - # pytz-deprecation-shim - # tzlocal + # via tzlocal bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -122,13 +120,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle @@ -138,7 +137,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -148,7 +147,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.19 +docutils==0.20.1 # via # readme-renderer # sphinx @@ -165,9 +164,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -178,14 +177,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -200,6 +199,7 @@ imagesize==1.4.1 importlib-metadata==6.6.0 # via # keyring + # python-arango # sphinx # twine importlib-resources==5.12.0 @@ -228,17 +228,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -250,9 +250,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -262,7 +262,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -290,13 +290,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -308,9 +308,9 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.7 +python-arango==7.5.8 # via testcontainers-arangodb python-dateutil==2.8.2 # via @@ -321,22 +321,20 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # babel # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -348,14 +346,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -367,7 +365,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -387,7 +385,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.0 +sphinx==7.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -401,7 +399,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -421,20 +419,19 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # azure-core # azure-storage-blob # rich # sqlalchemy -tzdata==2023.3 - # via pytz-deprecation-shim -tzlocal==4.3 +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango diff --git a/requirements/ubuntu-latest-3.9.txt b/requirements/ubuntu-latest-3.9.txt index a1e4dd7fa..bf3410c49 100644 --- a/requirements/ubuntu-latest-3.9.txt +++ b/requirements/ubuntu-latest-3.9.txt @@ -86,7 +86,7 @@ attrs==23.1.0 # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite @@ -96,15 +96,15 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -118,13 +118,14 @@ charset-normalizer==3.1.0 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle @@ -134,7 +135,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -144,7 +145,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.19 +docutils==0.20.1 # via # readme-renderer # sphinx @@ -161,9 +162,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -174,14 +175,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -196,6 +197,7 @@ imagesize==1.4.1 importlib-metadata==6.6.0 # via # keyring + # python-arango # sphinx # twine iniconfig==2.0.0 @@ -222,17 +224,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -244,9 +246,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -256,7 +258,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -284,13 +286,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -302,9 +304,9 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.7 +python-arango==7.5.8 # via testcontainers-arangodb python-dateutil==2.8.2 # via @@ -315,21 +317,19 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -341,14 +341,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -360,7 +360,7 @@ scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -380,7 +380,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.0 +sphinx==7.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -394,7 +394,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -414,19 +414,18 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # azure-core # azure-storage-blob # sqlalchemy -tzdata==2023.3 - # via pytz-deprecation-shim -tzlocal==4.3 +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango diff --git a/requirements/windows-latest-3.10.txt b/requirements/windows-latest-3.10.txt index c567ecf4c..4c6dd2f8f 100644 --- a/requirements/windows-latest-3.10.txt +++ b/requirements/windows-latest-3.10.txt @@ -86,7 +86,7 @@ attrs==23.1.0 # jsonschema # outcome # trio -azure-core==1.26.4 +azure-core==1.27.0 # via azure-storage-blob azure-storage-blob==12.16.0 # via testcontainers-azurite @@ -96,15 +96,15 @@ bcrypt==4.0.1 # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.129 +boto3==1.26.148 # via testcontainers-localstack -botocore==1.29.129 +botocore==1.29.148 # via # boto3 # s3transfer -cachetools==5.3.0 +cachetools==5.3.1 # via google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # minio # opensearch-py @@ -124,13 +124,14 @@ colorama==0.4.6 # docker-compose # pytest # sphinx -coverage[toml]==7.2.5 +coverage[toml]==7.2.7 # via pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob # paramiko + # pymysql cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 @@ -139,7 +140,7 @@ distro==1.8.0 # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.0 +docker[ssh]==6.1.3 # via # docker-compose # testcontainers-core @@ -149,7 +150,7 @@ dockerpty==0.4.1 # via docker-compose docopt==0.6.2 # via docker-compose -docutils==0.19 +docutils==0.20.1 # via # readme-renderer # sphinx @@ -166,9 +167,9 @@ flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.11.0 # via google-cloud-pubsub -google-auth==2.17.3 +google-auth==2.19.1 # via google-api-core -google-cloud-pubsub==2.16.1 +google-cloud-pubsub==2.17.1 # via testcontainers-gcp googleapis-common-protos[grpc]==1.59.0 # via @@ -179,14 +180,14 @@ greenlet==2.0.2 # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub -grpcio==1.54.0 +grpcio==1.54.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.54.2 # via # google-api-core # google-cloud-pubsub @@ -201,6 +202,7 @@ imagesize==1.4.1 importlib-metadata==6.6.0 # via # keyring + # python-arango # twine iniconfig==2.0.0 # via pytest @@ -222,17 +224,17 @@ keyring==23.13.1 # via twine markdown-it-py==2.2.0 # via rich -markupsafe==2.1.2 +markupsafe==2.1.3 # via jinja2 mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.14 +minio==7.1.15 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.8.0 +neo4j==5.9.0 # via testcontainers-neo4j opensearch-py==2.2.0 # via testcontainers-opensearch @@ -244,9 +246,9 @@ packaging==23.1 # docker # pytest # sphinx -paramiko==3.1.0 +paramiko==3.2.0 # via docker -pg8000==1.29.4 +pg8000==1.29.6 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq @@ -256,7 +258,7 @@ pluggy==1.0.0 # via pytest proto-plus==1.22.2 # via google-cloud-pubsub -protobuf==4.22.4 +protobuf==4.23.2 # via # google-api-core # google-cloud-pubsub @@ -284,13 +286,13 @@ pygments==2.15.1 # readme-renderer # rich # sphinx -pyjwt==2.6.0 +pyjwt==2.7.0 # via python-arango pymongo==4.3.3 # via testcontainers-mongodb pymssql==2.2.7 # via testcontainers-mssql -pymysql==1.0.3 +pymysql[rsa]==1.0.3 # via testcontainers-mysql pynacl==1.5.0 # via paramiko @@ -302,9 +304,9 @@ pytest==7.3.1 # via # -r requirements.in # pytest-cov -pytest-cov==4.0.0 +pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.7 +python-arango==7.5.8 # via testcontainers-arangodb python-dateutil==2.8.2 # via @@ -315,14 +317,12 @@ python-dotenv==0.21.1 # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==2.16.1 +python-keycloak==3.0.0 # via testcontainers-keycloak pytz==2023.3 # via # clickhouse-driver # neo4j -pytz-deprecation-shim==0.1.0.post0 - # via tzlocal pywin32==306 # via docker pywin32-ctypes==0.2.0 @@ -331,9 +331,9 @@ pyyaml==5.4.1 # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.4 +redis==4.5.5 # via testcontainers-redis -requests==2.30.0 +requests==2.31.0 # via # azure-core # docker @@ -345,14 +345,14 @@ requests==2.30.0 # requests-toolbelt # sphinx # twine -requests-toolbelt==0.10.1 +requests-toolbelt==1.0.0 # via # python-arango # python-keycloak # twine rfc3986==2.0.0 # via twine -rich==13.3.5 +rich==13.4.1 # via twine rsa==4.9 # via @@ -362,7 +362,7 @@ s3transfer==0.6.1 # via boto3 scramp==1.4.4 # via pg8000 -selenium==4.9.0 +selenium==4.9.1 # via testcontainers-selenium six==1.16.0 # via @@ -382,7 +382,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.0 +sphinx==7.0.1 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -396,7 +396,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.12 +sqlalchemy==2.0.15 # via # testcontainers-mssql # testcontainers-mysql @@ -416,21 +416,20 @@ trio-websocket==0.10.2 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.5.0 +typing-extensions==4.6.3 # via # azure-core # azure-storage-blob # sqlalchemy tzdata==2023.3 - # via - # pytz-deprecation-shim - # tzlocal -tzlocal==4.3 + # via tzlocal +tzlocal==5.0.1 # via clickhouse-driver -urllib3[socks]==1.26.15 +urllib3[socks]==1.26.16 # via # botocore # docker + # google-auth # minio # opensearch-py # python-arango From 2dbc38e64189fbddaf395f858bfe096f20bfd019 Mon Sep 17 00:00:00 2001 From: Kevin Wittek Date: Wed, 22 Nov 2023 10:57:47 +0100 Subject: [PATCH 266/425] Remove compose from requirements and update dependencies for fixing the build (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also update the Selenium module to workaround the breaking changes in the Selenium dependency. Co-authored-by: Eddú Meléndez Gonzales Co-authored-by: Dave Ankin --- .github/workflows/main.yml | 1 - README.rst | 1 - compose/README.rst | 1 - compose/setup.py | 18 -- compose/testcontainers/compose/__init__.py | 193 ----------------- compose/tests/.env.test | 1 - compose/tests/docker-compose-2.yml | 6 - compose/tests/docker-compose-3.yml | 8 - compose/tests/docker-compose-4.yml | 3 - compose/tests/docker-compose.yml | 17 -- compose/tests/test_docker_compose.py | 125 ----------- requirements.in | 1 - requirements/macos-latest-3.10.txt | 213 ++++++++---------- requirements/ubuntu-latest-3.10.txt | 213 ++++++++---------- requirements/ubuntu-latest-3.11.txt | 211 ++++++++---------- requirements/ubuntu-latest-3.7.txt | 169 ++++++--------- requirements/ubuntu-latest-3.8.txt | 197 +++++++---------- requirements/ubuntu-latest-3.9.txt | 213 ++++++++---------- requirements/windows-latest-3.10.txt | 216 ++++++++----------- selenium/testcontainers/selenium/__init__.py | 6 +- selenium/tests/test_selenium.py | 6 +- 21 files changed, 623 insertions(+), 1196 deletions(-) delete mode 100644 compose/README.rst delete mode 100644 compose/setup.py delete mode 100644 compose/testcontainers/compose/__init__.py delete mode 100644 compose/tests/.env.test delete mode 100644 compose/tests/docker-compose-2.yml delete mode 100644 compose/tests/docker-compose-3.yml delete mode 100644 compose/tests/docker-compose-4.yml delete mode 100644 compose/tests/docker-compose.yml delete mode 100644 compose/tests/test_docker_compose.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84b8ca101..d71bb9d06 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,6 @@ jobs: - arangodb - azurite - clickhouse - - compose - core - elasticsearch - google diff --git a/README.rst b/README.rst index f490e76b6..07dfc0bad 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,6 @@ testcontainers-python facilitates the use of Docker containers for functional an arangodb/README azurite/README clickhouse/README - compose/README elasticsearch/README google/README kafka/README diff --git a/compose/README.rst b/compose/README.rst deleted file mode 100644 index beafee0d5..000000000 --- a/compose/README.rst +++ /dev/null @@ -1 +0,0 @@ -.. autoclass:: testcontainers.compose.DockerCompose diff --git a/compose/setup.py b/compose/setup.py deleted file mode 100644 index bfe128e22..000000000 --- a/compose/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Docker Compose component of testcontainers-python." - -setup( - name="testcontainers-compose", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "docker-compose", - ], - python_requires=">=3.7", -) diff --git a/compose/testcontainers/compose/__init__.py b/compose/testcontainers/compose/__init__.py deleted file mode 100644 index 3a785a3c0..000000000 --- a/compose/testcontainers/compose/__init__.py +++ /dev/null @@ -1,193 +0,0 @@ -import subprocess -from typing import Iterable, List, Optional, Tuple, Union - -import requests - -from testcontainers.core.exceptions import NoSuchPortExposed -from testcontainers.core.waiting_utils import wait_container_is_ready - - -class DockerCompose: - """ - Manage docker compose environments. - - Args: - filepath: Relative directory containing the docker compose configuration file. - compose_file_name: File name of the docker compose configuration file. - pull: Pull images before launching environment. - build: Build images referenced in the configuration file. - env_file: Path to an env file containing environment variables to pass to docker compose. - services: List of services to start. - - Example: - - This example spins up chrome and firefox containers using docker compose. - - .. doctest:: - - >>> from testcontainers.compose import DockerCompose - - >>> compose = DockerCompose("compose/tests", compose_file_name="docker-compose-4.yml", - ... pull=True) - >>> with compose: - ... stdout, stderr = compose.get_logs() - >>> b"Hello from Docker!" in stdout - True - - .. code-block:: yaml - - services: - hello-world: - image: "hello-world" - """ - - def __init__( - self, - filepath: str, - compose_file_name: Union[str, Iterable] = "docker-compose.yml", - pull: bool = False, - build: bool = False, - env_file: Optional[str] = None, - services: Optional[List[str]] = None - ) -> None: - self.filepath = filepath - self.compose_file_names = [compose_file_name] if isinstance(compose_file_name, str) else \ - list(compose_file_name) - self.pull = pull - self.build = build - self.env_file = env_file - self.services = services - - def __enter__(self) -> "DockerCompose": - self.start() - return self - - def __exit__(self, exc_type, exc_val, exc_tb) -> None: - self.stop() - - def docker_compose_command(self) -> List[str]: - """ - Returns command parts used for the docker compose commands - - Returns: - cmd: Docker compose command parts. - """ - docker_compose_cmd = ['docker-compose'] - for file in self.compose_file_names: - docker_compose_cmd += ['-f', file] - if self.env_file: - docker_compose_cmd += ['--env-file', self.env_file] - return docker_compose_cmd - - def start(self) -> None: - """ - Starts the docker compose environment. - """ - if self.pull: - pull_cmd = self.docker_compose_command() + ['pull'] - self._call_command(cmd=pull_cmd) - - up_cmd = self.docker_compose_command() + ['up', '-d'] - if self.build: - up_cmd.append('--build') - if self.services: - up_cmd.extend(self.services) - - self._call_command(cmd=up_cmd) - - def stop(self) -> None: - """ - Stops the docker compose environment. - """ - down_cmd = self.docker_compose_command() + ['down', '-v'] - self._call_command(cmd=down_cmd) - - def get_logs(self) -> Tuple[str, str]: - """ - Returns all log output from stdout and stderr - - Returns: - stdout: Standard output stream. - stderr: Standard error stream. - """ - logs_cmd = self.docker_compose_command() + ["logs"] - result = subprocess.run( - logs_cmd, - cwd=self.filepath, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - return result.stdout, result.stderr - - def exec_in_container(self, service_name: str, command: List[str]) -> Tuple[str, str]: - """ - Executes a command in the container of one of the services. - - Args: - service_name: Name of the docker compose service to run the command in. - command: Command to execute. - - Returns: - stdout: Standard output stream. - stderr: Standard error stream. - """ - exec_cmd = self.docker_compose_command() + ['exec', '-T', service_name] + command - result = subprocess.run( - exec_cmd, - cwd=self.filepath, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - return result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode - - def get_service_port(self, service_name: str, port: int) -> int: - """ - Returns the mapped port for one of the services. - - Args: - service_name: Name of the docker compose service. - port: Internal port to get the mapping for. - - Returns: - mapped_port: Mapped port on the host. - """ - return self._get_service_info(service_name, port)[1] - - def get_service_host(self, service_name: str, port: int) -> str: - """ - Returns the host for one of the services. - - Args: - service_name: Name of the docker compose service. - port: Internal port to get the mapping for. - - Returns: - host: Hostname for the service. - """ - return self._get_service_info(service_name, port)[0] - - def _get_service_info(self, service: str, port: int) -> List[str]: - port_cmd = self.docker_compose_command() + ["port", service, str(port)] - output = subprocess.check_output(port_cmd, cwd=self.filepath).decode("utf-8") - result = str(output).rstrip().split(":") - if len(result) != 2 or not all(result): - raise NoSuchPortExposed(f"port {port} is not exposed for service {service}") - return result - - def _call_command(self, cmd: Union[str, List[str]], filepath: Optional[str] = None) -> None: - if filepath is None: - filepath = self.filepath - subprocess.call(cmd, cwd=filepath) - - @wait_container_is_ready(requests.exceptions.ConnectionError) - def wait_for(self, url: str) -> 'DockerCompose': - """ - Waits for a response from a given URL. This is typically used to block until a service in - the environment has started and is responding. Note that it does not assert any sort of - return code, only check that the connection was successful. - - Args: - url: URL from one of the services in the environment to use to wait on. - """ - requests.get(url) - return self diff --git a/compose/tests/.env.test b/compose/tests/.env.test deleted file mode 100644 index 84b2baafd..000000000 --- a/compose/tests/.env.test +++ /dev/null @@ -1 +0,0 @@ -TAG_TEST_ASSERT_KEY="test_has_passed" diff --git a/compose/tests/docker-compose-2.yml b/compose/tests/docker-compose-2.yml deleted file mode 100644 index e360bb010..000000000 --- a/compose/tests/docker-compose-2.yml +++ /dev/null @@ -1,6 +0,0 @@ -services: - alpine: - image: alpine - command: sleep 3600 - ports: - - "3306:3306" diff --git a/compose/tests/docker-compose-3.yml b/compose/tests/docker-compose-3.yml deleted file mode 100644 index 874126c7a..000000000 --- a/compose/tests/docker-compose-3.yml +++ /dev/null @@ -1,8 +0,0 @@ -services: - alpine: - image: alpine - command: sleep 3600 - ports: - - "3306:3306" - environment: - TEST_ASSERT_KEY: ${TAG_TEST_ASSERT_KEY} diff --git a/compose/tests/docker-compose-4.yml b/compose/tests/docker-compose-4.yml deleted file mode 100644 index 081e598d1..000000000 --- a/compose/tests/docker-compose-4.yml +++ /dev/null @@ -1,3 +0,0 @@ -services: - hello-world: - image: "hello-world" diff --git a/compose/tests/docker-compose.yml b/compose/tests/docker-compose.yml deleted file mode 100644 index 6c12ae335..000000000 --- a/compose/tests/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -services: - hub: - image: selenium/hub - ports: - - "4444:4444" - firefox: - image: selenium/node-firefox - links: - - hub - expose: - - "5555" - chrome: - image: selenium/node-chrome - links: - - hub - expose: - - "5555" diff --git a/compose/tests/test_docker_compose.py b/compose/tests/test_docker_compose.py deleted file mode 100644 index da611f1c8..000000000 --- a/compose/tests/test_docker_compose.py +++ /dev/null @@ -1,125 +0,0 @@ -import os -from unittest.mock import patch - -import pytest - -from testcontainers.compose import DockerCompose -from testcontainers.core.docker_client import DockerClient -from testcontainers.core.exceptions import NoSuchPortExposed -from testcontainers.core.waiting_utils import wait_for_logs - -ROOT = os.path.dirname(__file__) - - -def test_can_spawn_service_via_compose(): - with DockerCompose(ROOT) as compose: - host = compose.get_service_host("hub", 4444) - port = compose.get_service_port("hub", 4444) - assert host == "0.0.0.0" - assert port == "4444" - - -def test_can_pull_images_before_spawning_service_via_compose(): - with DockerCompose(ROOT, pull=True) as compose: - host = compose.get_service_host("hub", 4444) - port = compose.get_service_port("hub", 4444) - assert host == "0.0.0.0" - assert port == "4444" - - -def test_can_build_images_before_spawning_service_via_compose(): - with patch.object(DockerCompose, "_call_command") as call_mock: - with DockerCompose(ROOT, build=True) as compose: - ... - - assert compose.build - docker_compose_cmd = call_mock.call_args_list[0][1]["cmd"] - assert "docker-compose" in docker_compose_cmd - assert "up" in docker_compose_cmd - assert "--build" in docker_compose_cmd - - -def test_can_specify_services(): - with patch.object(DockerCompose, "_call_command") as call_mock: - with DockerCompose(ROOT, services=["hub", "firefox"]) as compose: - ... - - assert compose.services - docker_compose_cmd = call_mock.call_args_list[0][1]["cmd"] - services_at_the_end = docker_compose_cmd[-2:] - assert "firefox" in services_at_the_end - assert "hub" in services_at_the_end - assert "chrome" not in docker_compose_cmd - - -@pytest.mark.parametrize("should_run_hub", [ - [True], - [False], -]) -def test_can_run_specific_services(should_run_hub: bool): - # compose V2 will improve this test by being able to assert that "firefox" also started/exited - services = ["firefox"] - if should_run_hub: - services.append("hub") - - with DockerCompose(ROOT, services=services) as compose: - if should_run_hub: - assert compose.get_service_host("hub", 4444) - assert compose.get_service_port("hub", 4444) - else: - with pytest.raises(NoSuchPortExposed): - assert compose.get_service_host("hub", 4444) - - -def test_can_throw_exception_if_no_port_exposed(): - with DockerCompose(ROOT) as compose: - with pytest.raises(NoSuchPortExposed): - compose.get_service_host("hub", 5555) - - -def test_compose_wait_for_container_ready(): - with DockerCompose(ROOT) as compose: - docker = DockerClient() - compose.wait_for("http://%s:4444/wd/hub" % docker.host()) - - -def test_compose_can_wait_for_logs(): - with DockerCompose(filepath=ROOT, compose_file_name="docker-compose-4.yml") as compose: - wait_for_logs(compose, "Hello from Docker!") - - -def test_can_parse_multiple_compose_files(): - with DockerCompose(filepath=ROOT, - compose_file_name=["docker-compose.yml", "docker-compose-2.yml"]) as compose: - host = compose.get_service_host("alpine", 3306) - port = compose.get_service_port("alpine", 3306) - assert host == "0.0.0.0" - assert port == "3306" - - host = compose.get_service_host("hub", 4444) - port = compose.get_service_port("hub", 4444) - assert host == "0.0.0.0" - assert port == "4444" - - -def test_can_get_logs(): - with DockerCompose(ROOT) as compose: - docker = DockerClient() - compose.wait_for("http://%s:4444/wd/hub" % docker.host()) - stdout, stderr = compose.get_logs() - assert stdout, 'There should be something on stdout' - - -def test_can_pass_env_params_by_env_file(): - with DockerCompose(ROOT, compose_file_name='docker-compose-3.yml', - env_file='.env.test') as compose: - stdout, *_ = compose.exec_in_container("alpine", ["printenv"]) - assert stdout.splitlines()[0], 'test_has_passed' - - -def test_can_exec_commands(): - with DockerCompose(ROOT) as compose: - result = compose.exec_in_container('hub', ['echo', 'my_test']) - assert result[0] == 'my_test\n', "The echo should be successful" - assert result[1] == '', "stderr should be empty" - assert result[2] == 0, 'The exit code should be successful' diff --git a/requirements.in b/requirements.in index e9e122610..d5e7fb603 100644 --- a/requirements.in +++ b/requirements.in @@ -2,7 +2,6 @@ -e file:azurite -e file:clickhouse -e file:core --e file:compose -e file:elasticsearch -e file:google -e file:kafka diff --git a/requirements/macos-latest-3.10.txt b/requirements/macos-latest-3.10.txt index bffd3237d..b440bf30a 100644 --- a/requirements/macos-latest-3.10.txt +++ b/requirements/macos-latest-3.10.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements.txt --resolver=backtracking +# pip-compile --output-file=requirements.txt # -e file:meta # via -r requirements.in @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,75 +72,63 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio -async-timeout==4.0.2 +async-timeout==4.0.3 # via redis attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx -bcrypt==4.0.1 - # via paramiko -bleach==6.0.0 - # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py # requests # selenium -cffi==1.15.1 +cffi==1.16.0 # via + # argon2-cffi-bindings # cryptography - # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.7 - # via pytest-cov +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose -dnspython==2.3.0 +dnspython==2.4.2 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.20.1 # via # readme-renderer @@ -152,36 +137,38 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 +exceptiongroup==1.1.3 # via # pytest # trio # trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -193,7 +180,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 # via # keyring # python-arango @@ -202,7 +189,7 @@ iniconfig==2.0.0 # via pytest isodate==0.6.1 # via azure-storage-blob -jaraco-classes==3.2.3 +jaraco-classes==3.3.0 # via keyring jinja2==3.1.2 # via sphinx @@ -210,13 +197,11 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.3.0 # via twine -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 # via jinja2 @@ -224,35 +209,36 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio -more-itertools==9.1.0 +more-itertools==10.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +nh3==0.2.14 + # via readme-renderer +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest + # python-arango # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.30.3 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.25.1 # via # google-api-core # google-cloud-pubsub @@ -260,7 +246,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -273,61 +259,54 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.8 +python-arango==7.8.1 # via testcontainers-arangodb python-dateutil==2.8.2 # via # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.7.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # clickhouse-driver # neo4j -pyyaml==5.4.1 - # via docker-compose -readme-renderer==37.3 +readme-renderer==42.0 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -342,82 +321,80 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 -selenium==4.9.1 +selenium==4.15.2 # via testcontainers-selenium six==1.16.0 # via # azure-core - # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.1 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 +sphinx==7.2.6 + # via + # -r requirements.in + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.22.0 +trio==0.23.1 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.8.0 # via # azure-core # azure-storage-blob # sqlalchemy -tzlocal==5.0.1 +tzlocal==5.2 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -425,19 +402,15 @@ urllib3[socks]==1.26.16 # selenium # testcontainers-core # twine -webencodings==0.5.1 - # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.4 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.15.0 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/ubuntu-latest-3.10.txt b/requirements/ubuntu-latest-3.10.txt index 5a06928f1..8bea3419e 100644 --- a/requirements/ubuntu-latest-3.10.txt +++ b/requirements/ubuntu-latest-3.10.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements.txt --resolver=backtracking +# pip-compile --output-file=requirements.txt # -e file:meta # via -r requirements.in @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,76 +72,64 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio -async-timeout==4.0.2 +async-timeout==4.0.3 # via redis attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx -bcrypt==4.0.1 - # via paramiko -bleach==6.0.0 - # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py # requests # selenium -cffi==1.15.1 +cffi==1.16.0 # via + # argon2-cffi-bindings # cryptography - # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.7 - # via pytest-cov +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose -dnspython==2.3.0 +dnspython==2.4.2 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.20.1 # via # readme-renderer @@ -153,36 +138,38 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 +exceptiongroup==1.1.3 # via # pytest # trio # trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -194,7 +181,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 # via # keyring # python-arango @@ -203,7 +190,7 @@ iniconfig==2.0.0 # via pytest isodate==0.6.1 # via azure-storage-blob -jaraco-classes==3.2.3 +jaraco-classes==3.3.0 # via keyring jeepney==0.8.0 # via @@ -215,13 +202,11 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.3.0 # via twine -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 # via jinja2 @@ -229,35 +214,36 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio -more-itertools==9.1.0 +more-itertools==10.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +nh3==0.2.14 + # via readme-renderer +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest + # python-arango # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.30.3 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.25.1 # via # google-api-core # google-cloud-pubsub @@ -265,7 +251,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -278,61 +264,54 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.8 +python-arango==7.8.1 # via testcontainers-arangodb python-dateutil==2.8.2 # via # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.7.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # clickhouse-driver # neo4j -pyyaml==5.4.1 - # via docker-compose -readme-renderer==37.3 +readme-renderer==42.0 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -347,84 +326,82 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.1 +selenium==4.15.2 # via testcontainers-selenium six==1.16.0 # via # azure-core - # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.1 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 +sphinx==7.2.6 + # via + # -r requirements.in + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.22.0 +trio==0.23.1 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.8.0 # via # azure-core # azure-storage-blob # sqlalchemy -tzlocal==5.0.1 +tzlocal==5.2 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -432,19 +409,15 @@ urllib3[socks]==1.26.16 # selenium # testcontainers-core # twine -webencodings==0.5.1 - # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.4 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.15.0 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/ubuntu-latest-3.11.txt b/requirements/ubuntu-latest-3.11.txt index 107b67427..a3f26f09f 100644 --- a/requirements/ubuntu-latest-3.11.txt +++ b/requirements/ubuntu-latest-3.11.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --output-file=requirements.txt --resolver=backtracking +# pip-compile --output-file=requirements.txt # -e file:meta # via -r requirements.in @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,74 +72,62 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx -bcrypt==4.0.1 - # via paramiko -bleach==6.0.0 - # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py # requests # selenium -cffi==1.15.1 +cffi==1.16.0 # via + # argon2-cffi-bindings # cryptography - # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.7 - # via pytest-cov +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose -dnspython==2.3.0 +dnspython==2.4.2 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.20.1 # via # readme-renderer @@ -151,33 +136,33 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 - # via trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -189,7 +174,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 # via # keyring # python-arango @@ -198,7 +183,7 @@ iniconfig==2.0.0 # via pytest isodate==0.6.1 # via azure-storage-blob -jaraco-classes==3.2.3 +jaraco-classes==3.3.0 # via keyring jeepney==0.8.0 # via @@ -210,13 +195,11 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.3.0 # via twine -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 # via jinja2 @@ -224,35 +207,36 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio -more-itertools==9.1.0 +more-itertools==10.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +nh3==0.2.14 + # via readme-renderer +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest + # python-arango # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.30.3 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.25.1 # via # google-api-core # google-cloud-pubsub @@ -260,7 +244,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -273,61 +257,54 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.8 +python-arango==7.8.1 # via testcontainers-arangodb python-dateutil==2.8.2 # via # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.7.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # clickhouse-driver # neo4j -pyyaml==5.4.1 - # via docker-compose -readme-renderer==37.3 +readme-renderer==42.0 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -342,80 +319,78 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.1 +selenium==4.15.2 # via testcontainers-selenium six==1.16.0 # via # azure-core - # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.1 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 +sphinx==7.2.6 + # via + # -r requirements.in + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose -trio==0.22.0 +trio==0.23.1 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.8.0 # via # azure-core # azure-storage-blob # sqlalchemy -tzlocal==5.0.1 +tzlocal==5.2 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -423,19 +398,15 @@ urllib3[socks]==1.26.16 # selenium # testcontainers-core # twine -webencodings==0.5.1 - # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.4 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.15.0 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/ubuntu-latest-3.7.txt b/requirements/ubuntu-latest-3.7.txt index b7197ef2e..2d6bc2c59 100644 --- a/requirements/ubuntu-latest-3.7.txt +++ b/requirements/ubuntu-latest-3.7.txt @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,40 +72,37 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio -async-timeout==4.0.2 +async-timeout==4.0.3 # via redis attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx backports-zoneinfo==0.2.1 # via tzlocal -bcrypt==4.0.1 - # via paramiko bleach==6.0.0 # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cached-property==1.5.2 - # via docker-compose -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py @@ -116,39 +110,30 @@ certifi==2023.5.7 # selenium cffi==1.15.1 # via + # argon2-cffi-bindings # cryptography - # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse coverage[toml]==7.2.7 - # via pytest-cov + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose dnspython==2.3.0 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.19 # via # readme-renderer @@ -157,36 +142,38 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 +exceptiongroup==1.1.3 # via # pytest # trio # trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -198,10 +185,9 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.7.0 # via # attrs - # jsonschema # keyring # pg8000 # pluggy @@ -229,11 +215,9 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.1.1 # via twine markdown-it-py==2.2.0 # via rich @@ -243,35 +227,33 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.29.8 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.2.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.24.4 # via # google-api-core # google-cloud-pubsub @@ -279,7 +261,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -292,28 +274,26 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov @@ -326,28 +306,23 @@ python-dateutil==2.8.2 # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.6.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # babel # clickhouse-driver # neo4j -pyyaml==5.4.1 - # via docker-compose readme-renderer==37.3 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -362,32 +337,28 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.1 +selenium==4.11.2 # via testcontainers-selenium six==1.16.0 # via # azure-core # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 @@ -408,28 +379,27 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.22.0 +trio==0.22.2 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.7.1 # via + # argon2-cffi # async-timeout # azure-core # azure-storage-blob @@ -440,13 +410,12 @@ typing-extensions==4.6.3 # redis # rich # sqlalchemy -tzlocal==5.0.1 +tzlocal==5.1 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -456,13 +425,11 @@ urllib3[socks]==1.26.16 # twine webencodings==0.5.1 # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.1 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket diff --git a/requirements/ubuntu-latest-3.8.txt b/requirements/ubuntu-latest-3.8.txt index 58cabaaba..dfe429866 100644 --- a/requirements/ubuntu-latest-3.8.txt +++ b/requirements/ubuntu-latest-3.8.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --output-file=requirements.txt --resolver=backtracking +# pip-compile --output-file=requirements.txt # -e file:meta # via -r requirements.in @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,78 +72,66 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio -async-timeout==4.0.2 +async-timeout==4.0.3 # via redis attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx backports-zoneinfo==0.2.1 # via tzlocal -bcrypt==4.0.1 - # via paramiko -bleach==6.0.0 - # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py # requests # selenium -cffi==1.15.1 +cffi==1.16.0 # via + # argon2-cffi-bindings # cryptography - # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.7 - # via pytest-cov +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose -dnspython==2.3.0 +dnspython==2.4.2 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.20.1 # via # readme-renderer @@ -155,36 +140,38 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 +exceptiongroup==1.1.3 # via # pytest # trio # trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -196,19 +183,19 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 # via # keyring # python-arango # sphinx # twine -importlib-resources==5.12.0 +importlib-resources==6.1.1 # via keyring iniconfig==2.0.0 # via pytest isodate==0.6.1 # via azure-storage-blob -jaraco-classes==3.2.3 +jaraco-classes==3.3.0 # via keyring jeepney==0.8.0 # via @@ -220,13 +207,11 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.3.0 # via twine -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 # via jinja2 @@ -234,35 +219,36 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio -more-itertools==9.1.0 +more-itertools==10.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +nh3==0.2.14 + # via readme-renderer +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest + # python-arango # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.30.3 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.25.1 # via # google-api-core # google-cloud-pubsub @@ -270,7 +256,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -283,62 +269,55 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.8 +python-arango==7.8.1 # via testcontainers-arangodb python-dateutil==2.8.2 # via # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.7.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # babel # clickhouse-driver # neo4j -pyyaml==5.4.1 - # via docker-compose -readme-renderer==37.3 +readme-renderer==42.0 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -353,39 +332,34 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.1 +selenium==4.15.2 # via testcontainers-selenium six==1.16.0 # via # azure-core - # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.1 +sphinx==7.1.2 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -399,39 +373,36 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.22.0 +trio==0.23.1 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.8.0 # via # azure-core # azure-storage-blob # rich # sqlalchemy -tzlocal==5.0.1 +tzlocal==5.2 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -439,19 +410,15 @@ urllib3[socks]==1.26.16 # selenium # testcontainers-core # twine -webencodings==0.5.1 - # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.4 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.15.0 +zipp==3.17.0 # via # importlib-metadata # importlib-resources diff --git a/requirements/ubuntu-latest-3.9.txt b/requirements/ubuntu-latest-3.9.txt index bf3410c49..3659fe21b 100644 --- a/requirements/ubuntu-latest-3.9.txt +++ b/requirements/ubuntu-latest-3.9.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --output-file=requirements.txt --resolver=backtracking +# pip-compile --output-file=requirements.txt # -e file:meta # via -r requirements.in @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,76 +72,64 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio -async-timeout==4.0.2 +async-timeout==4.0.3 # via redis attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx -bcrypt==4.0.1 - # via paramiko -bleach==6.0.0 - # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py # requests # selenium -cffi==1.15.1 +cffi==1.16.0 # via + # argon2-cffi-bindings # cryptography - # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse -coverage[toml]==7.2.7 - # via pytest-cov +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql # secretstorage cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose -dnspython==2.3.0 +dnspython==2.4.2 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.20.1 # via # readme-renderer @@ -153,36 +138,38 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 +exceptiongroup==1.1.3 # via # pytest # trio # trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -194,7 +181,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 # via # keyring # python-arango @@ -204,7 +191,7 @@ iniconfig==2.0.0 # via pytest isodate==0.6.1 # via azure-storage-blob -jaraco-classes==3.2.3 +jaraco-classes==3.3.0 # via keyring jeepney==0.8.0 # via @@ -216,13 +203,11 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.3.0 # via twine -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 # via jinja2 @@ -230,35 +215,36 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio -more-itertools==9.1.0 +more-itertools==10.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +nh3==0.2.14 + # via readme-renderer +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest + # python-arango # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.30.3 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.25.1 # via # google-api-core # google-cloud-pubsub @@ -266,7 +252,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -279,61 +265,54 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.8 +python-arango==7.8.1 # via testcontainers-arangodb python-dateutil==2.8.2 # via # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.7.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # clickhouse-driver # neo4j -pyyaml==5.4.1 - # via docker-compose -readme-renderer==37.3 +readme-renderer==42.0 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -348,84 +327,82 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 secretstorage==3.3.3 # via keyring -selenium==4.9.1 +selenium==4.15.2 # via testcontainers-selenium six==1.16.0 # via # azure-core - # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.1 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 +sphinx==7.2.6 + # via + # -r requirements.in + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.22.0 +trio==0.23.1 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.8.0 # via # azure-core # azure-storage-blob # sqlalchemy -tzlocal==5.0.1 +tzlocal==5.2 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -433,19 +410,15 @@ urllib3[socks]==1.26.16 # selenium # testcontainers-core # twine -webencodings==0.5.1 - # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.4 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.15.0 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/windows-latest-3.10.txt b/requirements/windows-latest-3.10.txt index 4c6dd2f8f..7cdf46d6f 100644 --- a/requirements/windows-latest-3.10.txt +++ b/requirements/windows-latest-3.10.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements.txt --resolver=backtracking +# pip-compile --output-file=requirements.txt # -e file:meta # via -r requirements.in @@ -12,8 +12,6 @@ # via -r requirements.in -e file:clickhouse # via -r requirements.in --e file:compose - # via -r requirements.in -e file:core # via # -r requirements.in @@ -21,7 +19,6 @@ # testcontainers-arangodb # testcontainers-azurite # testcontainers-clickhouse - # testcontainers-compose # testcontainers-elasticsearch # testcontainers-gcp # testcontainers-kafka @@ -75,81 +72,68 @@ # via -r requirements.in alabaster==0.7.13 # via sphinx +argon2-cffi==23.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asn1crypto==1.5.1 # via scramp -async-generator==1.10 - # via trio -async-timeout==4.0.2 +async-timeout==4.0.3 # via redis attrs==23.1.0 # via - # jsonschema # outcome # trio -azure-core==1.27.0 +azure-core==1.29.5 # via azure-storage-blob -azure-storage-blob==12.16.0 +azure-storage-blob==12.19.0 # via testcontainers-azurite -babel==2.12.1 +babel==2.13.1 # via sphinx -bcrypt==4.0.1 - # via paramiko -bleach==6.0.0 - # via readme-renderer -boto3==1.26.148 +boto3==1.29.1 # via testcontainers-localstack -botocore==1.29.148 +botocore==1.32.1 # via # boto3 # s3transfer -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth -certifi==2023.5.7 +certifi==2023.7.22 # via # minio # opensearch-py # requests # selenium -cffi==1.15.1 +cffi==1.16.0 # via + # argon2-cffi-bindings # cryptography - # pynacl # trio -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests clickhouse-driver==0.2.6 # via testcontainers-clickhouse colorama==0.4.6 # via - # docker-compose # pytest # sphinx -coverage[toml]==7.2.7 - # via pytest-cov +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov cryptography==36.0.2 # via # -r requirements.in # azure-storage-blob - # paramiko # pymysql cx-oracle==8.3.0 # via testcontainers-oracle deprecation==2.1.0 # via python-keycloak -distro==1.8.0 - # via docker-compose -dnspython==2.3.0 +dnspython==2.4.2 # via pymongo -docker[ssh]==6.1.3 - # via - # docker-compose - # testcontainers-core -docker-compose==1.29.2 - # via testcontainers-compose -dockerpty==0.4.1 - # via docker-compose -docopt==0.6.2 - # via docker-compose +docker==6.1.3 + # via testcontainers-core docutils==0.20.1 # via # readme-renderer @@ -158,36 +142,38 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.1 +exceptiongroup==1.1.3 # via # pytest # trio # trio-websocket flake8==3.7.9 # via -r requirements.in -google-api-core[grpc]==2.11.0 - # via google-cloud-pubsub -google-auth==2.19.1 +google-api-core[grpc]==2.14.0 + # via + # google-api-core + # google-cloud-pubsub +google-auth==2.23.4 # via google-api-core -google-cloud-pubsub==2.17.1 +google-cloud-pubsub==2.18.4 # via testcontainers-gcp -googleapis-common-protos[grpc]==1.59.0 +googleapis-common-protos[grpc]==1.61.0 # via # google-api-core # grpc-google-iam-v1 # grpcio-status -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.54.2 +grpcio==1.59.2 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.54.2 +grpcio-status==1.59.2 # via # google-api-core # google-cloud-pubsub @@ -199,7 +185,7 @@ idna==3.4 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 # via # keyring # python-arango @@ -208,7 +194,7 @@ iniconfig==2.0.0 # via pytest isodate==0.6.1 # via azure-storage-blob -jaraco-classes==3.2.3 +jaraco-classes==3.3.0 # via keyring jinja2==3.1.2 # via sphinx @@ -216,13 +202,11 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==3.2.0 - # via docker-compose kafka-python==2.0.2 # via testcontainers-kafka -keyring==23.13.1 +keyring==24.3.0 # via twine -markdown-it-py==2.2.0 +markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 # via jinja2 @@ -230,35 +214,36 @@ mccabe==0.6.1 # via flake8 mdurl==0.1.2 # via markdown-it-py -minio==7.1.15 +minio==7.2.0 # via testcontainers-minio -more-itertools==9.1.0 +more-itertools==10.1.0 # via jaraco-classes -neo4j==5.9.0 +neo4j==5.14.1 # via testcontainers-neo4j -opensearch-py==2.2.0 +nh3==0.2.14 + # via readme-renderer +opensearch-py==2.4.1 # via testcontainers-opensearch -outcome==1.2.0 +outcome==1.3.0.post0 # via trio -packaging==23.1 +packaging==23.2 # via # deprecation # docker # pytest + # python-arango # sphinx -paramiko==3.2.0 - # via docker -pg8000==1.29.6 +pg8000==1.30.3 # via -r requirements.in pika==1.3.2 # via testcontainers-rabbitmq pkginfo==1.9.6 # via twine -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -proto-plus==1.22.2 +proto-plus==1.22.3 # via google-cloud-pubsub -protobuf==4.23.2 +protobuf==4.25.1 # via # google-api-core # google-cloud-pubsub @@ -266,7 +251,7 @@ protobuf==4.23.2 # grpc-google-iam-v1 # grpcio-status # proto-plus -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.9 # via testcontainers-postgres pyasn1==0.5.0 # via @@ -279,65 +264,58 @@ pycodestyle==2.5.0 # via flake8 pycparser==2.21 # via cffi +pycryptodome==3.19.0 + # via minio pyflakes==2.1.1 # via flake8 -pygments==2.15.1 +pygments==2.16.1 # via # readme-renderer # rich # sphinx -pyjwt==2.7.0 +pyjwt==2.8.0 # via python-arango -pymongo==4.3.3 +pymongo==4.6.0 # via testcontainers-mongodb -pymssql==2.2.7 +pymssql==2.2.10 # via testcontainers-mssql -pymysql[rsa]==1.0.3 +pymysql[rsa]==1.1.0 # via testcontainers-mysql -pynacl==1.5.0 - # via paramiko -pyrsistent==0.19.3 - # via jsonschema pysocks==1.7.1 # via urllib3 -pytest==7.3.1 +pytest==7.4.3 # via # -r requirements.in # pytest-cov pytest-cov==4.1.0 # via -r requirements.in -python-arango==7.5.8 +python-arango==7.8.1 # via testcontainers-arangodb python-dateutil==2.8.2 # via # botocore # opensearch-py # pg8000 -python-dotenv==0.21.1 - # via docker-compose python-jose==3.3.0 # via python-keycloak -python-keycloak==3.0.0 +python-keycloak==3.7.0 # via testcontainers-keycloak -pytz==2023.3 +pytz==2023.3.post1 # via # clickhouse-driver # neo4j pywin32==306 # via docker -pywin32-ctypes==0.2.0 +pywin32-ctypes==0.2.2 # via keyring -pyyaml==5.4.1 - # via docker-compose -readme-renderer==37.3 +readme-renderer==42.0 # via twine -redis==4.5.5 +redis==5.0.1 # via testcontainers-redis requests==2.31.0 # via # azure-core # docker - # docker-compose # google-api-core # opensearch-py # python-arango @@ -352,84 +330,82 @@ requests-toolbelt==1.0.0 # twine rfc3986==2.0.0 # via twine -rich==13.4.1 +rich==13.7.0 # via twine rsa==4.9 # via # google-auth # python-jose -s3transfer==0.6.1 +s3transfer==0.7.0 # via boto3 scramp==1.4.4 # via pg8000 -selenium==4.9.1 +selenium==4.15.2 # via testcontainers-selenium six==1.16.0 # via # azure-core - # bleach - # dockerpty # ecdsa - # google-auth # isodate - # jsonschema # opensearch-py # python-dateutil - # websocket-client sniffio==1.3.0 # via trio snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.0.1 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 +sphinx==7.2.6 + # via + # -r requirements.in + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy==2.0.15 +sqlalchemy==2.0.23 # via # testcontainers-mssql # testcontainers-mysql # testcontainers-oracle # testcontainers-postgres -texttable==1.6.7 - # via docker-compose tomli==2.0.1 # via # coverage # pytest -trio==0.22.0 +trio==0.23.1 # via # selenium # trio-websocket -trio-websocket==0.10.2 +trio-websocket==0.11.1 # via selenium twine==4.0.2 # via -r requirements.in -typing-extensions==4.6.3 +typing-extensions==4.8.0 # via # azure-core # azure-storage-blob # sqlalchemy tzdata==2023.3 # via tzlocal -tzlocal==5.0.1 +tzlocal==5.2 # via clickhouse-driver -urllib3[socks]==1.26.16 +urllib3[socks]==1.26.18 # via # botocore # docker - # google-auth # minio # opensearch-py # python-arango @@ -437,19 +413,15 @@ urllib3[socks]==1.26.16 # selenium # testcontainers-core # twine -webencodings==0.5.1 - # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose -wheel==0.40.0 +websocket-client==1.6.4 + # via docker +wheel==0.41.3 # via -r requirements.in -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers-core wsproto==1.2.0 # via trio-websocket -zipp==3.15.0 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/selenium/testcontainers/selenium/__init__.py b/selenium/testcontainers/selenium/__init__.py index 6b2070787..29caf296b 100644 --- a/selenium/testcontainers/selenium/__init__.py +++ b/selenium/testcontainers/selenium/__init__.py @@ -12,6 +12,7 @@ # under the License. from selenium import webdriver +from selenium.webdriver.common.options import ArgOptions from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready from typing import Optional @@ -60,9 +61,12 @@ def _configure(self) -> None: @wait_container_is_ready(urllib3.exceptions.HTTPError) def _connect(self) -> webdriver.Remote: + options = ArgOptions() + for key, value in self.capabilities.items(): + options.set_capability(key, value) return webdriver.Remote( command_executor=(self.get_connection_url()), - desired_capabilities=self.capabilities) + options=options) def get_driver(self) -> webdriver.Remote: return self._connect() diff --git a/selenium/tests/test_selenium.py b/selenium/tests/test_selenium.py index 0b25cd5ec..94cbaac35 100644 --- a/selenium/tests/test_selenium.py +++ b/selenium/tests/test_selenium.py @@ -1,5 +1,6 @@ import pytest from selenium.webdriver import DesiredCapabilities +from selenium.webdriver.common.by import By from testcontainers.selenium import BrowserWebDriverContainer from testcontainers.core.utils import is_arm @@ -11,8 +12,9 @@ def test_webdriver_container_container(caps): with BrowserWebDriverContainer(caps).maybe_emulate_amd64() as chrome: webdriver = chrome.get_driver() - webdriver.get("http://google.com") - webdriver.find_element("name", "q").send_keys("Hello") + webdriver.get("http://example.com") + header = webdriver.find_element(By.TAG_NAME, "h1").text + assert header == "Example Domain" def test_selenium_custom_image(): From b74d5b3def8c1f964590b450258eef9c5a631722 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 22 Nov 2023 08:02:21 -0500 Subject: [PATCH 267/425] Add wait strategy behavior to `NginxContainer` #395 --- nginx/testcontainers/nginx/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/nginx/testcontainers/nginx/__init__.py b/nginx/testcontainers/nginx/__init__.py index 0226d700b..d0680f19a 100644 --- a/nginx/testcontainers/nginx/__init__.py +++ b/nginx/testcontainers/nginx/__init__.py @@ -10,8 +10,13 @@ # 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 urllib.error +import urllib.parse +import urllib.request + from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.waiting_utils import wait_container_is_ready class NginxContainer(DockerContainer): @@ -20,3 +25,17 @@ def __init__(self, image: str = "nginx:latest", port: int = 80, **kwargs) -> Non super(NginxContainer, self).__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) + + def start(self) -> 'NginxContainer': + super().start() + + host = self.get_container_host_ip() + port = str(self.get_exposed_port(self.port)) + self._connect(host, port) + + return self + + @wait_container_is_ready(urllib.error.URLError) + def _connect(self, host: str, port: str) -> None: + url = urllib.parse.urlunsplit(('http', f'{host}:{port}', '', '', '')) + urllib.request.urlopen(url, timeout=1) From 6668ca4fbe9ff444db6bf8ce80c6d5fc55f42865 Mon Sep 17 00:00:00 2001 From: Kevin Wittek Date: Tue, 28 Nov 2023 10:58:40 +0100 Subject: [PATCH 268/425] Add support for Codespaces (#396) --- .devcontainer/devcontainer.json | 35 +++++++++++++++++++++++++++++++++ .gitignore | 1 - README.rst | 2 ++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..69edb9363 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bookworm", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "latest", + "dockerDashComposeVersion": "v2" + } + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pip install --user -r requirements/ubuntu-latest-3.11.txt", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python" + ] + } + } + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.gitignore b/.gitignore index 3da297de6..18837562c 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,6 @@ venv .testrepository/ # vscode: -.devcontainer/ .vscode/ .DS_Store diff --git a/README.rst b/README.rst index 07dfc0bad..b688393cf 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,8 @@ testcontainers-python :target: https://pypi.python.org/pypi/testcontainers .. image:: https://readthedocs.org/projects/testcontainers-python/badge/?version=latest :target: http://testcontainers-python.readthedocs.io/en/latest/?badge=latest +.. image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/testcontainers/testcontainers-python testcontainers-python facilitates the use of Docker containers for functional and integration testing. The collection of packages currently supports the following features. From 6ee98a32345677bcd6ffb95d8674485d4bf15831 Mon Sep 17 00:00:00 2001 From: Ashay Thorat Date: Wed, 29 Nov 2023 01:19:40 +0530 Subject: [PATCH 269/425] Adds support to run lightweight kubernetes testcontainer using k3s (#313) * Adds support to run lightweight kubernetes testcontainer using k3s --- .github/workflows/main.yml | 1 + README.rst | 1 + k3s/README.rst | 1 + k3s/setup.py | 19 ++++++++ k3s/testcontainers/k3s/__init__.py | 65 ++++++++++++++++++++++++++++ k3s/tests/test_k3s.py | 12 +++++ requirements.in | 1 + requirements/macos-latest-3.10.txt | 55 ++++++++++++++++------- requirements/ubuntu-latest-3.10.txt | 55 ++++++++++++++++------- requirements/ubuntu-latest-3.11.txt | 53 +++++++++++++++++------ requirements/ubuntu-latest-3.7.txt | 55 ++++++++++++++++------- requirements/ubuntu-latest-3.8.txt | 55 ++++++++++++++++------- requirements/ubuntu-latest-3.9.txt | 55 ++++++++++++++++------- requirements/windows-latest-3.10.txt | 55 ++++++++++++++++------- 14 files changed, 379 insertions(+), 104 deletions(-) create mode 100644 k3s/README.rst create mode 100644 k3s/setup.py create mode 100644 k3s/testcontainers/k3s/__init__.py create mode 100644 k3s/tests/test_k3s.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d71bb9d06..9fa7e0fed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,6 +43,7 @@ jobs: - rabbitmq - redis - selenium + - k3s runs-on: ${{ matrix.runtime.machine }} steps: - uses: actions/checkout@v3 diff --git a/README.rst b/README.rst index b688393cf..729a590bc 100644 --- a/README.rst +++ b/README.rst @@ -35,6 +35,7 @@ testcontainers-python facilitates the use of Docker containers for functional an rabbitmq/README redis/README selenium/README + k3s/README Getting Started --------------- diff --git a/k3s/README.rst b/k3s/README.rst new file mode 100644 index 000000000..51e4c5020 --- /dev/null +++ b/k3s/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.k3s.K3SContainer diff --git a/k3s/setup.py b/k3s/setup.py new file mode 100644 index 000000000..935820d87 --- /dev/null +++ b/k3s/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, find_namespace_packages + +description = "K3S component of testcontainers-python." + +setup( + name="testcontainers-k3s", + version="0.0.1rc1", + packages=find_namespace_packages(), + description=description, + long_description=description, + long_description_content_type="text/x-rst", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + "kubernetes", + "pyyaml" + ], + python_requires=">=3.7", +) diff --git a/k3s/testcontainers/k3s/__init__.py b/k3s/testcontainers/k3s/__init__.py new file mode 100644 index 000000000..48c9d0959 --- /dev/null +++ b/k3s/testcontainers/k3s/__init__.py @@ -0,0 +1,65 @@ +# +# 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. + +from testcontainers.core.config import MAX_TRIES +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +class K3SContainer(DockerContainer): + """ + K3S container. + + Example: + + .. doctest:: + + >>> import yaml + >>> from testcontainers.k3s import K3SContainer + >>> from kubernetes import client, config + + >>> with K3SContainer() as k3s: + ... config.load_kube_config_from_dict(yaml.safe_load(k3s.config_yaml())) + ... pod = client.CoreV1Api().list_pod_for_all_namespaces(limit=1) + ... assert len(pod.items) > 0, "Unable to get running nodes from k3s cluster" + """ + + KUBE_SECURE_PORT = 6443 + RANCHER_WEBHOOK_PORT = 8443 + + def __init__(self, image="rancher/k3s:latest", **kwargs) -> None: + super(K3SContainer, self).__init__(image, **kwargs) + self.with_exposed_ports(self.KUBE_SECURE_PORT, self.RANCHER_WEBHOOK_PORT) + self.with_env("K3S_URL", f'https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}') + self.with_command("server --disable traefik --tls-san=" + self.get_container_host_ip()) + self.with_kwargs(privileged=True, tmpfs={"/run": "", "/var/run": ""}) + self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw") + + def _connect(self) -> None: + wait_for_logs(self, predicate="Node controller sync successful", timeout=MAX_TRIES) + + def start(self) -> "K3SContainer": + super().start() + self._connect() + return self + + def config_yaml(self) -> str: + """This function returns the kubernetes config yaml which can be used + to initialise k8s client + """ + execution = self.get_wrapped_container().exec_run(['cat', '/etc/rancher/k3s/k3s.yaml']) + config_yaml = execution.output.decode('utf-8') \ + .replace(f'https://127.0.0.1:{self.KUBE_SECURE_PORT}', + f'https://{self.get_container_host_ip()}:' + f'{self.get_exposed_port(self.KUBE_SECURE_PORT)}') + return config_yaml diff --git a/k3s/tests/test_k3s.py b/k3s/tests/test_k3s.py new file mode 100644 index 000000000..edff1c6d8 --- /dev/null +++ b/k3s/tests/test_k3s.py @@ -0,0 +1,12 @@ +# The versions below were the current supported versions at time of writing (2022-08-11) +import yaml +from kubernetes import client, config + +from testcontainers.k3s import K3SContainer + + +def test_docker_run_k3s(): + with K3SContainer() as k3s: + config.load_kube_config_from_dict(yaml.safe_load(k3s.config_yaml())) + pod = client.CoreV1Api().list_pod_for_all_namespaces(limit=1) + assert len(pod.items) > 0, "Unable to get running nodes from k3s cluster" diff --git a/requirements.in b/requirements.in index d5e7fb603..104dc36a3 100644 --- a/requirements.in +++ b/requirements.in @@ -20,6 +20,7 @@ -e file:rabbitmq -e file:redis -e file:selenium +-e file:k3s cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. pg8000 diff --git a/requirements/macos-latest-3.10.txt b/requirements/macos-latest-3.10.txt index b440bf30a..d36feeeb6 100644 --- a/requirements/macos-latest-3.10.txt +++ b/requirements/macos-latest-3.10.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -90,16 +93,17 @@ azure-storage-blob==12.19.0 # via testcontainers-azurite babel==2.13.1 # via sphinx -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -137,7 +141,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 # via # pytest # trio @@ -149,7 +153,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -161,20 +167,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -201,6 +207,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.3.0 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -213,11 +221,15 @@ minio==7.2.0 # via testcontainers-minio more-itertools==10.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j nh3==0.2.14 # via readme-renderer -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -248,7 +260,7 @@ protobuf==4.25.1 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -263,7 +275,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -289,6 +301,7 @@ python-arango==7.8.1 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -299,6 +312,10 @@ pytz==2023.3.post1 # via # clickhouse-driver # neo4j +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==42.0 # via twine redis==5.0.1 @@ -308,12 +325,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -327,7 +348,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -338,6 +359,7 @@ six==1.16.0 # azure-core # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -395,6 +417,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -403,8 +426,10 @@ urllib3[socks]==1.26.18 # testcontainers-core # twine websocket-client==1.6.4 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core diff --git a/requirements/ubuntu-latest-3.10.txt b/requirements/ubuntu-latest-3.10.txt index 8bea3419e..bc349e7c9 100644 --- a/requirements/ubuntu-latest-3.10.txt +++ b/requirements/ubuntu-latest-3.10.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -90,16 +93,17 @@ azure-storage-blob==12.19.0 # via testcontainers-azurite babel==2.13.1 # via sphinx -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -138,7 +142,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 # via # pytest # trio @@ -150,7 +154,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -162,20 +168,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -206,6 +212,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.3.0 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -218,11 +226,15 @@ minio==7.2.0 # via testcontainers-minio more-itertools==10.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j nh3==0.2.14 # via readme-renderer -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -253,7 +265,7 @@ protobuf==4.25.1 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -268,7 +280,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -294,6 +306,7 @@ python-arango==7.8.1 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -304,6 +317,10 @@ pytz==2023.3.post1 # via # clickhouse-driver # neo4j +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==42.0 # via twine redis==5.0.1 @@ -313,12 +330,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -332,7 +353,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -345,6 +366,7 @@ six==1.16.0 # azure-core # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -402,6 +424,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -410,8 +433,10 @@ urllib3[socks]==1.26.18 # testcontainers-core # twine websocket-client==1.6.4 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core diff --git a/requirements/ubuntu-latest-3.11.txt b/requirements/ubuntu-latest-3.11.txt index a3f26f09f..6dfda74d1 100644 --- a/requirements/ubuntu-latest-3.11.txt +++ b/requirements/ubuntu-latest-3.11.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -88,16 +91,17 @@ azure-storage-blob==12.19.0 # via testcontainers-azurite babel==2.13.1 # via sphinx -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -143,7 +147,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -155,20 +161,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -199,6 +205,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.3.0 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -211,11 +219,15 @@ minio==7.2.0 # via testcontainers-minio more-itertools==10.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j nh3==0.2.14 # via readme-renderer -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -246,7 +258,7 @@ protobuf==4.25.1 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -261,7 +273,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -287,6 +299,7 @@ python-arango==7.8.1 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -297,6 +310,10 @@ pytz==2023.3.post1 # via # clickhouse-driver # neo4j +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==42.0 # via twine redis==5.0.1 @@ -306,12 +323,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -325,7 +346,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -338,6 +359,7 @@ six==1.16.0 # azure-core # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -391,6 +413,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -399,8 +422,10 @@ urllib3[socks]==1.26.18 # testcontainers-core # twine websocket-client==1.6.4 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core diff --git a/requirements/ubuntu-latest-3.7.txt b/requirements/ubuntu-latest-3.7.txt index 2d6bc2c59..c3ebc37bb 100644 --- a/requirements/ubuntu-latest-3.7.txt +++ b/requirements/ubuntu-latest-3.7.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -94,16 +97,17 @@ backports-zoneinfo==0.2.1 # via tzlocal bleach==6.0.0 # via readme-renderer -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -142,7 +146,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 # via # pytest # trio @@ -154,7 +158,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -166,20 +172,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -219,6 +225,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.1.1 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==2.2.0 # via rich markupsafe==2.1.3 @@ -231,9 +239,13 @@ minio==7.2.0 # via testcontainers-minio more-itertools==9.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -263,7 +275,7 @@ protobuf==4.24.4 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -278,7 +290,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -304,6 +316,7 @@ python-arango==7.5.6 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -315,6 +328,10 @@ pytz==2023.3.post1 # babel # clickhouse-driver # neo4j +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==37.3 # via twine redis==5.0.1 @@ -324,12 +341,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -343,7 +364,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -357,6 +378,7 @@ six==1.16.0 # bleach # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -416,6 +438,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -426,8 +449,10 @@ urllib3[socks]==1.26.18 webencodings==0.5.1 # via bleach websocket-client==1.6.1 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core diff --git a/requirements/ubuntu-latest-3.8.txt b/requirements/ubuntu-latest-3.8.txt index dfe429866..605b3cf1c 100644 --- a/requirements/ubuntu-latest-3.8.txt +++ b/requirements/ubuntu-latest-3.8.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -92,16 +95,17 @@ babel==2.13.1 # via sphinx backports-zoneinfo==0.2.1 # via tzlocal -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -140,7 +144,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 # via # pytest # trio @@ -152,7 +156,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -164,20 +170,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -211,6 +217,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.3.0 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -223,11 +231,15 @@ minio==7.2.0 # via testcontainers-minio more-itertools==10.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j nh3==0.2.14 # via readme-renderer -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -258,7 +270,7 @@ protobuf==4.25.1 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -273,7 +285,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -299,6 +311,7 @@ python-arango==7.8.1 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -310,6 +323,10 @@ pytz==2023.3.post1 # babel # clickhouse-driver # neo4j +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==42.0 # via twine redis==5.0.1 @@ -319,12 +336,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -338,7 +359,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -351,6 +372,7 @@ six==1.16.0 # azure-core # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -403,6 +425,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -411,8 +434,10 @@ urllib3[socks]==1.26.18 # testcontainers-core # twine websocket-client==1.6.4 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core diff --git a/requirements/ubuntu-latest-3.9.txt b/requirements/ubuntu-latest-3.9.txt index 3659fe21b..970a09211 100644 --- a/requirements/ubuntu-latest-3.9.txt +++ b/requirements/ubuntu-latest-3.9.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -90,16 +93,17 @@ azure-storage-blob==12.19.0 # via testcontainers-azurite babel==2.13.1 # via sphinx -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -138,7 +142,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 # via # pytest # trio @@ -150,7 +154,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -162,20 +168,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -207,6 +213,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.3.0 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -219,11 +227,15 @@ minio==7.2.0 # via testcontainers-minio more-itertools==10.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j nh3==0.2.14 # via readme-renderer -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -254,7 +266,7 @@ protobuf==4.25.1 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -269,7 +281,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -295,6 +307,7 @@ python-arango==7.8.1 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -305,6 +318,10 @@ pytz==2023.3.post1 # via # clickhouse-driver # neo4j +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==42.0 # via twine redis==5.0.1 @@ -314,12 +331,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -333,7 +354,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -346,6 +367,7 @@ six==1.16.0 # azure-core # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -403,6 +425,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -411,8 +434,10 @@ urllib3[socks]==1.26.18 # testcontainers-core # twine websocket-client==1.6.4 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core diff --git a/requirements/windows-latest-3.10.txt b/requirements/windows-latest-3.10.txt index 7cdf46d6f..b9f41c654 100644 --- a/requirements/windows-latest-3.10.txt +++ b/requirements/windows-latest-3.10.txt @@ -21,6 +21,7 @@ # testcontainers-clickhouse # testcontainers-elasticsearch # testcontainers-gcp + # testcontainers-k3s # testcontainers-kafka # testcontainers-keycloak # testcontainers-localstack @@ -40,6 +41,8 @@ # via -r requirements.in -e file:google # via -r requirements.in +-e file:k3s + # via -r requirements.in -e file:kafka # via -r requirements.in -e file:keycloak @@ -90,16 +93,17 @@ azure-storage-blob==12.19.0 # via testcontainers-azurite babel==2.13.1 # via sphinx -boto3==1.29.1 +boto3==1.33.1 # via testcontainers-localstack -botocore==1.32.1 +botocore==1.33.1 # via # boto3 # s3transfer cachetools==5.3.2 # via google-auth -certifi==2023.7.22 +certifi==2023.11.17 # via + # kubernetes # minio # opensearch-py # requests @@ -142,7 +146,7 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 # via # pytest # trio @@ -154,7 +158,9 @@ google-api-core[grpc]==2.14.0 # google-api-core # google-cloud-pubsub google-auth==2.23.4 - # via google-api-core + # via + # google-api-core + # kubernetes google-cloud-pubsub==2.18.4 # via testcontainers-gcp googleapis-common-protos[grpc]==1.61.0 @@ -166,20 +172,20 @@ greenlet==3.0.1 # via sqlalchemy grpc-google-iam-v1==0.12.7 # via google-cloud-pubsub -grpcio==1.59.2 +grpcio==1.59.3 # via # google-api-core # google-cloud-pubsub # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.59.2 +grpcio-status==1.59.3 # via # google-api-core # google-cloud-pubsub h11==0.14.0 # via wsproto -idna==3.4 +idna==3.6 # via # requests # trio @@ -206,6 +212,8 @@ kafka-python==2.0.2 # via testcontainers-kafka keyring==24.3.0 # via twine +kubernetes==28.1.0 + # via testcontainers-k3s markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -218,11 +226,15 @@ minio==7.2.0 # via testcontainers-minio more-itertools==10.1.0 # via jaraco-classes -neo4j==5.14.1 +neo4j==5.15.0 # via testcontainers-neo4j nh3==0.2.14 # via readme-renderer -opensearch-py==2.4.1 +oauthlib==3.2.2 + # via + # kubernetes + # requests-oauthlib +opensearch-py==2.4.2 # via testcontainers-opensearch outcome==1.3.0.post0 # via trio @@ -253,7 +265,7 @@ protobuf==4.25.1 # proto-plus psycopg2-binary==2.9.9 # via testcontainers-postgres -pyasn1==0.5.0 +pyasn1==0.5.1 # via # pyasn1-modules # python-jose @@ -268,7 +280,7 @@ pycryptodome==3.19.0 # via minio pyflakes==2.1.1 # via flake8 -pygments==2.16.1 +pygments==2.17.2 # via # readme-renderer # rich @@ -294,6 +306,7 @@ python-arango==7.8.1 python-dateutil==2.8.2 # via # botocore + # kubernetes # opensearch-py # pg8000 python-jose==3.3.0 @@ -308,6 +321,10 @@ pywin32==306 # via docker pywin32-ctypes==0.2.2 # via keyring +pyyaml==6.0.1 + # via + # kubernetes + # testcontainers-k3s readme-renderer==42.0 # via twine redis==5.0.1 @@ -317,12 +334,16 @@ requests==2.31.0 # azure-core # docker # google-api-core + # kubernetes # opensearch-py # python-arango # python-keycloak + # requests-oauthlib # requests-toolbelt # sphinx # twine +requests-oauthlib==1.3.1 + # via kubernetes requests-toolbelt==1.0.0 # via # python-arango @@ -336,7 +357,7 @@ rsa==4.9 # via # google-auth # python-jose -s3transfer==0.7.0 +s3transfer==0.8.0 # via boto3 scramp==1.4.4 # via pg8000 @@ -347,6 +368,7 @@ six==1.16.0 # azure-core # ecdsa # isodate + # kubernetes # opensearch-py # python-dateutil sniffio==1.3.0 @@ -406,6 +428,7 @@ urllib3[socks]==1.26.18 # via # botocore # docker + # kubernetes # minio # opensearch-py # python-arango @@ -414,8 +437,10 @@ urllib3[socks]==1.26.18 # testcontainers-core # twine websocket-client==1.6.4 - # via docker -wheel==0.41.3 + # via + # docker + # kubernetes +wheel==0.42.0 # via -r requirements.in wrapt==1.16.0 # via testcontainers-core From eb9723648eb733c63a71a5448070c45c7b7db070 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 31 Jan 2024 11:45:54 -0500 Subject: [PATCH 270/425] Disable workflows and add issue templates (#411) * disable workflows, add issue templates * also remove codeowners * bring back docs * bring back main --- .../bug-or-unexpected-behavior.md | 2 +- .github/ISSUE_TEMPLATE/feature-proposal.md | 22 ++++++++++++ .github/ISSUE_TEMPLATE/question.md | 35 +++++++++++++++++++ ...label.yml => attention-label.yml.disabled} | 0 ...irements.yml => requirements.yml.disabled} | 0 ...ge-label.yml => triage-label.yml.disabled} | 0 CODEOWNERS | 23 ------------ 7 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/feature-proposal.md create mode 100644 .github/ISSUE_TEMPLATE/question.md rename .github/workflows/{attention-label.yml => attention-label.yml.disabled} (100%) rename .github/workflows/{requirements.yml => requirements.yml.disabled} (100%) rename .github/workflows/{triage-label.yml => triage-label.yml.disabled} (100%) delete mode 100644 CODEOWNERS diff --git a/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md b/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md index 56242051e..98b61f3f1 100644 --- a/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md +++ b/.github/ISSUE_TEMPLATE/bug-or-unexpected-behavior.md @@ -1,7 +1,7 @@ --- name: Bug or unexpected behavior about: Create a report to help us improve. -title: '' +title: 'Bug: ' labels: bug assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature-proposal.md b/.github/ISSUE_TEMPLATE/feature-proposal.md new file mode 100644 index 000000000..cabafa26b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-proposal.md @@ -0,0 +1,22 @@ +--- +name: Feature Proposal +about: Send a note to the tc-python team about something you would like to see changed or improved. +title: 'Feature: ' +labels: '🚀 enhancement' +assignees: '' + +--- + + + +**What are you trying to do?** + +Describe the intention of the enhancement. + +**Why should it be done this way?** + +Describe the motivation of the enhancement. + +**Other references:** + +Include any other relevant reading material about the enhancement. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..9a7af6ead --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,35 @@ +--- +name: Question +about: Ask a question about how to use this library. +title: 'Question: ' +labels: '📖 documentation' +assignees: '' + +--- + + + +**What are you trying to do?** + +Ask your question here + +**Where are you trying to do it?** + +Provide a self-contained code snippet that illustrates the bug or unexpected behavior. +Ideally, include a link to a public repository with a minimal project where someone from the +testcontainers-python can submit a PR with a solution to the problem you are facing with the library. + +**Runtime environment** + +Provide a summary of your runtime environment. Which operating system, python version, and docker version are you using? What is the version of `testcontainers-python` you are using? You can run the following commands to get the relevant information. + +```bash +# Get the operating system information (on a unix os). +$ uname -a +# Get the python version. +$ python --version +# Get the docker version and other docker information. +$ docker info +# Get all python packages. +$ pip freeze +``` diff --git a/.github/workflows/attention-label.yml b/.github/workflows/attention-label.yml.disabled similarity index 100% rename from .github/workflows/attention-label.yml rename to .github/workflows/attention-label.yml.disabled diff --git a/.github/workflows/requirements.yml b/.github/workflows/requirements.yml.disabled similarity index 100% rename from .github/workflows/requirements.yml rename to .github/workflows/requirements.yml.disabled diff --git a/.github/workflows/triage-label.yml b/.github/workflows/triage-label.yml.disabled similarity index 100% rename from .github/workflows/triage-label.yml rename to .github/workflows/triage-label.yml.disabled diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index aa08e1052..000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,23 +0,0 @@ -/arangodb @nshine -/azurite @pffijt -/clickhouse @yakimka -# /compose -# /core -/elasticsearch @nivm @daTokenizer -/google @tillahoffmann -/kafka @ash1425 -/keycloak @timbmg -/localstack @ImFlog -# /meta -/minio @maltehedderich -/mongodb @dabrign -# /mssql -# /mysql -/neo4j @eastlondoner -# /nginx -/opensearch @maltehedderich -# /oracle -# /postgres -/rabbitmq @KerstenBreuer -/redis @daTokenizer -# /selenium From b59bbbc79338b4e8d7c98eea639da4016b6018bc Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 10 Feb 2024 02:47:45 -0500 Subject: [PATCH 271/425] Create settings.yaml (#419) --- .github/settings.yaml | 156 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .github/settings.yaml diff --git a/.github/settings.yaml b/.github/settings.yaml new file mode 100644 index 000000000..f340a93e7 --- /dev/null +++ b/.github/settings.yaml @@ -0,0 +1,156 @@ +# These settings are synced to GitHub by https://probot.github.io/apps/settings/ + +repository: + # See https://docs.github.com/en/rest/reference/repos#update-a-repository for all available settings. + + # The name of the repository. Changing this will rename the repository + name: testcontainers-python + + # A short description of the repository that will show up on GitHub + description: >- + Testcontainers is a Python library that providing a friendly + API to run Docker container. It is designed to create runtime environment + to use during your automatic tests. + + # A URL with more information about the repository + homepage: https://testcontainers-python.readthedocs.io/en/latest + + # A comma-separated list of topics to set on the repository + topics: database,python,python3,selenium,testcontainers,testing + + # Either `true` to make the repository private, or `false` to make it public. + private: false + + # Either `true` to enable issues for this repository, `false` to disable them. + has_issues: true + + # Either `true` to enable projects for this repository, or `false` to disable them. + # If projects are disabled for the organization, passing `true` will cause an API error. + has_projects: false + + # Either `true` to enable the wiki for this repository, `false` to disable it. + has_wiki: false + + # Either `true` to enable downloads for this repository, `false` to disable them. + has_downloads: true + + # Updates the default branch for this repository. + default_branch: main + + # Either `true` to allow squash-merging pull requests, or `false` to prevent + # squash-merging. + allow_squash_merge: true + + # Either `true` to allow merging pull requests with a merge commit, or `false` + # to prevent merging pull requests with merge commits. + allow_merge_commit: false + + # Either `true` to allow rebase-merging pull requests, or `false` to prevent + # rebase-merging. + allow_rebase_merge: false + + # Either `true` to enable automatic deletion of branches on merge, or `false` to disable + delete_branch_on_merge: true + +# Labels: define labels for Issues and Pull Requests +# If including a `#`, make sure to wrap it with quotes! +labels: + - { name: '⛔ invalid', color: '#e6e6e6', description: '' } + - { name: 'dependencies', color: '#0366d6', description: 'Pull requests that update a dependency file' } + - { name: 'good first issue', color: '#1C49A0', description: '' } + - { name: '✅ close on merge', color: '#0E8A16', description: 'Issue that will be closed by an open pull request' } + - { name: '✨ package: new', color: '#0E8A16', description: '' } + - { name: '❓ question', color: '#cc317c', description: '' } + - { name: '🍏 macos', color: '#C5BF0',, description: '' } + - { name: '🐛 bug', color: '#ee001',, description: '' } + - { name: '🐧 linux', color: '#3ED4D',, description: '' } + - { name: '👀 requires attention', color: '#fef2c0', description: '' } + - { name: '📖 documentation', color: '#d93f0b', description: '' } + - { name: '📦 package: clickhouse', color: '#0052CC', description: '' } + - { name: '📦 package: compose', color: '#0052CC', description: '' } + - { name: '📦 package: core', color: '#0052CC', description: '' } + - { name: '📦 package: elasticsearch', color: '#0052CC', description: '' } + - { name: '📦 package: google', color: '#0052CC', description: '' } + - { name: '📦 package: kafka', color: '#0052CC', description: '' } + - { name: '📦 package: keycloak', color: '#0052CC', description: '' } + - { name: '📦 package: mongodb', color: '#0052CC', description: '' } + - { name: '📦 package: mssql', color: '#0052CC', description: '' } + - { name: '📦 package: neo4j', color: '#0052CC', description: '' } + - { name: '📦 package: oracle', color: '#0052CC', description: '' } + - { name: '📦 package: postgres', color: '#0052CC', description: '' } + - { name: '📦 package: rabbitmq', color: '#0052CC', description: '' } + - { name: '📦 package: selenium', color: '#0052CC', description: '' } + - { name: '🔀 requires triage', color: '#bfdadc', description: '' } + - { name: '🔧 maintenance', color: '#c2f759', description: '' } + - { name: '🚀 enhancement', color: '#84b6eb', description: '' } + - { name: '🚫 wontfix', color: '#ffffff', description: '' } + - { name: '🛟 help wanted', color: '#128A0C', description: '' } + +# Collaborators: give specific users access to this repository. +# See https://docs.github.com/en/rest/reference/repos#add-a-repository-collaborator for available options +collaborators: + - username: totallyzen + permission: maintain + - username: alexanderankin + permission: maintain + - username: kiview + permission: admin + #- username: testcontainersbot + # permission: write + + # Note: `permission` is only valid on organization-owned repositories. + # The permission to grant the collaborator. Can be one of: + # * `pull` - can pull, but not push to or administer this repository. + # * `push` - can pull and push, but not administer this repository. + # * `admin` - can pull, push and administer this repository. + # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions. + # * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access. + +# See https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions for available options +teams: + # Please make sure the team already exist in the organization, as the repository-settings application is not creating them. + # See https://github.com/repository-settings/app/discussions/639 for more information about teams and settings + # - name: go-team + # # The permission to grant the team. Can be one of: + # # * `pull` - can pull, but not push to or administer this repository. + # # * `push` - can pull and push, but not administer this repository. + # # * `admin` - can pull, push and administer this repository. + # # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions. + # # * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access. + # permission: admin + - name: oss-maintainers + permission: admin + +branches: + - name: main + # https://docs.github.com/en/rest/reference/repos#update-branch-protection + # Branch Protection settings. Set to null to disable + protection: + # Required. Require at least one approving review on a pull request, before merging. Set to null to disable. + required_pull_request_reviews: + # The number of approvals required. (1-6) + required_approving_review_count: 1 + # Dismiss approved reviews automatically when a new commit is pushed. + dismiss_stale_reviews: false + # Blocks merge until code owners have reviewed. + require_code_owner_reviews: false + # Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories. + dismissal_restrictions: + users: [] + teams: [] # [go-team] + # Required. Require status checks to pass before merging. Set to null to disable + required_status_checks: + # Required. Require branches to be up to date before merging. + strict: false + # Required. The list of status checks to require in order to merge into this branch + contexts: [] + # - "core / test (ubuntu, 3.11)" + # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. + enforce_admins: false + # Prevent merge commits from being pushed to matching branches + required_linear_history: true + # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable. + restrictions: + apps: [] + users: [kiview,totallyzen,alexanderankin] + teams: [oss-maintainers] From 3abe5f13524cee953b25c38db3bf064d2ca0a279 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sun, 11 Feb 2024 07:50:30 -0500 Subject: [PATCH 272/425] Rename settings.yaml to settings.yml (#420) --- .github/{settings.yaml => settings.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{settings.yaml => settings.yml} (100%) diff --git a/.github/settings.yaml b/.github/settings.yml similarity index 100% rename from .github/settings.yaml rename to .github/settings.yml From 348f83da14bad3e7e90ff5a58ed2cf599aa19e93 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 12 Feb 2024 06:46:39 -0500 Subject: [PATCH 273/425] try adding a required check to see if it helps (#421) --- .github/settings.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index f340a93e7..f191b87d3 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -143,8 +143,8 @@ branches: # Required. Require branches to be up to date before merging. strict: false # Required. The list of status checks to require in order to merge into this branch - contexts: [] - # - "core / test (ubuntu, 3.11)" + contexts: + - "core / test (ubuntu, 3.11)" # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. enforce_admins: false # Prevent merge commits from being pushed to matching branches From 6c695835520bdcbf9824e8cefa00f7613d2a7cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:59:31 +0100 Subject: [PATCH 274/425] feat(build): use poetry and organise modules (#408) # Changes Fixes #204 - We do not publish `testcontainers-*` packages any more as it's a maintenance nightmare on PyPI - example: in case of loss of access to the PyPI package, it's a messy and long procedure to reclaim it - Instead the project root is now a collection of modules, see `pyproject.toml` - All published under `testcontainers` which will allow the previous `testcontainers[extra1, extra2]` format to be used (replacing the `/meta` package mechanism) - Removes old config files like `.coveragerc` and `setup.cfg` and integrates them into `pyproject.toml` - more grooming incoming in future PRs (like the move to `ruff` which is a more performant formatter/linter) --------- Co-authored-by: Balint Bartha Co-authored-by: Dave Ankin --- .coveragerc | 12 - .github/workflows/ci-community.yml | 76 + .github/workflows/ci-core.yml | 34 + .github/workflows/docs.yml | 18 +- .github/workflows/main.yml | 91 - .github/workflows/triage-label.yml.disabled | 1 - README.rst => INDEX.rst | 47 +- MANIFEST.in | 1 - Makefile | 16 +- README.md | 24 + arangodb/setup.py | 18 - azurite/setup.py | 18 - clickhouse/setup.py | 18 - conf.py | 4 +- core/README.rst | 4 +- core/setup.py | 19 - core/testcontainers/core/container.py | 11 +- core/testcontainers/core/docker_client.py | 10 +- core/testcontainers/core/generic.py | 8 +- core/testcontainers/core/utils.py | 4 +- core/testcontainers/core/waiting_utils.py | 10 +- core/tests/test_docker_client.py | 1 + elasticsearch/setup.py | 17 - google/setup.py | 18 - k3s/setup.py | 19 - kafka/setup.py | 18 - keycloak/setup.py | 18 - localstack/setup.py | 18 - meta/README.rst | 1 - meta/setup.py | 71 - minio/setup.py | 18 - {arangodb => modules/arangodb}/README.rst | 0 .../testcontainers/arangodb/__init__.py | 0 .../arangodb}/tests/test_arangodb.py | 0 {azurite => modules/azurite}/README.rst | 0 .../testcontainers/azurite/__init__.py | 0 .../azurite}/tests/test_azurite.py | 0 {clickhouse => modules/clickhouse}/README.rst | 0 .../testcontainers/clickhouse/__init__.py | 0 .../clickhouse}/tests/test_clickhouse.py | 0 .../elasticsearch}/README.rst | 0 .../testcontainers/elasticsearch/__init__.py | 2 +- .../tests/test_elasticsearch.py | 4 +- {google => modules/google}/README.rst | 0 .../google}/testcontainers/google/__init__.py | 0 .../google}/testcontainers/google/pubsub.py | 0 .../google}/tests/test_google.py | 0 {k3s => modules/k3s}/README.rst | 0 .../k3s}/testcontainers/k3s/__init__.py | 0 {k3s => modules/k3s}/tests/test_k3s.py | 0 {kafka => modules/kafka}/README.rst | 0 .../kafka}/testcontainers/kafka/__init__.py | 0 {kafka => modules/kafka}/tests/test_kafka.py | 0 {keycloak => modules/keycloak}/README.rst | 0 .../testcontainers/keycloak/__init__.py | 0 .../keycloak}/tests/test_keycloak.py | 0 {localstack => modules/localstack}/README.rst | 0 .../testcontainers/localstack/__init__.py | 0 .../localstack}/tests/test_localstack.py | 0 {minio => modules/minio}/README.rst | 0 .../minio}/testcontainers/minio/__init__.py | 0 {minio => modules/minio}/tests/test_minio.py | 0 {mongodb => modules/mongodb}/README.rst | 0 .../testcontainers/mongodb/__init__.py | 0 .../mongodb}/tests/test_mongodb.py | 0 {mssql => modules/mssql}/README.rst | 0 .../mssql}/testcontainers/mssql/__init__.py | 0 {mssql => modules/mssql}/tests/test_mssql.py | 0 {mysql => modules/mysql}/README.rst | 0 .../mysql}/testcontainers/mysql/__init__.py | 0 {mysql => modules/mysql}/tests/test_mysql.py | 0 {neo4j => modules/neo4j}/README.rst | 0 .../neo4j}/testcontainers/neo4j/__init__.py | 0 {neo4j => modules/neo4j}/tests/test_neo4j.py | 0 {nginx => modules/nginx}/README.rst | 0 .../nginx}/testcontainers/nginx/__init__.py | 0 {nginx => modules/nginx}/tests/test_nginx.py | 0 {opensearch => modules/opensearch}/README.rst | 0 .../testcontainers/opensearch/__init__.py | 8 +- .../opensearch}/tests/test_opensearch.py | 0 {oracle => modules/oracle}/README.rst | 0 .../oracle}/testcontainers/oracle/__init__.py | 0 .../oracle}/tests/test_oracle.py | 0 {postgres => modules/postgres}/README.rst | 0 .../testcontainers/postgres/__init__.py | 0 .../postgres}/tests/test_postgres.py | 0 {rabbitmq => modules/rabbitmq}/README.rst | 0 .../testcontainers/rabbitmq/__init__.py | 0 .../rabbitmq}/tests/test_rabbitmq.py | 0 {redis => modules/redis}/README.rst | 0 .../redis}/testcontainers/redis/__init__.py | 0 {redis => modules/redis}/tests/test_redis.py | 0 {selenium => modules/selenium}/README.rst | 0 .../testcontainers/selenium/__init__.py | 0 .../selenium}/tests/test_selenium.py | 0 mongodb/setup.py | 18 - mssql/setup.py | 19 - mysql/setup.py | 19 - neo4j/setup.py | 18 - nginx/setup.py | 17 - opensearch/setup.py | 18 - oracle/setup.py | 19 - poetry.lock | 2998 +++++++++++++++++ postgres/setup.py | 19 - pyproject.toml | 144 + rabbitmq/setup.py | 18 - redis/setup.py | 18 - requirements.in | 31 - selenium/setup.py | 18 - setup.cfg | 10 - 110 files changed, 3354 insertions(+), 687 deletions(-) delete mode 100644 .coveragerc create mode 100644 .github/workflows/ci-community.yml create mode 100644 .github/workflows/ci-core.yml delete mode 100644 .github/workflows/main.yml rename README.rst => INDEX.rst (85%) delete mode 100644 MANIFEST.in create mode 100644 README.md delete mode 100644 arangodb/setup.py delete mode 100644 azurite/setup.py delete mode 100644 clickhouse/setup.py delete mode 100644 core/setup.py delete mode 100644 elasticsearch/setup.py delete mode 100644 google/setup.py delete mode 100644 k3s/setup.py delete mode 100644 kafka/setup.py delete mode 100644 keycloak/setup.py delete mode 100644 localstack/setup.py delete mode 100644 meta/README.rst delete mode 100644 meta/setup.py delete mode 100644 minio/setup.py rename {arangodb => modules/arangodb}/README.rst (100%) rename {arangodb => modules/arangodb}/testcontainers/arangodb/__init__.py (100%) rename {arangodb => modules/arangodb}/tests/test_arangodb.py (100%) rename {azurite => modules/azurite}/README.rst (100%) rename {azurite => modules/azurite}/testcontainers/azurite/__init__.py (100%) rename {azurite => modules/azurite}/tests/test_azurite.py (100%) rename {clickhouse => modules/clickhouse}/README.rst (100%) rename {clickhouse => modules/clickhouse}/testcontainers/clickhouse/__init__.py (100%) rename {clickhouse => modules/clickhouse}/tests/test_clickhouse.py (100%) rename {elasticsearch => modules/elasticsearch}/README.rst (100%) rename {elasticsearch => modules/elasticsearch}/testcontainers/elasticsearch/__init__.py (99%) rename {elasticsearch => modules/elasticsearch}/tests/test_elasticsearch.py (80%) rename {google => modules/google}/README.rst (100%) rename {google => modules/google}/testcontainers/google/__init__.py (100%) rename {google => modules/google}/testcontainers/google/pubsub.py (100%) rename {google => modules/google}/tests/test_google.py (100%) rename {k3s => modules/k3s}/README.rst (100%) rename {k3s => modules/k3s}/testcontainers/k3s/__init__.py (100%) rename {k3s => modules/k3s}/tests/test_k3s.py (100%) rename {kafka => modules/kafka}/README.rst (100%) rename {kafka => modules/kafka}/testcontainers/kafka/__init__.py (100%) rename {kafka => modules/kafka}/tests/test_kafka.py (100%) rename {keycloak => modules/keycloak}/README.rst (100%) rename {keycloak => modules/keycloak}/testcontainers/keycloak/__init__.py (100%) rename {keycloak => modules/keycloak}/tests/test_keycloak.py (100%) rename {localstack => modules/localstack}/README.rst (100%) rename {localstack => modules/localstack}/testcontainers/localstack/__init__.py (100%) rename {localstack => modules/localstack}/tests/test_localstack.py (100%) rename {minio => modules/minio}/README.rst (100%) rename {minio => modules/minio}/testcontainers/minio/__init__.py (100%) rename {minio => modules/minio}/tests/test_minio.py (100%) rename {mongodb => modules/mongodb}/README.rst (100%) rename {mongodb => modules/mongodb}/testcontainers/mongodb/__init__.py (100%) rename {mongodb => modules/mongodb}/tests/test_mongodb.py (100%) rename {mssql => modules/mssql}/README.rst (100%) rename {mssql => modules/mssql}/testcontainers/mssql/__init__.py (100%) rename {mssql => modules/mssql}/tests/test_mssql.py (100%) rename {mysql => modules/mysql}/README.rst (100%) rename {mysql => modules/mysql}/testcontainers/mysql/__init__.py (100%) rename {mysql => modules/mysql}/tests/test_mysql.py (100%) rename {neo4j => modules/neo4j}/README.rst (100%) rename {neo4j => modules/neo4j}/testcontainers/neo4j/__init__.py (100%) rename {neo4j => modules/neo4j}/tests/test_neo4j.py (100%) rename {nginx => modules/nginx}/README.rst (100%) rename {nginx => modules/nginx}/testcontainers/nginx/__init__.py (100%) rename {nginx => modules/nginx}/tests/test_nginx.py (100%) rename {opensearch => modules/opensearch}/README.rst (100%) rename {opensearch => modules/opensearch}/testcontainers/opensearch/__init__.py (95%) rename {opensearch => modules/opensearch}/tests/test_opensearch.py (100%) rename {oracle => modules/oracle}/README.rst (100%) rename {oracle => modules/oracle}/testcontainers/oracle/__init__.py (100%) rename {oracle => modules/oracle}/tests/test_oracle.py (100%) rename {postgres => modules/postgres}/README.rst (100%) rename {postgres => modules/postgres}/testcontainers/postgres/__init__.py (100%) rename {postgres => modules/postgres}/tests/test_postgres.py (100%) rename {rabbitmq => modules/rabbitmq}/README.rst (100%) rename {rabbitmq => modules/rabbitmq}/testcontainers/rabbitmq/__init__.py (100%) rename {rabbitmq => modules/rabbitmq}/tests/test_rabbitmq.py (100%) rename {redis => modules/redis}/README.rst (100%) rename {redis => modules/redis}/testcontainers/redis/__init__.py (100%) rename {redis => modules/redis}/tests/test_redis.py (100%) rename {selenium => modules/selenium}/README.rst (100%) rename {selenium => modules/selenium}/testcontainers/selenium/__init__.py (100%) rename {selenium => modules/selenium}/tests/test_selenium.py (100%) delete mode 100644 mongodb/setup.py delete mode 100644 mssql/setup.py delete mode 100644 mysql/setup.py delete mode 100644 neo4j/setup.py delete mode 100644 nginx/setup.py delete mode 100644 opensearch/setup.py delete mode 100644 oracle/setup.py create mode 100644 poetry.lock delete mode 100644 postgres/setup.py create mode 100644 pyproject.toml delete mode 100644 rabbitmq/setup.py delete mode 100644 redis/setup.py delete mode 100644 requirements.in delete mode 100644 selenium/setup.py diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index a50a161ae..000000000 --- a/.coveragerc +++ /dev/null @@ -1,12 +0,0 @@ -[run] -branch = True -omit = - oracle.py - -[report] -exclude_lines = - pass - raise NoSuchElementException - raise NotImplementedError - if 0: - def __str__ \ No newline at end of file diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml new file mode 100644 index 000000000..4abcae752 --- /dev/null +++ b/.github/workflows/ci-community.yml @@ -0,0 +1,76 @@ +# Contrinuous Integration for community modules + +name: modules + +on: + push: + branches: [main] + paths: + - "modules/**" + pull_request: + branches: [main] + paths: + - "modules/**" + +permissions: + actions: write # needed for self-cancellation + +jobs: + test: + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + module: + - arangodb + - azurite + - clickhouse + - elasticsearch + - google + - kafka + - keycloak + - localstack + - minio + - mongodb + - mssql + - mysql + - neo4j + - nginx + - opensearch + - oracle + - postgres + - rabbitmq + - redis + - selenium + - k3s + runs-on: ubuntu-latest + steps: + - name: Get changed files + id: changes-for-module + uses: tj-actions/changed-files@v42 + with: + files: | + modules/${{ matrix.module }}/** + - name: Exit early, nothing to do + if: ${{ steps.changes-for-module.outputs.any_changed == 'false' }} + run: | + # cancel and wait for run to end + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout contents + uses: actions/checkout@v4 + - name: Setup Poetry + run: pipx install poetry + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: poetry + - name: Install Python dependencies + run: poetry install -E ${{ matrix.module }} + - name: Run linter + run: make modules/${{ matrix.module }}/lint + - name: Run tests + run: make modules/${{ matrix.module }}/tests \ No newline at end of file diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml new file mode 100644 index 000000000..56a45cd33 --- /dev/null +++ b/.github/workflows/ci-core.yml @@ -0,0 +1,34 @@ +# Contrinuous Integration for the core package + +name: core + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + strategy: + matrix: + os: [ ubuntu ] + python-version: ["3.9", "3.10", "3.11"] + runs-on: ${{ matrix.os }}-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Poetry + run: pipx install poetry + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: poetry + - name: Install Python dependencies + run: poetry install + - name: Run linter + run: make core/lint + - name: Run twine check + run: poetry build && poetry run twine check dist/*.tar.gz + - name: Run tests + run: make core/tests diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 266d60dd2..75f71e21d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,5 @@ -name: testcontainers documentation +name: docs + on: push: branches: [main] @@ -10,15 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Setup python 3.10 - uses: actions/setup-python@v4 + - name: Setup Poetry + run: pipx install poetry + - name: Setup python + uses: actions/setup-python@v5 with: - python-version: "3.10" - cache: pip - cache-dependency-path: requirements/ubuntu-latest-3.10.txt + python-version: "3.11" + cache: poetry - name: Install Python dependencies - run: | - pip install --upgrade pip - pip install -r requirements/ubuntu-latest-3.10.txt + run: poetry install --all-extras - name: Build documentation run: make docs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 9fa7e0fed..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: testcontainers packages -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - strategy: - matrix: - runtime: - - machine: ubuntu-latest - python-version: "3.7" - - machine: ubuntu-latest - python-version: "3.8" - - machine: ubuntu-latest - python-version: "3.9" - - machine: ubuntu-latest - python-version: "3.10" - - machine: ubuntu-latest - python-version: "3.11" - component: - - arangodb - - azurite - - clickhouse - - core - - elasticsearch - - google - - kafka - - keycloak - - localstack - - meta - - minio - - mongodb - - mssql - - mysql - - neo4j - - nginx - - opensearch - - oracle - - postgres - - rabbitmq - - redis - - selenium - - k3s - runs-on: ${{ matrix.runtime.machine }} - steps: - - uses: actions/checkout@v3 - - name: Setup python ${{ matrix.runtime.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.runtime.python-version }} - cache: pip - cache-dependency-path: ${{ format('requirements/{0}-{1}.txt', matrix.runtime.machine, matrix.runtime.python-version) }} - - name: Install Python dependencies - run: | - pip install --upgrade pip - pip install -r requirements/${{ matrix.runtime.machine }}-${{ matrix.runtime.python-version }}.txt - - name: Run docker diagnostics - if: matrix.component == 'core' - run: | - echo "Build minimal container for docker-in-docker diagnostics" - docker build -f Dockerfile.diagnostics -t testcontainers-python . - echo "Bare metal diagnostics" - python diagnostics.py - echo "Container diagnostics with bridge network" - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=bridge testcontainers-python python diagnostics.py - echo "Container diagnostics with host network" - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --network=host testcontainers-python python diagnostics.py - - name: Lint the code - run: make ${{ matrix.component }}/lint - - name: Run tests - if: matrix.component != 'meta' - run: make ${{ matrix.component }}/tests - - name: Run doctests - if: matrix.component != 'meta' - run: make ${{ matrix.component }}/doctest - - name: Build the package - run: make ${{ matrix.component }}/dist - - name: Upload the package to pypi - if: > - github.event_name == 'push' - && github.ref == 'refs/heads/main' - && github.repository_owner == 'testcontainers' - && matrix.runtime.python-version == '3.10' - && matrix.runtime.machine == 'ubuntu-latest' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: make ${{ matrix.component }}/upload diff --git a/.github/workflows/triage-label.yml.disabled b/.github/workflows/triage-label.yml.disabled index 6ca75ccb1..a5ecf38a9 100644 --- a/.github/workflows/triage-label.yml.disabled +++ b/.github/workflows/triage-label.yml.disabled @@ -1,4 +1,3 @@ -name: Automatically add triage labels to new issues and pull requests on: issues: types: diff --git a/README.rst b/INDEX.rst similarity index 85% rename from README.rst rename to INDEX.rst index 729a590bc..87c413355 100644 --- a/README.rst +++ b/INDEX.rst @@ -15,27 +15,27 @@ testcontainers-python facilitates the use of Docker containers for functional an .. toctree:: core/README - arangodb/README - azurite/README - clickhouse/README - elasticsearch/README - google/README - kafka/README - keycloak/README - localstack/README - minio/README - mongodb/README - mssql/README - mysql/README - neo4j/README - nginx/README - opensearch/README - oracle/README - postgres/README - rabbitmq/README - redis/README - selenium/README - k3s/README + modules/arangodb/README + modules/azurite/README + modules/clickhouse/README + modules/elasticsearch/README + modules/google/README + modules/kafka/README + modules/keycloak/README + modules/localstack/README + modules/minio/README + modules/mongodb/README + modules/mssql/README + modules/mysql/README + modules/neo4j/README + modules/nginx/README + modules/opensearch/README + modules/oracle/README + modules/postgres/README + modules/rabbitmq/README + modules/redis/README + modules/selenium/README + modules/k3s/README Getting Started --------------- @@ -47,8 +47,9 @@ Getting Started >>> with PostgresContainer("postgres:9.5") as postgres: ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) - ... result = engine.execute("select version()") - ... version, = result.fetchone() + ... with engine.begin() as connection: + ... result = connection.execute(sqlalchemy.text("select version()")) + ... version, = result.fetchone() >>> version 'PostgreSQL 9.5...' diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index ceeea233f..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include VERSION diff --git a/Makefile b/Makefile index 501172c97..8b40e3188 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -PYTHON_VERSIONS = 3.7 3.8 3.9 3.10 3.11 +PYTHON_VERSIONS = 3.9 3.10 3.11 PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} RUN = docker run --rm -it # Get all directories that contain a setup.py and get the directory name. -PACKAGES = $(subst /,,$(dir $(wildcard */setup.py))) +PACKAGES = core $(addprefix modules/,$(notdir $(wildcard modules/*))) # All */dist folders for each of the packages. DISTRIBUTIONS = $(addsuffix /dist,${PACKAGES}) @@ -25,12 +25,12 @@ ${DISTRIBUTIONS} : %/dist : %/setup.py # Targets to run the test suite for each package. tests : ${TESTS} ${TESTS} : %/tests : - pytest -svx --cov-report=term-missing --cov=testcontainers.$* --tb=short --strict-markers $*/tests + poetry run pytest -v --cov=testcontainers.$* $*/tests # Targets to lint the code. lint : ${LINT} ${LINT} : %/lint : - flake8 $* + poetry run flake8 $* # Targets to publish packages. upload : ${UPLOAD} @@ -42,7 +42,7 @@ ${UPLOAD} : %/upload : fi # Targets to build docker images -image: requirements/ubunut-latest-${PYTHON_VERSION}.txt +image: requirements/ubuntu-latest-${PYTHON_VERSION}.txt docker build --build-arg version=${PYTHON_VERSION} -t ${IMAGE} . # Targets to run tests in docker containers @@ -54,13 +54,13 @@ ${TESTS_DIND} : %/tests-dind : image # Target to build the documentation docs : - sphinx-build -nW . docs/_build + poetry run sphinx-build -nW . docs/_build doctest : ${DOCTESTS} - sphinx-build -b doctest . docs/_build + poetry run sphinx-build -b doctest . docs/_build ${DOCTESTS} : %/doctest : - sphinx-build -b doctest -c doctests $* docs/_build + poetry run sphinx-build -b doctest -c doctests $* docs/_build # Remove any generated files. clean : diff --git a/README.md b/README.md new file mode 100644 index 000000000..58f5eca52 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Testcontainers Python + +`testcontainers-python` facilitates the use of Docker containers for functional and integration testing. + +For more information, see [the docs][readthedocs]. + +[readthedocs]: https://testcontainers-python.readthedocs.io/en/latest/ + +## Getting Started + +```pycon +>>> from testcontainers.postgres import PostgresContainer +>>> import sqlalchemy + +>>> with PostgresContainer("postgres:9.5") as postgres: +... engine = sqlalchemy.create_engine(postgres.get_connection_url()) +... with engine.begin() as connection: +... result = connection.execute(sqlalchemy.text("select version()")) +... version, = result.fetchone() +>>> version +'PostgreSQL 9.5...' +``` + +The snippet above will spin up a postgres database in a container. The `get_connection_url()` convenience method returns a `sqlalchemy` compatible url we use to connect to the database and retrieve the database version. diff --git a/arangodb/setup.py b/arangodb/setup.py deleted file mode 100644 index 2309bca7f..000000000 --- a/arangodb/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Arango DB component of testcontainers-python." - -setup( - name="testcontainers-arangodb", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "python-arango", - ], - python_requires=">=3.7", -) diff --git a/azurite/setup.py b/azurite/setup.py deleted file mode 100644 index 18b10858b..000000000 --- a/azurite/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Azurite component of testcontainers-python." - -setup( - name="testcontainers-azurite", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "azure-storage-blob", - ], - python_requires=">=3.7", -) diff --git a/clickhouse/setup.py b/clickhouse/setup.py deleted file mode 100644 index 004a151c7..000000000 --- a/clickhouse/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "ClickHouse component of testcontainers-python." - -setup( - name="testcontainers-clickhouse", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "clickhouse-driver", - ], - python_requires=">=3.7", -) diff --git a/conf.py b/conf.py index efae1fe68..df300209f 100644 --- a/conf.py +++ b/conf.py @@ -50,7 +50,7 @@ source_suffix = '.rst' # The master toctree document. -master_doc = 'README' +master_doc = 'INDEX' # General information about the project. project = u'testcontainers' @@ -76,7 +76,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', "meta/README.rst"] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', "meta/README.rst", '.venv'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/core/README.rst b/core/README.rst index c5afdffc7..d8071467e 100644 --- a/core/README.rst +++ b/core/README.rst @@ -1,6 +1,6 @@ testcontainers-core =================== -:code:`testcontainers-core` is a utility package for spinning up Docker containers in testing environments. +:code:`testcontainers-core` is the core functionality for spinning up Docker containers in test environments. -.. autoclass:: testcontainers.core.container.DockerContainer +.. autoclass:: testcontainers.core.container.DockerContainer \ No newline at end of file diff --git a/core/setup.py b/core/setup.py deleted file mode 100644 index ef7c87f07..000000000 --- a/core/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Core component of testcontainers-python." - -setup( - name="testcontainers-core", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "docker>=4.0.0", - "urllib3<2.0", # https://github.com/docker/docker-py/issues/3113#issuecomment-1533389349 - "wrapt", - ], - python_requires=">=3.7", -) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 65acf9bed..c3825b935 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,11 +1,12 @@ -from docker.models.containers import Container import os from typing import Iterable, Optional, Tuple -from .waiting_utils import wait_container_is_ready -from .docker_client import DockerClient -from .exceptions import ContainerStartException -from .utils import setup_logger, inside_container, is_arm +from docker.models.containers import Container + +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.utils import setup_logger, inside_container, is_arm +from testcontainers.core.waiting_utils import wait_container_is_ready logger = setup_logger(__name__) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 7d5bcf530..228bfd1c1 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -11,16 +11,16 @@ # License for the specific language governing permissions and limitations # under the License. import atexit -import docker -from docker.errors import NotFound -from docker.models.containers import Container, ContainerCollection import functools as ft import os -from typing import List, Optional, Union import urllib +from typing import List, Optional, Union -from .utils import default_gateway_ip, inside_container, setup_logger +import docker +from docker.errors import NotFound +from docker.models.containers import Container, ContainerCollection +from .utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 7faac273a..e63478064 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -12,10 +12,10 @@ # under the License. from typing import Optional -from .container import DockerContainer -from .exceptions import ContainerStartException -from .utils import raise_for_deprecated_parameter -from .waiting_utils import wait_container_is_ready +from testcontainers.core.container import DockerContainer +from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.waiting_utils import wait_container_is_ready ADDITIONAL_TRANSIENT_ERRORS = [] try: diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index d8b288c7d..9a02747b0 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -1,8 +1,8 @@ +import logging import os import platform -import sys import subprocess -import logging +import sys LINUX = "linux" MAC = "mac" diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index d177d2b69..5e9aa33c1 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -16,17 +16,17 @@ import time import traceback from typing import Any, Callable, Iterable, Mapping, Optional, TYPE_CHECKING, Union + import wrapt -from . import config -from .utils import setup_logger +from testcontainers.core import config +from testcontainers.core.utils import setup_logger if TYPE_CHECKING: - from .container import DockerContainer + from testcontainers.core.container import DockerContainer logger = setup_logger(__name__) - # Get a tuple of transient exceptions for which we'll retry. Other exceptions will be raised. TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionError) @@ -46,7 +46,7 @@ def wait_container_is_ready(*transient_exceptions) -> Callable: @wrapt.decorator def wrapper(wrapped: Callable, instance: Any, args: Iterable, kwargs: Mapping) -> Any: - from .container import DockerContainer + from testcontainers.core.container import DockerContainer if isinstance(instance, DockerContainer): logger.info("Waiting for container %s with image %s to be ready ...", diff --git a/core/tests/test_docker_client.py b/core/tests/test_docker_client.py index af498ff69..ccd640e22 100644 --- a/core/tests/test_docker_client.py +++ b/core/tests/test_docker_client.py @@ -1,5 +1,6 @@ from unittest.mock import MagicMock, patch import docker + from testcontainers.core.docker_client import DockerClient from testcontainers.core.container import DockerContainer diff --git a/elasticsearch/setup.py b/elasticsearch/setup.py deleted file mode 100644 index 09d57cc6b..000000000 --- a/elasticsearch/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Elasticsearch component of testcontainers-python." - -setup( - name="testcontainers-elasticsearch", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - ], - python_requires=">=3.7", -) diff --git a/google/setup.py b/google/setup.py deleted file mode 100644 index 10a1247dc..000000000 --- a/google/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Google Cloud Platform component of testcontainers-python." - -setup( - name="testcontainers-gcp", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "google-cloud-pubsub>=2", - ], - python_requires=">=3.7", -) diff --git a/k3s/setup.py b/k3s/setup.py deleted file mode 100644 index 935820d87..000000000 --- a/k3s/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "K3S component of testcontainers-python." - -setup( - name="testcontainers-k3s", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "kubernetes", - "pyyaml" - ], - python_requires=">=3.7", -) diff --git a/kafka/setup.py b/kafka/setup.py deleted file mode 100644 index ac9412f7b..000000000 --- a/kafka/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Kafka component of testcontainers-python." - -setup( - name="testcontainers-kafka", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "kafka-python", - ], - python_requires=">=3.7", -) diff --git a/keycloak/setup.py b/keycloak/setup.py deleted file mode 100644 index 13236ea55..000000000 --- a/keycloak/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Keycloak component of testcontainers-python." - -setup( - name="testcontainers-keycloak", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "python-keycloak", - ], - python_requires=">=3.7", -) diff --git a/localstack/setup.py b/localstack/setup.py deleted file mode 100644 index 5dbb93f31..000000000 --- a/localstack/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "LocalStack component of testcontainers-python." - -setup( - name="testcontainers-localstack", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "boto3", - "testcontainers-core", - ], - python_requires=">=3.7", -) diff --git a/meta/README.rst b/meta/README.rst deleted file mode 100644 index cbd214fc1..000000000 --- a/meta/README.rst +++ /dev/null @@ -1 +0,0 @@ -The :code:`testcontainers` meta package facilitates the installation of the collection of namespace packages that make up the testcontainers ecosystem for python. It follows `Jupyter's approach `__ of installing a collection of packages. diff --git a/meta/setup.py b/meta/setup.py deleted file mode 100644 index 2dfba3a2d..000000000 --- a/meta/setup.py +++ /dev/null @@ -1,71 +0,0 @@ -# -# 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 setuptools - -description = "Python interface for throwaway instances of anything that can run in a Docker " \ - "container." -long_description = f"{description} See https://testcontainers-python.readthedocs.io/en/latest/ " \ - "for details." - -setuptools.setup( - name="testcontainers", - version="4.0.0rc1", - description=description, - long_description=long_description, - long_description_content_type="text/x-rst", - author="Sergey Pirogov", - author_email="automationremarks@gmail.com", - url="https://github.com/testcontainers/testcontainers-python", - keywords=["testing", "logging", "docker", "test automation"], - classifiers=[ - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Information Technology", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Software Development :: Libraries :: Python Modules", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX", - "Operating System :: Unix", - "Operating System :: MacOS", - ], - install_requires=[ - "testcontainers-core", - ], - extras_require={ - "arangodb": ["testcontainers-arangodb"], - "azurite": ["testcontainers-azurite"], - "clickhouse": ["testcontainers-clickhouse"], - "docker-compose": ["testcontainers-compose"], - "google-cloud-pubsub": ["testcontainers-gcp"], - "kafka": ["testcontainers-kafka"], - "keycloak": ["testcontainers-keycloak"], - "minio": ["testcontainers-minio"], - "mongo": ["testcontainers-mongo"], - "mssqlserver": ["testcontainers-mssql"], - "mysql": ["testcontainers-mysql"], - "neo4j": ["testcontainers-neo4j"], - "opensearch": ["testcontainers-opensearch"], - "oracle": ["testcontainers-oracle"], - "postgresql": ["testcontainers-postgres"], - "rabbitmq": ["testcontainers-rabbitmq"], - "redis": ["testcontainers-redis"], - "selenium": ["testcontainers-selenium"], - }, - python_requires=">=3.7", -) diff --git a/minio/setup.py b/minio/setup.py deleted file mode 100644 index 939257079..000000000 --- a/minio/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "MinIO component of testcontainers-python." - -setup( - name="testcontainers-minio", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "minio", - ], - python_requires=">=3.7", -) diff --git a/arangodb/README.rst b/modules/arangodb/README.rst similarity index 100% rename from arangodb/README.rst rename to modules/arangodb/README.rst diff --git a/arangodb/testcontainers/arangodb/__init__.py b/modules/arangodb/testcontainers/arangodb/__init__.py similarity index 100% rename from arangodb/testcontainers/arangodb/__init__.py rename to modules/arangodb/testcontainers/arangodb/__init__.py diff --git a/arangodb/tests/test_arangodb.py b/modules/arangodb/tests/test_arangodb.py similarity index 100% rename from arangodb/tests/test_arangodb.py rename to modules/arangodb/tests/test_arangodb.py diff --git a/azurite/README.rst b/modules/azurite/README.rst similarity index 100% rename from azurite/README.rst rename to modules/azurite/README.rst diff --git a/azurite/testcontainers/azurite/__init__.py b/modules/azurite/testcontainers/azurite/__init__.py similarity index 100% rename from azurite/testcontainers/azurite/__init__.py rename to modules/azurite/testcontainers/azurite/__init__.py diff --git a/azurite/tests/test_azurite.py b/modules/azurite/tests/test_azurite.py similarity index 100% rename from azurite/tests/test_azurite.py rename to modules/azurite/tests/test_azurite.py diff --git a/clickhouse/README.rst b/modules/clickhouse/README.rst similarity index 100% rename from clickhouse/README.rst rename to modules/clickhouse/README.rst diff --git a/clickhouse/testcontainers/clickhouse/__init__.py b/modules/clickhouse/testcontainers/clickhouse/__init__.py similarity index 100% rename from clickhouse/testcontainers/clickhouse/__init__.py rename to modules/clickhouse/testcontainers/clickhouse/__init__.py diff --git a/clickhouse/tests/test_clickhouse.py b/modules/clickhouse/tests/test_clickhouse.py similarity index 100% rename from clickhouse/tests/test_clickhouse.py rename to modules/clickhouse/tests/test_clickhouse.py diff --git a/elasticsearch/README.rst b/modules/elasticsearch/README.rst similarity index 100% rename from elasticsearch/README.rst rename to modules/elasticsearch/README.rst diff --git a/elasticsearch/testcontainers/elasticsearch/__init__.py b/modules/elasticsearch/testcontainers/elasticsearch/__init__.py similarity index 99% rename from elasticsearch/testcontainers/elasticsearch/__init__.py rename to modules/elasticsearch/testcontainers/elasticsearch/__init__.py index ec9458330..546dd9df1 100644 --- a/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/modules/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -68,7 +68,7 @@ class ElasticSearchContainer(DockerContainer): >>> import urllib >>> from testcontainers.elasticsearch import ElasticSearchContainer - >>> with ElasticSearchContainer(f'elasticsearch:8.3.3') as es: + >>> with ElasticSearchContainer(f'elasticsearch:8.3.3', mem_limit='3G') as es: ... resp = urllib.request.urlopen(es.get_url()) ... json.loads(resp.read().decode())['version']['number'] '8.3.3' diff --git a/elasticsearch/tests/test_elasticsearch.py b/modules/elasticsearch/tests/test_elasticsearch.py similarity index 80% rename from elasticsearch/tests/test_elasticsearch.py rename to modules/elasticsearch/tests/test_elasticsearch.py index 6bbc57fd0..924dfeb88 100644 --- a/elasticsearch/tests/test_elasticsearch.py +++ b/modules/elasticsearch/tests/test_elasticsearch.py @@ -1,5 +1,5 @@ import json -import urllib +import urllib.request import pytest from testcontainers.elasticsearch import ElasticSearchContainer @@ -8,6 +8,6 @@ # The versions below were the current supported versions at time of writing (2022-08-11) @pytest.mark.parametrize('version', ['6.8.23', '7.17.5', '8.3.3']) def test_docker_run_elasticsearch(version): - with ElasticSearchContainer(f'elasticsearch:{version}') as es: + with ElasticSearchContainer(f'elasticsearch:{version}', mem_limit='3G') as es: resp = urllib.request.urlopen(es.get_url()) assert json.loads(resp.read().decode())['version']['number'] == version diff --git a/google/README.rst b/modules/google/README.rst similarity index 100% rename from google/README.rst rename to modules/google/README.rst diff --git a/google/testcontainers/google/__init__.py b/modules/google/testcontainers/google/__init__.py similarity index 100% rename from google/testcontainers/google/__init__.py rename to modules/google/testcontainers/google/__init__.py diff --git a/google/testcontainers/google/pubsub.py b/modules/google/testcontainers/google/pubsub.py similarity index 100% rename from google/testcontainers/google/pubsub.py rename to modules/google/testcontainers/google/pubsub.py diff --git a/google/tests/test_google.py b/modules/google/tests/test_google.py similarity index 100% rename from google/tests/test_google.py rename to modules/google/tests/test_google.py diff --git a/k3s/README.rst b/modules/k3s/README.rst similarity index 100% rename from k3s/README.rst rename to modules/k3s/README.rst diff --git a/k3s/testcontainers/k3s/__init__.py b/modules/k3s/testcontainers/k3s/__init__.py similarity index 100% rename from k3s/testcontainers/k3s/__init__.py rename to modules/k3s/testcontainers/k3s/__init__.py diff --git a/k3s/tests/test_k3s.py b/modules/k3s/tests/test_k3s.py similarity index 100% rename from k3s/tests/test_k3s.py rename to modules/k3s/tests/test_k3s.py diff --git a/kafka/README.rst b/modules/kafka/README.rst similarity index 100% rename from kafka/README.rst rename to modules/kafka/README.rst diff --git a/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py similarity index 100% rename from kafka/testcontainers/kafka/__init__.py rename to modules/kafka/testcontainers/kafka/__init__.py diff --git a/kafka/tests/test_kafka.py b/modules/kafka/tests/test_kafka.py similarity index 100% rename from kafka/tests/test_kafka.py rename to modules/kafka/tests/test_kafka.py diff --git a/keycloak/README.rst b/modules/keycloak/README.rst similarity index 100% rename from keycloak/README.rst rename to modules/keycloak/README.rst diff --git a/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py similarity index 100% rename from keycloak/testcontainers/keycloak/__init__.py rename to modules/keycloak/testcontainers/keycloak/__init__.py diff --git a/keycloak/tests/test_keycloak.py b/modules/keycloak/tests/test_keycloak.py similarity index 100% rename from keycloak/tests/test_keycloak.py rename to modules/keycloak/tests/test_keycloak.py diff --git a/localstack/README.rst b/modules/localstack/README.rst similarity index 100% rename from localstack/README.rst rename to modules/localstack/README.rst diff --git a/localstack/testcontainers/localstack/__init__.py b/modules/localstack/testcontainers/localstack/__init__.py similarity index 100% rename from localstack/testcontainers/localstack/__init__.py rename to modules/localstack/testcontainers/localstack/__init__.py diff --git a/localstack/tests/test_localstack.py b/modules/localstack/tests/test_localstack.py similarity index 100% rename from localstack/tests/test_localstack.py rename to modules/localstack/tests/test_localstack.py diff --git a/minio/README.rst b/modules/minio/README.rst similarity index 100% rename from minio/README.rst rename to modules/minio/README.rst diff --git a/minio/testcontainers/minio/__init__.py b/modules/minio/testcontainers/minio/__init__.py similarity index 100% rename from minio/testcontainers/minio/__init__.py rename to modules/minio/testcontainers/minio/__init__.py diff --git a/minio/tests/test_minio.py b/modules/minio/tests/test_minio.py similarity index 100% rename from minio/tests/test_minio.py rename to modules/minio/tests/test_minio.py diff --git a/mongodb/README.rst b/modules/mongodb/README.rst similarity index 100% rename from mongodb/README.rst rename to modules/mongodb/README.rst diff --git a/mongodb/testcontainers/mongodb/__init__.py b/modules/mongodb/testcontainers/mongodb/__init__.py similarity index 100% rename from mongodb/testcontainers/mongodb/__init__.py rename to modules/mongodb/testcontainers/mongodb/__init__.py diff --git a/mongodb/tests/test_mongodb.py b/modules/mongodb/tests/test_mongodb.py similarity index 100% rename from mongodb/tests/test_mongodb.py rename to modules/mongodb/tests/test_mongodb.py diff --git a/mssql/README.rst b/modules/mssql/README.rst similarity index 100% rename from mssql/README.rst rename to modules/mssql/README.rst diff --git a/mssql/testcontainers/mssql/__init__.py b/modules/mssql/testcontainers/mssql/__init__.py similarity index 100% rename from mssql/testcontainers/mssql/__init__.py rename to modules/mssql/testcontainers/mssql/__init__.py diff --git a/mssql/tests/test_mssql.py b/modules/mssql/tests/test_mssql.py similarity index 100% rename from mssql/tests/test_mssql.py rename to modules/mssql/tests/test_mssql.py diff --git a/mysql/README.rst b/modules/mysql/README.rst similarity index 100% rename from mysql/README.rst rename to modules/mysql/README.rst diff --git a/mysql/testcontainers/mysql/__init__.py b/modules/mysql/testcontainers/mysql/__init__.py similarity index 100% rename from mysql/testcontainers/mysql/__init__.py rename to modules/mysql/testcontainers/mysql/__init__.py diff --git a/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py similarity index 100% rename from mysql/tests/test_mysql.py rename to modules/mysql/tests/test_mysql.py diff --git a/neo4j/README.rst b/modules/neo4j/README.rst similarity index 100% rename from neo4j/README.rst rename to modules/neo4j/README.rst diff --git a/neo4j/testcontainers/neo4j/__init__.py b/modules/neo4j/testcontainers/neo4j/__init__.py similarity index 100% rename from neo4j/testcontainers/neo4j/__init__.py rename to modules/neo4j/testcontainers/neo4j/__init__.py diff --git a/neo4j/tests/test_neo4j.py b/modules/neo4j/tests/test_neo4j.py similarity index 100% rename from neo4j/tests/test_neo4j.py rename to modules/neo4j/tests/test_neo4j.py diff --git a/nginx/README.rst b/modules/nginx/README.rst similarity index 100% rename from nginx/README.rst rename to modules/nginx/README.rst diff --git a/nginx/testcontainers/nginx/__init__.py b/modules/nginx/testcontainers/nginx/__init__.py similarity index 100% rename from nginx/testcontainers/nginx/__init__.py rename to modules/nginx/testcontainers/nginx/__init__.py diff --git a/nginx/tests/test_nginx.py b/modules/nginx/tests/test_nginx.py similarity index 100% rename from nginx/tests/test_nginx.py rename to modules/nginx/tests/test_nginx.py diff --git a/opensearch/README.rst b/modules/opensearch/README.rst similarity index 100% rename from opensearch/README.rst rename to modules/opensearch/README.rst diff --git a/opensearch/testcontainers/opensearch/__init__.py b/modules/opensearch/testcontainers/opensearch/__init__.py similarity index 95% rename from opensearch/testcontainers/opensearch/__init__.py rename to modules/opensearch/testcontainers/opensearch/__init__.py index 0422b9361..567ba264d 100644 --- a/opensearch/testcontainers/opensearch/__init__.py +++ b/modules/opensearch/testcontainers/opensearch/__init__.py @@ -1,5 +1,6 @@ from opensearchpy import OpenSearch from opensearchpy.exceptions import ConnectionError, TransportError +from urllib3.exceptions import ProtocolError from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -84,7 +85,12 @@ def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: **kwargs, ) - @wait_container_is_ready(ConnectionError, TransportError) + @wait_container_is_ready( + ConnectionError, + TransportError, + ProtocolError, + ConnectionResetError + ) def _healthcheck(self) -> None: """This is an internal method used to check if the OpenSearch container is healthy and ready to receive requests.""" diff --git a/opensearch/tests/test_opensearch.py b/modules/opensearch/tests/test_opensearch.py similarity index 100% rename from opensearch/tests/test_opensearch.py rename to modules/opensearch/tests/test_opensearch.py diff --git a/oracle/README.rst b/modules/oracle/README.rst similarity index 100% rename from oracle/README.rst rename to modules/oracle/README.rst diff --git a/oracle/testcontainers/oracle/__init__.py b/modules/oracle/testcontainers/oracle/__init__.py similarity index 100% rename from oracle/testcontainers/oracle/__init__.py rename to modules/oracle/testcontainers/oracle/__init__.py diff --git a/oracle/tests/test_oracle.py b/modules/oracle/tests/test_oracle.py similarity index 100% rename from oracle/tests/test_oracle.py rename to modules/oracle/tests/test_oracle.py diff --git a/postgres/README.rst b/modules/postgres/README.rst similarity index 100% rename from postgres/README.rst rename to modules/postgres/README.rst diff --git a/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py similarity index 100% rename from postgres/testcontainers/postgres/__init__.py rename to modules/postgres/testcontainers/postgres/__init__.py diff --git a/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py similarity index 100% rename from postgres/tests/test_postgres.py rename to modules/postgres/tests/test_postgres.py diff --git a/rabbitmq/README.rst b/modules/rabbitmq/README.rst similarity index 100% rename from rabbitmq/README.rst rename to modules/rabbitmq/README.rst diff --git a/rabbitmq/testcontainers/rabbitmq/__init__.py b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py similarity index 100% rename from rabbitmq/testcontainers/rabbitmq/__init__.py rename to modules/rabbitmq/testcontainers/rabbitmq/__init__.py diff --git a/rabbitmq/tests/test_rabbitmq.py b/modules/rabbitmq/tests/test_rabbitmq.py similarity index 100% rename from rabbitmq/tests/test_rabbitmq.py rename to modules/rabbitmq/tests/test_rabbitmq.py diff --git a/redis/README.rst b/modules/redis/README.rst similarity index 100% rename from redis/README.rst rename to modules/redis/README.rst diff --git a/redis/testcontainers/redis/__init__.py b/modules/redis/testcontainers/redis/__init__.py similarity index 100% rename from redis/testcontainers/redis/__init__.py rename to modules/redis/testcontainers/redis/__init__.py diff --git a/redis/tests/test_redis.py b/modules/redis/tests/test_redis.py similarity index 100% rename from redis/tests/test_redis.py rename to modules/redis/tests/test_redis.py diff --git a/selenium/README.rst b/modules/selenium/README.rst similarity index 100% rename from selenium/README.rst rename to modules/selenium/README.rst diff --git a/selenium/testcontainers/selenium/__init__.py b/modules/selenium/testcontainers/selenium/__init__.py similarity index 100% rename from selenium/testcontainers/selenium/__init__.py rename to modules/selenium/testcontainers/selenium/__init__.py diff --git a/selenium/tests/test_selenium.py b/modules/selenium/tests/test_selenium.py similarity index 100% rename from selenium/tests/test_selenium.py rename to modules/selenium/tests/test_selenium.py diff --git a/mongodb/setup.py b/mongodb/setup.py deleted file mode 100644 index 0f8966c6a..000000000 --- a/mongodb/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "MongoDB component of testcontainers-python." - -setup( - name="testcontainers-mongodb", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "pymongo", - ], - python_requires=">=3.7", -) diff --git a/mssql/setup.py b/mssql/setup.py deleted file mode 100644 index c1fd74855..000000000 --- a/mssql/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Microsoft SQL Server component of testcontainers-python." - -setup( - name="testcontainers-mssql", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "sqlalchemy", - "pymssql", - ], - python_requires=">=3.7", -) diff --git a/mysql/setup.py b/mysql/setup.py deleted file mode 100644 index c09182e4c..000000000 --- a/mysql/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "MySQL component of testcontainers-python." - -setup( - name="testcontainers-mysql", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "sqlalchemy", - "pymysql[rsa]" - ], - python_requires=">=3.7", -) diff --git a/neo4j/setup.py b/neo4j/setup.py deleted file mode 100644 index ec2c30bb3..000000000 --- a/neo4j/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Neo4j component of testcontainers-python." - -setup( - name="testcontainers-neo4j", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "neo4j", - ], - python_requires=">=3.7", -) diff --git a/nginx/setup.py b/nginx/setup.py deleted file mode 100644 index bb24ba3ce..000000000 --- a/nginx/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "NGINX component of testcontainers-python." - -setup( - name="testcontainers-nginx", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - ], - python_requires=">=3.7", -) diff --git a/opensearch/setup.py b/opensearch/setup.py deleted file mode 100644 index 1e3db8c76..000000000 --- a/opensearch/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "OpenSearch component of testcontainers-python." - -setup( - name="testcontainers-opensearch", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "opensearch-py", - ], - python_requires=">=3.7", -) diff --git a/oracle/setup.py b/oracle/setup.py deleted file mode 100644 index 0a6fd4e26..000000000 --- a/oracle/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Oracle component of testcontainers-python." - -setup( - name="testcontainers-oracle", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "sqlalchemy", - "cx_Oracle", - ], - python_requires=">=3.7", -) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..c69c14480 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2998 @@ +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = true +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +optional = false +python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = true +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "azure-core" +version = "1.29.7" +description = "Microsoft Azure Core Library for Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "azure-core-1.29.7.tar.gz", hash = "sha256:2944faf1a7ff1558b1f457cabf60f279869cabaeef86b353bed8eb032c7d8c5e"}, + {file = "azure_core-1.29.7-py3-none-any.whl", hash = "sha256:95a7b41b4af102e5fcdfac9500fcc82ff86e936c7145a099b7848b9ac0501250"}, +] + +[package.dependencies] +requests = ">=2.21.0" +six = ">=1.11.0" +typing-extensions = ">=4.6.0" + +[package.extras] +aio = ["aiohttp (>=3.0)"] + +[[package]] +name = "azure-storage-blob" +version = "12.19.0" +description = "Microsoft Azure Blob Storage Client Library for Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "azure-storage-blob-12.19.0.tar.gz", hash = "sha256:26c0a4320a34a3c2a1b74528ba6812ebcb632a04cd67b1c7377232c4b01a5897"}, + {file = "azure_storage_blob-12.19.0-py3-none-any.whl", hash = "sha256:7bbc2c9c16678f7a420367fef6b172ba8730a7e66df7f4d7a55d5b3c8216615b"}, +] + +[package.dependencies] +azure-core = ">=1.28.0,<2.0.0" +cryptography = ">=2.1.4" +isodate = ">=0.6.1" +typing-extensions = ">=4.3.0" + +[package.extras] +aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] + +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "boto3" +version = "1.34.28" +description = "The AWS SDK for Python" +optional = true +python-versions = ">= 3.8" +files = [ + {file = "boto3-1.34.28-py3-none-any.whl", hash = "sha256:fb56622ce195c06ae0d15ae9472d44529362a869ad52862a5a28b891530969f9"}, + {file = "boto3-1.34.28.tar.gz", hash = "sha256:9e0dcca7bb0567f7b4b84d1d26c19b217abfe149d19106af7f120f09142688cf"}, +] + +[package.dependencies] +botocore = ">=1.34.28,<1.35.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.10.0,<0.11.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.34.28" +description = "Low-level, data-driven core of boto 3." +optional = true +python-versions = ">= 3.8" +files = [ + {file = "botocore-1.34.28-py3-none-any.whl", hash = "sha256:03be8209257ab65f3c8be7377cf8d38bff6a6afbe3d36c72924e48959bb694dc"}, + {file = "botocore-1.34.28.tar.gz", hash = "sha256:45c99ccc6389ab1a87e996a7cc8797c7e41d5ecd9a5757d567ba3a57cb7655e7"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = [ + {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, +] + +[package.extras] +crt = ["awscrt (==0.19.19)"] + +[[package]] +name = "cachetools" +version = "5.3.2" +description = "Extensible memoizing collections and decorators" +optional = true +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "clickhouse-driver" +version = "0.2.6" +description = "Python driver with native interface for ClickHouse" +optional = true +python-versions = ">=3.7, <4" +files = [ + {file = "clickhouse-driver-0.2.6.tar.gz", hash = "sha256:028baf4d65a0b3f9e0ac5df248cab20657b51adbfce6c5427aa6c16a7318dda1"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e61d975081b74cae9efe7a64b1de1a8aec5643affb81b57487dcae7d195f250f"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee0395b49bd8c0cd3dca6b3a4b9db347c1d300de83ee7b4f482a9d48b6c7af54"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d8aff4d3f0d78fd4b11e28ef344a5ee71d6850fef4a79e3265e0728b4d1d89"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b086bd658889af10205cb8307b714c8202bdfd05a4833fc7f4f82df2d88a963"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a1b6815d56a03398110c7f602a87ad767ecfd7a0869e61f2d8bfa0779dce2b"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36146fc3782a9e45a57c1094f3f8051db4117089502a3310312768dd7e14ef6d"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4032fb9b15ddbc484073ca165e5271eed494f3f3c4e8cb3a495bbc7a151fa556"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2e0332b4e4b68be0d5e97ee40cd3ce7f4f85523e3ea3656b4dda658ba723067"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7b659583fa2a8058dfeba952c0f17d5077b2af17db9c0f45ab8a5f9cf4dc1523"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d4ce06dc2d2593bedade4bf369c28d7b0494532774e849f7213f800b06a274a2"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3bf9071ca89f661ae7dd46f2561e7e97fe71fc96dbbaf0607afe636f173e5f40"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:429d9c16355aea62a462f75f373a2d9c4b76da7996a0eb6f038b2aa079020597"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1960244de84d7888598180e69689d1ba7ec6c9c99cd2c080a76315a7a29a5cab"}, + {file = "clickhouse_driver-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:5a6bdfde4e2fb81414200303950ba75c3f7ee9249e4a997854ce18e1cb4beea9"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef0a9453a972ec32399cc93a510aec33fa4b9b1f0c5050a3a40e5d298a89a7aa"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:95c13374741a8749980436603922ad7c476ae3b5e17850c50faba3879db66bdb"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d715d392eaadb43ded1c104354aebbc233f69bbf3919aa61beb7cc6ecdaa950a"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d75d50265616a2779d0b2acaebf7253783e2b8ad0df3efa6d23f0db1c9bf50"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32f0e02c28e9a6f1c1f116d1aa14772e73beb7efd4f30490d9f171d39b40551a"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08037080bd7d1d2816767d1bc693380073ce8bdf4ad0f62871c12b77b90323a5"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfe60ec8b695c298e6156c71a35ae6586676992cdfde6d2bf0c0b74414bfa0c"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:36ae6e627ffba6ed46fa9ac4dd745bfbfd5d9f39b198f46051ebfd0dde5e01c2"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2508638aa6f44cc0653840b99e308d3c0a8684c71e3132d7d067b160bdff5a81"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:88b77719e62aaa03a9d2d05e395788c4c112d41ff35a6756e7e7a1ac5dc1b4fb"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:95d1206252c6c9d0abad61310eda455ebcdcd0b1f41c3584daffbfd52b68654f"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3bdff826074af1b339fe9bff17844f6b8117080f895b8601f536b13a9d04f82a"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-win32.whl", hash = "sha256:c8c02606eabe4288045bbba497088b7fe976c34330c1066db9744fa09fef4a2a"}, + {file = "clickhouse_driver-0.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:44df94940739a72a02716bb14ac8b683aef84b54b05783d96201ff334bcd88fb"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:079708ac620343736c2c8dace6663178156f4ded47bf25245b56147498d0d7de"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e13369cf516df6c33c156fe66cfff502f66fc25f2a515c761ed1480fc83b3aa9"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbc0bf957fc6d0163ee06ac02275bdb2f40d109fc225366e387358e78d968a43"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f58b0ffb434fefe99b7419e09d6071a49773e9eb49c5ebeedf7c3180b40c2330"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0746dac9aa5cf2c275187aef16b67ae922ef257c82671948a6be86e19ee9cb2"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f1ce40c9a2715ea44be9a5c33cb5b08048c1ef5595a6739443473e4ba23fedf"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9499a2b2d5e856c7e8efd28da479df8a962e2497c70bf5e2d9a25875d520465"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8b2e849bb7102365a480d9d1083ed203a244f0c02a0fc973eab6078b3d14638d"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5846c50e2dfe0ce2f300275955a20f82422b1128b09ab5a9ea4d8a00d4ba8438"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a12990b54b92b2a2598f144388e766d6261492408f2434738fe649423371894b"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:af14a5699fea890a1f8f022c624ca9f61994e15913cfaf4e0e58b1e4ac99540a"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:965fb8370eb7ee8a20cdf54d7c2fe024f587da692bd15e94dd2eee93a3c88f4b"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-win32.whl", hash = "sha256:9c552205d2b6125a99121080417c5c7bbc47af81ed15bb5ff9be464fed96bb68"}, + {file = "clickhouse_driver-0.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:a58fb8b12a32d58ce0c72839293ec5bacc7904f3db36a82bb963f394dbb5f230"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f2a9abb8b1464985f7a480f956744736e611970ffc8ffd3eb0b46343a3a691e6"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e01696c450a2de41d586689dbaed0893d4de7469811abd3bf831a0483e723a"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0bb85760dabbef493aec985ad94612132ddeb5b81569cf0a7222f6cb7278eda"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26fa7c46a65beb6725e9d77701ed2871c8b3b7fd0c187c3c8550ae95e9886038"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c489cd1cf8b98f78e95559122c5b0d52f25b619cfd2ca31d0784a0bea38940b4"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af20ef3ddc7f834ebb316c349770f5c381354b573419523b9703ea48ed4bd692"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82538f76640cbd22540f9de301d996b1e48dbf5de71a79fc06826ea094c8e5f7"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ccd34a5592f4212483138bea45dc6526c4cf7b5aa4b806f422b66d27232f7271"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:ab10bc9db2fbc0d5ab785c7771bfaac526ac6724b8727c2f0708caee878a6a48"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c9882ca5fd98b1801a283889e085e88c929fae1b68adc4e6b00ef1cf60adb843"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:051a1bd0f70a8d0bc11ef90b6e6291981cd8e3031cc126a7c78206849c1b8cf7"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:48f47694d5e54af192a4aa2a24f947795c362ab40a253d088593880fede97568"}, + {file = "clickhouse_driver-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b783e5d3d12947c73d991bceb6b8765231512ab0ac6363823cdcd2c283c67a99"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d24e0acf8fef1d787851ae048e0168b2fb10297c3235cbb87974f78db37d3d3"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27dc025f10a930aed453eec5ed9a0404e7b2db671da4a253109facf5c1ad1b4c"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d534c744b1b211241f8c58d2ad5fcfc465a0503011d9b9073c00e25507abcbf3"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:858b8039a1241591b63f368de9dbdef6c4e6466b6bf0e01d53d36f7091af7569"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21eb62e1de7d2d5483d121d1447e857030bf866d4f23572b0dedc515f9359cd0"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eddbc4e90da8d3f08b5aa6c58a7155ebb398cc34255083e7103071b4c4a76952"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f67ac3bf4cec39c051b33152bc1f370a3f0311774c73965727e14877e314fd"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:231ef65b7c99f2e937990b7072cf68bda6b51f45c903e5c8c068f7d754bdd489"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f4aadf85dc199f3d1ef06b961c87b168d009c88bfe431b4821460678d4ad51a9"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f08bc819a0c17b787c2984406611f5f2d9a8e33118090376c4bc8d932f38ea10"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ace862809bb89f896c2150e20ea6bbeb969c25ca40bfe389179469b0e0ec5dd7"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:512d2d5714e811dfc63eefdf5e0171f24a5e698134e684d8efe27c001fc3a06b"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-win32.whl", hash = "sha256:b9b775f70371a7333ac828fe2bbd9473c94e18728ac6b70b2865cdee1f0d551f"}, + {file = "clickhouse_driver-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:d13fe44620750abcd4c93c067d6e44c8a1ea050856c4c27a5633ad8ff197a689"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e17995752eef4f742976abab03ff3f5b81edb9b9218b151abaf3534055fcf2b8"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c23baf4b4185b3ee13332c05c201e242600e35deb8b0b0d95211e71d5eb3f59"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbd7e3e33d2bc5f32da2557e97299340a722f948790494a2e9efaed4635ff499"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c872e9fee17d278816fc30b4df4b10bedd8eec9efaa614c71725f147b00b30d"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c8af2761676cd306962a86cc87a4187efcfdaf253a0d908c8f8ef791277a7fe"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7ee63cf9443a94a0bc856ac947c9bfd8c214c12e340846f341391bb161cc4e"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3117c1bbcbec64a39c283ab2ff1ca284e57d5943b8e68e6f1df718ab04cb66e2"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9079621484b1f10017a65f7f84d81b13e44e9f23c5da1e04731405531bb63d58"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:aa73edc701635c5042c0355c8a0222eb03af117bebaf898017958d0e2cefb3b8"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:b5d9afcdec1fcc4e675fd25d31cd506b369efddc78d5e775804cfc911c773551"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6ad658e12e7c928283eb47f82fe4d36c8974918aabad1b3981212813fe21d03b"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82fdc618428b31c418e6cdedfd66bd50b7662e2c092f685438bbe77e7f295f57"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-win32.whl", hash = "sha256:1736793aa273ebb71895eaa77ae4ab8ce361a28fc1cd5d92247f7af22a030c07"}, + {file = "clickhouse_driver-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:0efc58bf8b21a84b68bbba083702dc17cab5255d2552e73dacadc830b612bd38"}, + {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5fdf175283918748e4821797e21cc0c91c44803e92698bd66f206769fb18da73"}, + {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82c4145bf1531e4a508e187e5175e9c4d3749de5d98643141a348464360b8076"}, + {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69c30943915b2ea794b8a85b8c2f6aa17dbf19a03cae1bc541c49b024f861200"}, + {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45cfa76e7dadf097e77190dc27c23361cf2806ad646df979c657336e667af03e"}, + {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d59c21e2d11e8e226ab1420a928f34be958301781dabc0176a8ae6e4d6dfa5b5"}, + {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07ab99b84510a88af3358a35deafa09942bdc26ad8213576af3f723e0cc11bb0"}, + {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712f9c313513898de98ac31e63aa2c0186f632d0490d6f2d010f259f35b9bf05"}, + {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f2cb2fc81ee5c44068dd1d15a052a092cd6008de16d7e7850b1da7e29f316e"}, + {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4bd0bf5fc48bf6317698714721fe5127c6ebc0d8ebd0ea14217bfd7d617303f5"}, + {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b52e08b5ccf3a8ceca3727d0b4594c88e5b7876d5a17451d61ed78b158ada843"}, + {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0081a4ac2fdb940c12dd74dd835323dfcda1e3df7cf178d9174f928fd28c1cfd"}, + {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27eeb4f3cf5403dd2e8c6871c25dc129e5fc3dc6fb4ea125cd755be6476c6ff1"}, + {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1ab6ce26455e4db46431fa75c6d6913e0ef91ac54ec8554ef9455e32d0090bb"}, + {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36143db3bee3f16cc98ecda6df0110a5c6c479c69ec99fb2bb904a8f8139b64d"}, + {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9fc3eacdcbc2ce5ca967c0e8dc74f2238fe9fd1bced50ea355580eebcc800dfd"}, +] + +[package.dependencies] +pytz = "*" +tzlocal = "*" + +[package.extras] +lz4 = ["clickhouse-cityhash (>=1.0.2.1)", "lz4", "lz4 (<=3.0.1)"] +numpy = ["numpy (>=1.12.0)", "pandas (>=0.24.0)"] +zstd = ["clickhouse-cityhash (>=1.0.2.1)", "zstd"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.4.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, + {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, + {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, + {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, + {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, + {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, + {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, + {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, + {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, + {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, + {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, + {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, + {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "42.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, + {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, + {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, + {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, + {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, + {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, + {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cx-oracle" +version = "8.3.0" +description = "Python interface to Oracle" +optional = true +python-versions = "*" +files = [ + {file = "cx_Oracle-8.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6a23da225f03f50a81980c61dbd6a358c3575f212ca7f4c22bb65a9faf94f7f"}, + {file = "cx_Oracle-8.3.0-cp310-cp310-win32.whl", hash = "sha256:715a8bbda5982af484ded14d184304cc552c1096c82471dd2948298470e88a04"}, + {file = "cx_Oracle-8.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07f01608dfb6603a8f2a868fc7c7bdc951480f187df8dbc50f4d48c884874e6a"}, + {file = "cx_Oracle-8.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4b3afe7a911cebaceda908228d36839f6441cbd38e5df491ec25960562bb01a0"}, + {file = "cx_Oracle-8.3.0-cp36-cp36m-win32.whl", hash = "sha256:076ffb71279d6b2dcbf7df028f62a01e18ce5bb73d8b01eab582bf14a62f4a61"}, + {file = "cx_Oracle-8.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b82e4b165ffd807a2bd256259a6b81b0a2452883d39f987509e2292d494ea163"}, + {file = "cx_Oracle-8.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b902db61dcdcbbf8dd981f5a46d72fef40c5150c7fc0eb0f0698b462d6eb834e"}, + {file = "cx_Oracle-8.3.0-cp37-cp37m-win32.whl", hash = "sha256:4c82ca74442c298ceec56d207450c192e06ecf8ad52eb4aaad0812e147ceabf7"}, + {file = "cx_Oracle-8.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:54164974d526b76fdefb0b66a42b68e1fca5df78713d0eeb8c1d0047b83f6bcf"}, + {file = "cx_Oracle-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:410747d542e5f94727f5f0e42e9706c772cf9094fb348ce965ab88b3a9e4d2d8"}, + {file = "cx_Oracle-8.3.0-cp38-cp38-win32.whl", hash = "sha256:3baa878597c5fadb2c72f359f548431c7be001e722ce4a4ebdf3d2293a1bb70b"}, + {file = "cx_Oracle-8.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:de42bdc882abdc5cea54597da27a05593b44143728e5b629ad5d35decb1a2036"}, + {file = "cx_Oracle-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df412238a9948340591beee9ec64fa62a2efacc0d91107034a7023e2991fba97"}, + {file = "cx_Oracle-8.3.0-cp39-cp39-win32.whl", hash = "sha256:70d3cf030aefd71f99b45beba77237b2af448adf5e26be0db3d0d3dee6ea4230"}, + {file = "cx_Oracle-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf01ce87edb4ef663b2e5bd604e1e0154d2cc2f12b60301f788b569d9db8a900"}, + {file = "cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9"}, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +description = "A library to handle automated deprecations" +optional = true +python-versions = "*" +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + +[package.dependencies] +packaging = "*" + +[[package]] +name = "dnspython" +version = "2.5.0" +description = "DNS toolkit" +optional = true +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, + {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1)"] +trio = ["trio (>=0.14)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "docker" +version = "7.0.0" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "docker-7.0.0-py3-none-any.whl", hash = "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b"}, + {file = "docker-7.0.0.tar.gz", hash = "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3"}, +] + +[package.dependencies] +packaging = ">=14.0" +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" + +[package.extras] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +optional = true +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "google-api-core" +version = "2.15.0" +description = "Google API client core library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.15.0.tar.gz", hash = "sha256:abc978a72658f14a2df1e5e12532effe40f94f868f6e23d95133bd6abcca35ca"}, + {file = "google_api_core-2.15.0-py3-none-any.whl", hash = "sha256:2aa56d2be495551e66bbff7f729b790546f87d5c90e74781aa77233bcb395a8a"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +grpcio = [ + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, +] +grpcio-status = [ + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, +] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-auth" +version = "2.27.0" +description = "Google Authentication Library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.27.0.tar.gz", hash = "sha256:e863a56ccc2d8efa83df7a80272601e43487fa9a728a376205c86c26aaefa821"}, + {file = "google_auth-2.27.0-py2.py3-none-any.whl", hash = "sha256:8e4bad367015430ff253fe49d500fdc3396c1a434db5740828c728e45bcce245"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-cloud-pubsub" +version = "2.19.0" +description = "Google Cloud Pub/Sub API client library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-cloud-pubsub-2.19.0.tar.gz", hash = "sha256:6a98c33f7eb5f7de2ae52efa059b2b5f75b2ccd9f0f11f2edcefdda8d14e425c"}, + {file = "google_cloud_pubsub-2.19.0-py2.py3-none-any.whl", hash = "sha256:0cc444e5b2220a703106668829315a724cfb4304d6772725035993bb2fc81388"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" +grpcio = ">=1.51.3,<2.0dev" +grpcio-status = ">=1.33.2" +proto-plus = [ + {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, +] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" + +[package.extras] +libcst = ["libcst (>=0.3.10)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.62.0" +description = "Common protobufs used in Google APIs" +optional = true +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, +] + +[package.dependencies] +grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = true +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.13.0" +description = "IAM API client library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "grpc-google-iam-v1-0.13.0.tar.gz", hash = "sha256:fad318608b9e093258fbf12529180f400d1c44453698a33509cc6ecf005b294e"}, + {file = "grpc_google_iam_v1-0.13.0-py2.py3-none-any.whl", hash = "sha256:53902e2af7de8df8c1bd91373d9be55b0743ec267a7428ea638db3775becae89"}, +] + +[package.dependencies] +googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} +grpcio = ">=1.44.0,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" + +[[package]] +name = "grpcio" +version = "1.60.0" +description = "HTTP/2-based RPC framework" +optional = true +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.60.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:d020cfa595d1f8f5c6b343530cd3ca16ae5aefdd1e832b777f9f0eb105f5b139"}, + {file = "grpcio-1.60.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b98f43fcdb16172dec5f4b49f2fece4b16a99fd284d81c6bbac1b3b69fcbe0ff"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:20e7a4f7ded59097c84059d28230907cd97130fa74f4a8bfd1d8e5ba18c81491"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452ca5b4afed30e7274445dd9b441a35ece656ec1600b77fff8c216fdf07df43"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43e636dc2ce9ece583b3e2ca41df5c983f4302eabc6d5f9cd04f0562ee8ec1ae"}, + {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e306b97966369b889985a562ede9d99180def39ad42c8014628dd3cc343f508"}, + {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f897c3b127532e6befdcf961c415c97f320d45614daf84deba0a54e64ea2457b"}, + {file = "grpcio-1.60.0-cp310-cp310-win32.whl", hash = "sha256:b87efe4a380887425bb15f220079aa8336276398dc33fce38c64d278164f963d"}, + {file = "grpcio-1.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:a9c7b71211f066908e518a2ef7a5e211670761651039f0d6a80d8d40054047df"}, + {file = "grpcio-1.60.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:fb464479934778d7cc5baf463d959d361954d6533ad34c3a4f1d267e86ee25fd"}, + {file = "grpcio-1.60.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:4b44d7e39964e808b071714666a812049765b26b3ea48c4434a3b317bac82f14"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:90bdd76b3f04bdb21de5398b8a7c629676c81dfac290f5f19883857e9371d28c"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91229d7203f1ef0ab420c9b53fe2ca5c1fbeb34f69b3bc1b5089466237a4a134"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b36a2c6d4920ba88fa98075fdd58ff94ebeb8acc1215ae07d01a418af4c0253"}, + {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:297eef542156d6b15174a1231c2493ea9ea54af8d016b8ca7d5d9cc65cfcc444"}, + {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:87c9224acba0ad8bacddf427a1c2772e17ce50b3042a789547af27099c5f751d"}, + {file = "grpcio-1.60.0-cp311-cp311-win32.whl", hash = "sha256:95ae3e8e2c1b9bf671817f86f155c5da7d49a2289c5cf27a319458c3e025c320"}, + {file = "grpcio-1.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:467a7d31554892eed2aa6c2d47ded1079fc40ea0b9601d9f79204afa8902274b"}, + {file = "grpcio-1.60.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:a7152fa6e597c20cb97923407cf0934e14224af42c2b8d915f48bc3ad2d9ac18"}, + {file = "grpcio-1.60.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:7db16dd4ea1b05ada504f08d0dca1cd9b926bed3770f50e715d087c6f00ad748"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:b0571a5aef36ba9177e262dc88a9240c866d903a62799e44fd4aae3f9a2ec17e"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fd9584bf1bccdfff1512719316efa77be235469e1e3295dce64538c4773840b"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6a478581b1a1a8fdf3318ecb5f4d0cda41cacdffe2b527c23707c9c1b8fdb55"}, + {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:77c8a317f0fd5a0a2be8ed5cbe5341537d5c00bb79b3bb27ba7c5378ba77dbca"}, + {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1c30bb23a41df95109db130a6cc1b974844300ae2e5d68dd4947aacba5985aa5"}, + {file = "grpcio-1.60.0-cp312-cp312-win32.whl", hash = "sha256:2aef56e85901c2397bd557c5ba514f84de1f0ae5dd132f5d5fed042858115951"}, + {file = "grpcio-1.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:e381fe0c2aa6c03b056ad8f52f8efca7be29fb4d9ae2f8873520843b6039612a"}, + {file = "grpcio-1.60.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:92f88ca1b956eb8427a11bb8b4a0c0b2b03377235fc5102cb05e533b8693a415"}, + {file = "grpcio-1.60.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:e278eafb406f7e1b1b637c2cf51d3ad45883bb5bd1ca56bc05e4fc135dfdaa65"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:a48edde788b99214613e440fce495bbe2b1e142a7f214cce9e0832146c41e324"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de2ad69c9a094bf37c1102b5744c9aec6cf74d2b635558b779085d0263166454"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:073f959c6f570797272f4ee9464a9997eaf1e98c27cb680225b82b53390d61e6"}, + {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c826f93050c73e7769806f92e601e0efdb83ec8d7c76ddf45d514fee54e8e619"}, + {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e30be89a75ee66aec7f9e60086fadb37ff8c0ba49a022887c28c134341f7179"}, + {file = "grpcio-1.60.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b0fb2d4801546598ac5cd18e3ec79c1a9af8b8f2a86283c55a5337c5aeca4b1b"}, + {file = "grpcio-1.60.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:9073513ec380434eb8d21970e1ab3161041de121f4018bbed3146839451a6d8e"}, + {file = "grpcio-1.60.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:74d7d9fa97809c5b892449b28a65ec2bfa458a4735ddad46074f9f7d9550ad13"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:1434ca77d6fed4ea312901122dc8da6c4389738bf5788f43efb19a838ac03ead"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e61e76020e0c332a98290323ecfec721c9544f5b739fab925b6e8cbe1944cf19"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675997222f2e2f22928fbba640824aebd43791116034f62006e19730715166c0"}, + {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5208a57eae445ae84a219dfd8b56e04313445d146873117b5fa75f3245bc1390"}, + {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:428d699c8553c27e98f4d29fdc0f0edc50e9a8a7590bfd294d2edb0da7be3629"}, + {file = "grpcio-1.60.0-cp38-cp38-win32.whl", hash = "sha256:83f2292ae292ed5a47cdcb9821039ca8e88902923198f2193f13959360c01860"}, + {file = "grpcio-1.60.0-cp38-cp38-win_amd64.whl", hash = "sha256:705a68a973c4c76db5d369ed573fec3367d7d196673fa86614b33d8c8e9ebb08"}, + {file = "grpcio-1.60.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c193109ca4070cdcaa6eff00fdb5a56233dc7610216d58fb81638f89f02e4968"}, + {file = "grpcio-1.60.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:676e4a44e740deaba0f4d95ba1d8c5c89a2fcc43d02c39f69450b1fa19d39590"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5ff21e000ff2f658430bde5288cb1ac440ff15c0d7d18b5fb222f941b46cb0d2"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c86343cf9ff7b2514dd229bdd88ebba760bd8973dac192ae687ff75e39ebfab"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fd3b3968ffe7643144580f260f04d39d869fcc2cddb745deef078b09fd2b328"}, + {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:30943b9530fe3620e3b195c03130396cd0ee3a0d10a66c1bee715d1819001eaf"}, + {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b10241250cb77657ab315270b064a6c7f1add58af94befa20687e7c8d8603ae6"}, + {file = "grpcio-1.60.0-cp39-cp39-win32.whl", hash = "sha256:79a050889eb8d57a93ed21d9585bb63fca881666fc709f5d9f7f9372f5e7fd03"}, + {file = "grpcio-1.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a97a681e82bc11a42d4372fe57898d270a2707f36c45c6676e49ce0d5c41353"}, + {file = "grpcio-1.60.0.tar.gz", hash = "sha256:2199165a1affb666aa24adf0c97436686d0a61bc5fc113c037701fb7c7fceb96"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.60.0)"] + +[[package]] +name = "grpcio-status" +version = "1.60.0" +description = "Status proto mapping for gRPC" +optional = true +python-versions = ">=3.6" +files = [ + {file = "grpcio-status-1.60.0.tar.gz", hash = "sha256:f10e0b6db3adc0fdc244b71962814ee982996ef06186446b5695b9fa635aa1ab"}, + {file = "grpcio_status-1.60.0-py3-none-any.whl", hash = "sha256:7d383fa36e59c1e61d380d91350badd4d12ac56e4de2c2b831b050362c3c572e"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.60.0" +protobuf = ">=4.21.6" + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = true +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.0.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = true +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "jaraco-classes" +version = "3.3.1" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.3.1-py3-none-any.whl", hash = "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206"}, + {file = "jaraco.classes-3.3.1.tar.gz", hash = "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = true +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "kafka-python" +version = "2.0.2" +description = "Pure Python client for Apache Kafka" +optional = true +python-versions = "*" +files = [ + {file = "kafka-python-2.0.2.tar.gz", hash = "sha256:04dfe7fea2b63726cd6f3e79a2d86e709d608d74406638c5da33a01d45a9d7e3"}, + {file = "kafka_python-2.0.2-py2.py3-none-any.whl", hash = "sha256:2d92418c7cb1c298fa6c7f0fb3519b520d0d7526ac6cb7ae2a4fc65a51a94b6e"}, +] + +[package.extras] +crc32c = ["crc32c"] + +[[package]] +name = "keyring" +version = "24.3.0" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, + {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab (>=1.1.0)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[[package]] +name = "kubernetes" +version = "29.0.0" +description = "Kubernetes python client" +optional = true +python-versions = ">=3.6" +files = [ + {file = "kubernetes-29.0.0-py2.py3-none-any.whl", hash = "sha256:ab8cb0e0576ccdfb71886366efb102c6a20f268d817be065ce7f9909c631e43e"}, + {file = "kubernetes-29.0.0.tar.gz", hash = "sha256:c4812e227ae74d07d53c88293e564e54b850452715a59a927e7e1bc6b9a60459"}, +] + +[package.dependencies] +certifi = ">=14.05.14" +google-auth = ">=1.0.1" +oauthlib = ">=3.2.2" +python-dateutil = ">=2.5.3" +pyyaml = ">=5.4.1" +requests = "*" +requests-oauthlib = "*" +six = ">=1.9.0" +urllib3 = ">=1.24.2" +websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" + +[package.extras] +adal = ["adal (>=1.0.2)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.4" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "minio" +version = "7.2.3" +description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" +optional = true +python-versions = "*" +files = [ + {file = "minio-7.2.3-py3-none-any.whl", hash = "sha256:e6b5ce0a9b4368da50118c3f0c4df5dbf33885d44d77fce6c0aa1c485e6af7a1"}, + {file = "minio-7.2.3.tar.gz", hash = "sha256:4971dfb1a71eeefd38e1ce2dc7edc4e6eb0f07f1c1d6d70c15457e3280cfc4b9"}, +] + +[package.dependencies] +argon2-cffi = "*" +certifi = "*" +pycryptodome = "*" +typing-extensions = "*" +urllib3 = "*" + +[[package]] +name = "more-itertools" +version = "10.2.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, + {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, +] + +[[package]] +name = "neo4j" +version = "5.16.0" +description = "Neo4j Bolt driver for Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "neo4j-5.16.0.tar.gz", hash = "sha256:3d04334f5f99dc06c8150e75f2d608a560789ef35670494ecdcec31c0af276a9"}, +] + +[package.dependencies] +pytz = "*" + +[package.extras] +numpy = ["numpy (>=1.7.0,<2.0.0)"] +pandas = ["numpy (>=1.7.0,<2.0.0)", "pandas (>=1.1.0,<3.0.0)"] +pyarrow = ["pyarrow (>=1.0.0)"] + +[[package]] +name = "nh3" +version = "0.2.15" +description = "Python bindings to the ammonia HTML sanitization library." +optional = false +python-versions = "*" +files = [ + {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"}, + {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"}, + {file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"}, + {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"}, + {file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"}, + {file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"}, + {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = true +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "opensearch-py" +version = "2.4.2" +description = "Python client for OpenSearch" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +files = [ + {file = "opensearch-py-2.4.2.tar.gz", hash = "sha256:564f175af134aa885f4ced6846eb4532e08b414fff0a7976f76b276fe0e69158"}, + {file = "opensearch_py-2.4.2-py2.py3-none-any.whl", hash = "sha256:7867319132133e2974c09f76a54eb1d502b989229be52da583d93ddc743ea111"}, +] + +[package.dependencies] +certifi = ">=2022.12.07" +python-dateutil = "*" +requests = ">=2.4.0,<3.0.0" +six = "*" +urllib3 = ">=1.26.18" + +[package.extras] +async = ["aiohttp (>=3,<4)"] +develop = ["black", "botocore", "coverage (<8.0.0)", "jinja2", "mock", "myst-parser", "pytest (>=3.0.0)", "pytest-cov", "pytest-mock (<4.0.0)", "pytz", "pyyaml", "requests (>=2.0.0,<3.0.0)", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] +docs = ["aiohttp (>=3,<4)", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] +kerberos = ["requests-kerberos"] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +description = "Capture the outcome of Python function calls." +optional = true +python-versions = ">=3.7" +files = [ + {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, + {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pg8000" +version = "1.30.4" +description = "PostgreSQL interface library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pg8000-1.30.4-py3-none-any.whl", hash = "sha256:64bbe27b11588a53cee08e840988416227263dc5191b649fab963949f3ddd84d"}, + {file = "pg8000-1.30.4.tar.gz", hash = "sha256:2fa6964fff591a5e076fa6dd21a317c74de2caaa52991bb1f8b3d8ef2e56d172"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.2" +scramp = ">=1.4.4" + +[[package]] +name = "pika" +version = "1.3.2" +description = "Pika Python AMQP Client Library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f"}, + {file = "pika-1.3.2.tar.gz", hash = "sha256:b2a327ddddf8570b4965b3576ac77091b850262d34ce8c1d8cb4e4146aa4145f"}, +] + +[package.extras] +gevent = ["gevent"] +tornado = ["tornado"] +twisted = ["twisted"] + +[[package]] +name = "pkginfo" +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov"] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "proto-plus" +version = "1.23.0" +description = "Beautiful, Pythonic protocol buffers." +optional = true +python-versions = ">=3.6" +files = [ + {file = "proto-plus-1.23.0.tar.gz", hash = "sha256:89075171ef11988b3fa157f5dbd8b9cf09d65fffee97e29ce403cd8defba19d2"}, + {file = "proto_plus-1.23.0-py3-none-any.whl", hash = "sha256:a829c79e619e1cf632de091013a4173deed13a55f326ef84f05af6f50ff4c82c"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<5.0.0dev" + +[package.extras] +testing = ["google-api-core[grpc] (>=1.31.5)"] + +[[package]] +name = "protobuf" +version = "4.25.2" +description = "" +optional = true +python-versions = ">=3.8" +files = [ + {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, + {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, + {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, + {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, + {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, + {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, + {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, + {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, + {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = true +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + +[[package]] +name = "pyasn1" +version = "0.5.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pycryptodome" +version = "3.20.0" +description = "Cryptographic library for Python" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pymongo" +version = "4.6.1" +description = "Python driver for MongoDB " +optional = true +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4344c30025210b9fa80ec257b0e0aab5aa1d5cca91daa70d82ab97b482cc038e"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:1c5654bb8bb2bdb10e7a0bc3c193dd8b49a960b9eebc4381ff5a2043f4c3c441"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:eaf2f65190c506def2581219572b9c70b8250615dc918b3b7c218361a51ec42e"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:262356ea5fcb13d35fb2ab6009d3927bafb9504ef02339338634fffd8a9f1ae4"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:2dd2f6960ee3c9360bed7fb3c678be0ca2d00f877068556785ec2eb6b73d2414"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:ff925f1cca42e933376d09ddc254598f8c5fcd36efc5cac0118bb36c36217c41"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:3cadf7f4c8e94d8a77874b54a63c80af01f4d48c4b669c8b6867f86a07ba994f"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55dac73316e7e8c2616ba2e6f62b750918e9e0ae0b2053699d66ca27a7790105"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:154b361dcb358ad377d5d40df41ee35f1cc14c8691b50511547c12404f89b5cb"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2940aa20e9cc328e8ddeacea8b9a6f5ddafe0b087fedad928912e787c65b4909"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:010bc9aa90fd06e5cc52c8fac2c2fd4ef1b5f990d9638548dde178005770a5e8"}, + {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e470fa4bace5f50076c32f4b3cc182b31303b4fefb9b87f990144515d572820b"}, + {file = "pymongo-4.6.1-cp310-cp310-win32.whl", hash = "sha256:da08ea09eefa6b960c2dd9a68ec47949235485c623621eb1d6c02b46765322ac"}, + {file = "pymongo-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:13d613c866f9f07d51180f9a7da54ef491d130f169e999c27e7633abe8619ec9"}, + {file = "pymongo-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a0ae7a48a6ef82ceb98a366948874834b86c84e288dbd55600c1abfc3ac1d88"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd94c503271e79917b27c6e77f7c5474da6930b3fb9e70a12e68c2dff386b9a"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d4ccac3053b84a09251da8f5350bb684cbbf8c8c01eda6b5418417d0a8ab198"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:349093675a2d3759e4fb42b596afffa2b2518c890492563d7905fac503b20daa"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88beb444fb438385e53dc9110852910ec2a22f0eab7dd489e827038fdc19ed8d"}, + {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8e62d06e90f60ea2a3d463ae51401475568b995bafaffd81767d208d84d7bb1"}, + {file = "pymongo-4.6.1-cp311-cp311-win32.whl", hash = "sha256:5556e306713e2522e460287615d26c0af0fe5ed9d4f431dad35c6624c5d277e9"}, + {file = "pymongo-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:b10d8cda9fc2fcdcfa4a000aa10413a2bf8b575852cd07cb8a595ed09689ca98"}, + {file = "pymongo-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b435b13bb8e36be11b75f7384a34eefe487fe87a6267172964628e2b14ecf0a7"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e438417ce1dc5b758742e12661d800482200b042d03512a8f31f6aaa9137ad40"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b47ebd89e69fbf33d1c2df79759d7162fc80c7652dacfec136dae1c9b3afac7"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbed8cccebe1169d45cedf00461b2842652d476d2897fd1c42cf41b635d88746"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30a9e06041fbd7a7590693ec5e407aa8737ad91912a1e70176aff92e5c99d20"}, + {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8729dbf25eb32ad0dc0b9bd5e6a0d0b7e5c2dc8ec06ad171088e1896b522a74"}, + {file = "pymongo-4.6.1-cp312-cp312-win32.whl", hash = "sha256:3177f783ae7e08aaf7b2802e0df4e4b13903520e8380915e6337cdc7a6ff01d8"}, + {file = "pymongo-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:00c199e1c593e2c8b033136d7a08f0c376452bac8a896c923fcd6f419e07bdd2"}, + {file = "pymongo-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6dcc95f4bb9ed793714b43f4f23a7b0c57e4ef47414162297d6f650213512c19"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:13552ca505366df74e3e2f0a4f27c363928f3dff0eef9f281eb81af7f29bc3c5"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:77e0df59b1a4994ad30c6d746992ae887f9756a43fc25dec2db515d94cf0222d"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3a7f02a58a0c2912734105e05dedbee4f7507e6f1bd132ebad520be0b11d46fd"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:026a24a36394dc8930cbcb1d19d5eb35205ef3c838a7e619e04bd170713972e7"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:3b287e814a01deddb59b88549c1e0c87cefacd798d4afc0c8bd6042d1c3d48aa"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:9a710c184ba845afb05a6f876edac8f27783ba70e52d5eaf939f121fc13b2f59"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:30b2c9caf3e55c2e323565d1f3b7e7881ab87db16997dc0cbca7c52885ed2347"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff62ba8ff70f01ab4fe0ae36b2cb0b5d1f42e73dfc81ddf0758cd9f77331ad25"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:547dc5d7f834b1deefda51aedb11a7af9c51c45e689e44e14aa85d44147c7657"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1de3c6faf948f3edd4e738abdb4b76572b4f4fdfc1fed4dad02427e70c5a6219"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2831e05ce0a4df10c4ac5399ef50b9a621f90894c2a4d2945dc5658765514ed"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:144a31391a39a390efce0c5ebcaf4bf112114af4384c90163f402cec5ede476b"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33bb16a07d3cc4e0aea37b242097cd5f7a156312012455c2fa8ca396953b11c4"}, + {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b7b1a83ce514700276a46af3d9e481ec381f05b64939effc9065afe18456a6b9"}, + {file = "pymongo-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:3071ec998cc3d7b4944377e5f1217c2c44b811fae16f9a495c7a1ce9b42fb038"}, + {file = "pymongo-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2346450a075625c4d6166b40a013b605a38b6b6168ce2232b192a37fb200d588"}, + {file = "pymongo-4.6.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:061598cbc6abe2f382ab64c9caa83faa2f4c51256f732cdd890bcc6e63bfb67e"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d483793a384c550c2d12cb794ede294d303b42beff75f3b3081f57196660edaf"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f9756f1d25454ba6a3c2f1ef8b7ddec23e5cdeae3dc3c3377243ae37a383db00"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ed23b0e2dac6f84f44c8494fbceefe6eb5c35db5c1099f56ab78fc0d94ab3af"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3d18a9b9b858ee140c15c5bfcb3e66e47e2a70a03272c2e72adda2482f76a6ad"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:c258dbacfff1224f13576147df16ce3c02024a0d792fd0323ac01bed5d3c545d"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:f7acc03a4f1154ba2643edeb13658d08598fe6e490c3dd96a241b94f09801626"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:76013fef1c9cd1cd00d55efde516c154aa169f2bf059b197c263a255ba8a9ddf"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0e6a6c807fa887a0c51cc24fe7ea51bb9e496fe88f00d7930063372c3664c3"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd1fa413f8b9ba30140de198e4f408ffbba6396864c7554e0867aa7363eb58b2"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d219b4508f71d762368caec1fc180960569766049bbc4d38174f05e8ef2fe5b"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b81ecf18031998ad7db53b960d1347f8f29e8b7cb5ea7b4394726468e4295e"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56816e43c92c2fa8c11dc2a686f0ca248bea7902f4a067fa6cbc77853b0f041e"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef801027629c5b511cf2ba13b9be29bfee36ae834b2d95d9877818479cdc99ea"}, + {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d4c2be9760b112b1caf649b4977b81b69893d75aa86caf4f0f398447be871f3c"}, + {file = "pymongo-4.6.1-cp38-cp38-win32.whl", hash = "sha256:39d77d8bbb392fa443831e6d4ae534237b1f4eee6aa186f0cdb4e334ba89536e"}, + {file = "pymongo-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:4497d49d785482cc1a44a0ddf8830b036a468c088e72a05217f5b60a9e025012"}, + {file = "pymongo-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:69247f7a2835fc0984bbf0892e6022e9a36aec70e187fcfe6cae6a373eb8c4de"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7bb0e9049e81def6829d09558ad12d16d0454c26cabe6efc3658e544460688d9"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a1810c2cbde714decf40f811d1edc0dae45506eb37298fd9d4247b8801509fe"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e2aced6fb2f5261b47d267cb40060b73b6527e64afe54f6497844c9affed5fd0"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d0355cff58a4ed6d5e5f6b9c3693f52de0784aa0c17119394e2a8e376ce489d4"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:3c74f4725485f0a7a3862cfd374cc1b740cebe4c133e0c1425984bcdcce0f4bb"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:9c79d597fb3a7c93d7c26924db7497eba06d58f88f58e586aa69b2ad89fee0f8"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8ec75f35f62571a43e31e7bd11749d974c1b5cd5ea4a8388725d579263c0fdf6"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e641f931c5cd95b376fd3c59db52770e17bec2bf86ef16cc83b3906c054845"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aafd036f6f2e5ad109aec92f8dbfcbe76cff16bad683eb6dd18013739c0b3ae"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f2b856518bfcfa316c8dae3d7b412aecacf2e8ba30b149f5eb3b63128d703b9"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec31adc2e988fd7db3ab509954791bbc5a452a03c85e45b804b4bfc31fa221d"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9167e735379ec43d8eafa3fd675bfbb12e2c0464f98960586e9447d2cf2c7a83"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1461199b07903fc1424709efafe379205bf5f738144b1a50a08b0396357b5abf"}, + {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3094c7d2f820eecabadae76bfec02669567bbdd1730eabce10a5764778564f7b"}, + {file = "pymongo-4.6.1-cp39-cp39-win32.whl", hash = "sha256:c91ea3915425bd4111cb1b74511cdc56d1d16a683a48bf2a5a96b6a6c0f297f7"}, + {file = "pymongo-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ef102a67ede70e1721fe27f75073b5314911dbb9bc27cde0a1c402a11531e7bd"}, + {file = "pymongo-4.6.1.tar.gz", hash = "sha256:31dab1f3e1d0cdd57e8df01b645f52d43cc1b653ed3afd535d2891f4fc4f9712"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (<2.0.0)"] +encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=7)"] +zstd = ["zstandard"] + +[[package]] +name = "pymssql" +version = "2.2.11" +description = "DB-API interface to Microsoft SQL Server for Python. (new Cython-based version)" +optional = true +python-versions = "*" +files = [ + {file = "pymssql-2.2.11-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:692ab328ac290bd2031bc4dd6deae32665dfffda1b12aaa92928d3ebc667d5ad"}, + {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:723a4612421027a01b51e42e786678a18c4a27613a3ccecf331c026e0cc41353"}, + {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:34ab2373ca607174ad7244cfe955c07b6bc77a1e21d3c3143dbe934dec82c3a4"}, + {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bc0ba19b4426c57509f065a03748d9ac230f1543ecdac57175e6ebd213a7bc0"}, + {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8d9d42a50f6e8e6b356e4e8b2fa1da725344ec0be6f8a6107b7196e5bd74906"}, + {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aec64022a2419fad9f496f8e310522635e39d092970e1d55375ea0be86725174"}, + {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c389c8041c94d4058827faf5735df5f8e4c1c1eebdd051859536dc393925a667"}, + {file = "pymssql-2.2.11-cp310-cp310-win32.whl", hash = "sha256:6452326cecd4dcee359a6f8878b827118a8c8523cd24de5b3a971a7a172e4275"}, + {file = "pymssql-2.2.11-cp310-cp310-win_amd64.whl", hash = "sha256:c1bde266dbc91b100abd0311102a6585df09cc963599421cc12fd6b4cfa8e3d3"}, + {file = "pymssql-2.2.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6ddaf0597138179517bdbf5b5aa3caffee65987316dc906359a5d0801d0847ee"}, + {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c26af25991715431559cb5b37f243b8ff676540f504ed0317774dfc71827af1"}, + {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:410e8c40b7c1b421e750cf80ccf2da8d802ed815575758ac9a78c5f6cd995723"}, + {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1767239ed45e1fa91d82fc0c63305750530787cd64089cabbe183eb538a35b"}, + {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:9a644e4158fed30ae9f3846f2f1c74d36fa1610eb552de35b7f611d063fa3c85"}, + {file = "pymssql-2.2.11-cp311-cp311-win32.whl", hash = "sha256:1956c111debe67f69a9c839b33ce420f0e8def1ef5ff9831c03d8ac840f82376"}, + {file = "pymssql-2.2.11-cp311-cp311-win_amd64.whl", hash = "sha256:0bdd1fb49b0e331e47e83f39d4af784c857e230bfc73519654bab29285c51c63"}, + {file = "pymssql-2.2.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2609bbd3b715822bb4fa6d457b2985d32ad6ab9580fdb61ae6e0eee251791d24"}, + {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c382aea9adaaee189f352d7a493e3f76c13f9337ec2b6aa40e76b114fa13ebac"}, + {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5928324a09de7466368c15ece1de4ab5ea968d24943ceade758836f9fc7149f5"}, + {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee8b10f797d0bfec626b803891cf9e98480ee11f2e8459a7616cdb7e4e4bf2de"}, + {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1d5aa1a090b17f4ba75ffac3bb371f6c8c869692b653689396f9b470fde06981"}, + {file = "pymssql-2.2.11-cp312-cp312-win32.whl", hash = "sha256:1f7ba71cf81af65c005173f279928bf86700d295f97e4965e169b5764bc6c4f2"}, + {file = "pymssql-2.2.11-cp312-cp312-win_amd64.whl", hash = "sha256:a0ebb0e40c93f8f1e40aad80f512ae4aa89cb1ec8a96964b9afedcff1d5813fd"}, + {file = "pymssql-2.2.11-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0ed115902956efaca9d9a20fa9b2b604e3e11d640416ca74900d215cdcbf3ab"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1a75afa17746972bb61120fb6ea907657fc1ab68250bbbd8b21a00d0720ed0f4"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2ae69d8e46637a203cfb48e05439fc9e2ff7646fa1f5396aa3577ce52810031"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13710240457ace5b8c9cca7f4971504656f5703b702895a86386e87c7103801"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7234b0f61dd9ccb2304171b5fd7ed9db133b4ea7c835c9942c9dc5bfc00c1cb"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcd76a8cc757c7cfe2d235f232a20d74ac8cebf9feabcdcbda5ef33157d14b1"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:84aff3235ad1289c4079c548cfcdf7eaaf2475b9f81557351deb42e8f45a9c2d"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b081aa7b02911e3f299f7d1f68ce8ca585a5119d44601bf4483da0aae8c2181"}, + {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d315f08c106c884d6b42f70c9518e765a5bc23f6d3a587346bc4e6f198768c7a"}, + {file = "pymssql-2.2.11-cp36-cp36m-win32.whl", hash = "sha256:c8b35b3d5e326729e5edb73d593103d2dbfb474bd36ee95b4e85e1f8271ba98a"}, + {file = "pymssql-2.2.11-cp36-cp36m-win_amd64.whl", hash = "sha256:139c5032e0a2765764987803f1266132fcc5da572848ccc4d29cebba794a4260"}, + {file = "pymssql-2.2.11-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:7bac28aed1d625a002e0289e0c18d1808cecbdc12e2a1a3927dbbaff66e5fff3"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4eeaacc1dbbc678f4e80c6fd6fc279468021fdf2e486adc8631ec0de6b6c0e62"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:428e32e53c554798bc2d0682a169fcb681df6b68544c4aedd1186018ea7e0447"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b621c5e32136dabc2fea25696beab0647ec336d25c04ab6d8eb8c8ee92f0e52"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:658c85474ea01ca3a30de769df06f46681e882524b05c6994cd6fd985c485f27"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070181361ab94bdaeb14b591a35d853f327bc90c660b04047d474274fbb80357"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:492e49616b58b2d6caf4a2598cb344572870171a7b65ba1ac61a5e248b6a8e1c"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:803122aec31fbd52f5d65ef3b30b3bd2dc7b2a9e3a8223d16078a25805155c45"}, + {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:09075e129655ab1178d2d60efb9b3fbf5cdb6da2338ecdb3a92c53a4ad7efa0c"}, + {file = "pymssql-2.2.11-cp37-cp37m-win32.whl", hash = "sha256:b4a8377527702d746c490c2ce67d17f1c351d182b49b82fae6e67ae206bf9663"}, + {file = "pymssql-2.2.11-cp37-cp37m-win_amd64.whl", hash = "sha256:167313d91606dc7a3c05b2ad60491a138b7408a8779599ab6430a48a67f133f0"}, + {file = "pymssql-2.2.11-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:8d418f4dca245421242ed9df59d3bcda0cd081650df6deb1bef7f157b6a6f9dd"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f0c44169df8d23c7ce172bd90ef5deb44caf19f15990e4db266e3193071988a4"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78032e45ea33c55d430b93e55370b900479ea324fae5d5d32486cc0fdc0fedd"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:984d99ee6a2579f86c536b1b0354ad3dc9701e98a4b3953f1301b4695477cd2f"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:287c8f79a7eca0c6787405797bac0f7c502d9be151f3f823aae12042235f8426"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ea4ea296afcae34bc61e4e0ef2f503270fd4bb097b308a07a9194f1f063aa1"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a114633fa02b7eb5bc63520bf07954106c0ed0ce032449c871abb8b8c435a872"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7332db36a537cbc16640a0c3473a2e419aa5bc1f9953cada3212e7b2587de658"}, + {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cd7292d872948c1f67c8cc12158f2c8ed9873d54368139ce1f67b2262ac34029"}, + {file = "pymssql-2.2.11-cp38-cp38-win32.whl", hash = "sha256:fbca115e11685b5891755cc22b3db4348071b8d100a41e1ce93526d9c3dbf2d5"}, + {file = "pymssql-2.2.11-cp38-cp38-win_amd64.whl", hash = "sha256:452b88a4ceca7efb934b5babb365851a3c52e723642092ebc92777397c2cacdb"}, + {file = "pymssql-2.2.11-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:001242cedc73587cbb10aec4069de50febbff3c4c50f9908a215476496b3beab"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:da492482b923b9cc9ad37f0f5592c776279299db2a89c0b7fc931aaefec652d4"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:139a833e6e72a624e4f2cde803a34a616d5661dd9a5b2ae0402d9d8a597b2f1f"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e57fbfad252434d64bdf4b6a935e4241616a4cf8df7af58b9772cd91fce9309a"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5308507c2c4e94ede7e5b164870c1ba2be55abab6daf795b5529e2da4e838b6"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdca43c42d5f370358535b2107140ed550d74f9ef0fc95d2d7fa8c4e40ee48c2"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:fe0cc975aac87b364fdb55cb89642435c3e859dcd99d7260f48af94111ba2673"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4551f50c8a3b6ffbd71f794ee1c0c0134134c5d6414302c2fa28b67fe4470d07"}, + {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ae9818df40588d5a49e7476f05e31cc83dea630d607178d66762ca8cf32e9f77"}, + {file = "pymssql-2.2.11-cp39-cp39-win32.whl", hash = "sha256:15257c7bd89c0283f70d6eaafd9b872201818572b8ba1e8576408ae23ef50c7c"}, + {file = "pymssql-2.2.11-cp39-cp39-win_amd64.whl", hash = "sha256:65bb674c0ba35379bf93d1b2cf06fdc5e7ec56e1d0e9de525bdcf977190b2865"}, + {file = "pymssql-2.2.11.tar.gz", hash = "sha256:15815bf1ff9edb475ec4ef567f23e23c4e828ce119ff5bf98a072b66b8d0ac1b"}, +] + +[[package]] +name = "pymysql" +version = "1.1.0" +description = "Pure Python MySQL Driver" +optional = true +python-versions = ">=3.7" +files = [ + {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, + {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, +] + +[package.dependencies] +cryptography = {version = "*", optional = true, markers = "extra == \"rsa\""} + +[package.extras] +ed25519 = ["PyNaCl (>=1.4.0)"] +rsa = ["cryptography"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-arango" +version = "7.9.1" +description = "Python Driver for ArangoDB" +optional = true +python-versions = ">=3.8" +files = [ + {file = "python-arango-7.9.1.tar.gz", hash = "sha256:18f7d365fb6cf45778fa73b559e3865d0a1c00081de65ef00ba238db52e374ab"}, + {file = "python_arango-7.9.1-py3-none-any.whl", hash = "sha256:23ec7b3aad774db5f99df20f6a1036385c85eb5c9864e47628bc622ea812f2f8"}, +] + +[package.dependencies] +importlib-metadata = ">=4.7.1" +packaging = ">=23.1" +PyJWT = "*" +requests = "*" +requests-toolbelt = "*" +setuptools = ">=42" +urllib3 = ">=1.26.0" + +[package.extras] +dev = ["black (>=22.3.0)", "flake8 (>=4.0.1)", "isort (>=5.10.1)", "mock", "mypy (>=0.942)", "pre-commit (>=2.17.0)", "pytest (>=7.1.1)", "pytest-cov (>=3.0.0)", "sphinx", "sphinx-rtd-theme", "types-pkg-resources", "types-requests", "types-setuptools"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-jose" +version = "3.3.0" +description = "JOSE implementation in Python" +optional = true +python-versions = "*" +files = [ + {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, + {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, +] + +[package.dependencies] +ecdsa = "!=0.15" +pyasn1 = "*" +rsa = "*" + +[package.extras] +cryptography = ["cryptography (>=3.4.0)"] +pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] +pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] + +[[package]] +name = "python-keycloak" +version = "3.7.0" +description = "python-keycloak is a Python package providing access to the Keycloak API." +optional = true +python-versions = ">=3.8,<4.0" +files = [ + {file = "python_keycloak-3.7.0-py3-none-any.whl", hash = "sha256:92aa0a7e965cc5422d335c36efa0519f3188d9b8048cc8083f8f6e23c13178a5"}, + {file = "python_keycloak-3.7.0.tar.gz", hash = "sha256:29eee9490ba354af81fcdf86ec81d840515d8b53002de831715e05d07298886a"}, +] + +[package.dependencies] +deprecation = ">=2.1.0" +python-jose = ">=3.3.0" +requests = ">=2.20.0" +requests-toolbelt = ">=0.6.0" + +[package.extras] +docs = ["Sphinx (>=6.1.0,<7.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (>=0.9.1,<0.10.0)", "m2r2 (>=0.3.2,<0.4.0)", "mock (>=4.0.3,<5.0.0)", "readthedocs-sphinx-ext (>=2.1.9,<3.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-autoapi (>=3.0.0,<4.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"] + +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = true +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = true +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "readme-renderer" +version = "42.0" +description = "readme_renderer is a library for rendering readme descriptions for Warehouse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "readme_renderer-42.0-py3-none-any.whl", hash = "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d"}, + {file = "readme_renderer-42.0.tar.gz", hash = "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1"}, +] + +[package.dependencies] +docutils = ">=0.13.1" +nh3 = ">=0.2.14" +Pygments = ">=2.5.1" + +[package.extras] +md = ["cmarkgfm (>=0.8.0)"] + +[[package]] +name = "redis" +version = "5.0.1" +description = "Python client for Redis database and key-value store" +optional = true +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"}, + {file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rfc3986" +version = "2.0.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = true +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "s3transfer" +version = "0.10.0" +description = "An Amazon S3 Transfer Manager" +optional = true +python-versions = ">= 3.8" +files = [ + {file = "s3transfer-0.10.0-py3-none-any.whl", hash = "sha256:3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e"}, + {file = "s3transfer-0.10.0.tar.gz", hash = "sha256:d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + +[[package]] +name = "scramp" +version = "1.4.4" +description = "An implementation of the SCRAM protocol." +optional = false +python-versions = ">=3.7" +files = [ + {file = "scramp-1.4.4-py3-none-any.whl", hash = "sha256:b142312df7c2977241d951318b7ee923d6b7a4f75ba0f05b621ece1ed616faa3"}, + {file = "scramp-1.4.4.tar.gz", hash = "sha256:b7022a140040f33cf863ab2657917ed05287a807b917950489b89b9f685d59bc"}, +] + +[package.dependencies] +asn1crypto = ">=1.5.1" + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "selenium" +version = "4.17.2" +description = "" +optional = true +python-versions = ">=3.8" +files = [ + {file = "selenium-4.17.2-py3-none-any.whl", hash = "sha256:5aee79026c07985dc1b0c909f34084aa996dfe5b307602de9016d7a621a473f2"}, + {file = "selenium-4.17.2.tar.gz", hash = "sha256:d43d6972e516855fb242ef9ce4ce759057b115070e702e7b1c1032fe7b38d87b"}, +] + +[package.dependencies] +certifi = ">=2021.10.8" +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +typing_extensions = ">=4.9.0" +urllib3 = {version = ">=1.26,<3", extras = ["socks"]} + +[[package]] +name = "setuptools" +version = "69.0.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = true +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = true +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = true +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.8" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.6" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.5" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.7" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.10" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sqlalchemy" +version = "2.0.25" +description = "Database Abstraction Library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, + {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, + {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "trio" +version = "0.24.0" +description = "A friendly Python library for async concurrency and I/O" +optional = true +python-versions = ">=3.8" +files = [ + {file = "trio-0.24.0-py3-none-any.whl", hash = "sha256:c3bd3a4e3e3025cd9a2241eae75637c43fe0b9e88b4c97b9161a55b9e54cd72c"}, + {file = "trio-0.24.0.tar.gz", hash = "sha256:ffa09a74a6bf81b84f8613909fb0beaee84757450183a7a2e0b47b455c0cac5d"}, +] + +[package.dependencies] +attrs = ">=20.1.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = "*" +outcome = "*" +sniffio = ">=1.3.0" +sortedcontainers = "*" + +[[package]] +name = "trio-websocket" +version = "0.11.1" +description = "WebSocket library for Trio" +optional = true +python-versions = ">=3.7" +files = [ + {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, + {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +trio = ">=0.11" +wsproto = ">=0.14" + +[[package]] +name = "twine" +version = "4.0.2" +description = "Collection of utilities for publishing packages on PyPI" +optional = false +python-versions = ">=3.7" +files = [ + {file = "twine-4.0.2-py3-none-any.whl", hash = "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8"}, + {file = "twine-4.0.2.tar.gz", hash = "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8"}, +] + +[package.dependencies] +importlib-metadata = ">=3.6" +keyring = ">=15.1" +pkginfo = ">=1.8.1" +readme-renderer = ">=35.0" +requests = ">=2.20" +requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" +rfc3986 = ">=1.4.0" +rich = ">=12.0.0" +urllib3 = ">=1.26.0" + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = true +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "tzdata" +version = "2023.4" +description = "Provider of IANA time zone data" +optional = true +python-versions = ">=2" +files = [ + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, +] + +[[package]] +name = "tzlocal" +version = "5.2" +description = "tzinfo object for the local timezone" +optional = true +python-versions = ">=3.8" +files = [ + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + +[[package]] +name = "urllib3" +version = "1.26.18" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, +] + +[package.dependencies] +PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "urllib3" +version = "2.0.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, +] + +[package.dependencies] +pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "websocket-client" +version = "1.7.0" +description = "WebSocket client for Python with low level API options" +optional = true +python-versions = ">=3.8" +files = [ + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = true +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[extras] +arangodb = ["python-arango"] +azurite = ["azure-storage-blob"] +clickhouse = ["clickhouse-driver"] +elasticsearch = [] +google = ["google-cloud-pubsub"] +k3s = ["kubernetes", "pyyaml"] +kafka = ["kafka-python"] +keycloak = ["python-keycloak"] +localstack = ["boto3"] +minio = ["minio"] +mongodb = ["pymongo"] +mssql = ["pymssql", "sqlalchemy"] +mysql = ["pymysql", "sqlalchemy"] +neo4j = ["neo4j"] +nginx = [] +opensearch = ["opensearch-py"] +oracle = ["cx_Oracle", "sqlalchemy"] +postgres = ["psycopg2-binary", "sqlalchemy"] +rabbitmq = ["pika"] +redis = ["redis"] +selenium = ["selenium"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<3.12" +content-hash = "9581bf8b84748e77f2c480e320307fe223cedc7eee614512b9ee5de8fd562bd3" diff --git a/postgres/setup.py b/postgres/setup.py deleted file mode 100644 index 1d9abd351..000000000 --- a/postgres/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "PostgreSQL component of testcontainers-python." - -setup( - name="testcontainers-postgres", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "sqlalchemy", - "psycopg2-binary", - ], - python_requires=">=3.7", -) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..9bc87dfeb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,144 @@ +[tool.poetry] +name = "testcontainers" +version = "4.0.0" # auto-incremented by release-please +description = "Python library for throwaway instances of anything that can run in a Docker container" +authors = ["Sergey Pirogov "] +maintainers = [ + "Balint Bartha ", + "David Ankin " +] +readme = "README.md" +keywords = ["testing", "logging", "docker", "test automation"] +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Intended Audience :: Information Technology", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Libraries :: Python Modules", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", +] +# testcontainers-core is a proper package dependency - only modules needed here +packages = [ + { include = "testcontainers", from = "core" }, + { include = "testcontainers", from = "modules/arangodb" }, + { include = "testcontainers", from = "modules/azurite" }, + { include = "testcontainers", from = "modules/clickhouse" }, + { include = "testcontainers", from = "modules/elasticsearch" }, + { include = "testcontainers", from = "modules/google" }, + { include = "testcontainers", from = "modules/k3s" }, + { include = "testcontainers", from = "modules/kafka" }, + { include = "testcontainers", from = "modules/keycloak" }, + { include = "testcontainers", from = "modules/localstack" }, + { include = "testcontainers", from = "modules/minio" }, + { include = "testcontainers", from = "modules/mongodb" }, + { include = "testcontainers", from = "modules/mssql" }, + { include = "testcontainers", from = "modules/mysql" }, + { include = "testcontainers", from = "modules/neo4j" }, + { include = "testcontainers", from = "modules/nginx" }, + { include = "testcontainers", from = "modules/opensearch" }, + { include = "testcontainers", from = "modules/oracle" }, + { include = "testcontainers", from = "modules/postgres" }, + { include = "testcontainers", from = "modules/rabbitmq" }, + { include = "testcontainers", from = "modules/redis" }, + { include = "testcontainers", from = "modules/selenium" } +] + +[tool.poetry.urls] +"GitHub" = "https://github.com/testcontainers/testcontainers-python" +"Issue Tracker" = "https://github.com/testcontainers/testcontainers-python/issues" + +[tool.poetry.dependencies] +python = ">=3.9,<3.12" +docker = "*" # ">=4.0" +urllib3 = "*" # "<2.0" +wrapt = "*" # "^1.16.0" + +# community modules +python-arango = { version = "^7.8", optional = true } +azure-storage-blob = { version = "^12.19", optional = true } +clickhouse-driver = { version = "*", optional = true } +google-cloud-pubsub = { version = ">=2", optional = true } +kubernetes = { version = "*", optional = true } +pyyaml = { version = "*", optional = true } +kafka-python = { version = "*", optional = true } +python-keycloak = { version = "*", optional = true } +boto3 = { version = "*", optional = true } +minio = { version = "*", optional = true } +pymongo = { version = "*", optional = true } +sqlalchemy = { version = "*", optional = true } +pymssql = { version = "*", optional = true } +pymysql = { version = "*", extras = ["rsa"], optional = true } +neo4j = { version = "*", optional = true } +opensearch-py = { version = "*", optional = true } +cx_Oracle = { version = "*", optional = true } +psycopg2-binary = { version = "*", optional = true } +pika = { version = "*", optional = true } +redis = { version = "*", optional = true } +selenium = { version = "*", optional = true } + +[tool.poetry.extras] +arangodb = ["python-arango"] +azurite = ["azure-storage-blob"] +clickhouse = ["clickhouse-driver"] +elasticsearch = [] +google = ["google-cloud-pubsub"] +k3s = ["kubernetes", "pyyaml"] +kafka = ["kafka-python"] +keycloak = ["python-keycloak"] +localstack = ["boto3"] +minio = ["minio"] +mongodb = ["pymongo"] +mssql = ["sqlalchemy", "pymssql"] +mysql = ["sqlalchemy", "pymysql"] +neo4j = ["neo4j"] +nginx = [] +opensearch = ["opensearch-py"] +oracle = ["sqlalchemy", "cx_Oracle"] +postgres = ["sqlalchemy", "psycopg2-binary"] +rabbitmq = ["pika"] +redis = ["redis"] +selenium = ["selenium"] + +[tool.poetry.group.dev.dependencies] +pytest = "7.4.3" +pytest-cov = "4.1.0" +sphinx = "^7.2.6" +flake8 = "^6.1.0" +pg8000 = "*" +twine = "^4.0.2" + +[[tool.poetry.source]] +name = "PyPI" +priority = "primary" + +[tool.black] +line-length = 120 + +[tool.pytest.ini_options] +addopts = "--cov-report=term --tb=short --strict-markers" +log_cli = true +log_cli_level = "INFO" + +[tool.coverage.run] +branch = true +omit = [ + "oracle.py" +] + +[tool.coverage.report] +exclude_lines = [ + "pass", + "raise NotImplementedError" # TODO: used in core/generic.py, not sure we need DbContainer +] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/rabbitmq/setup.py b/rabbitmq/setup.py deleted file mode 100644 index 853887ead..000000000 --- a/rabbitmq/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "RabbitMQ component of testcontainers-python." - -setup( - name="testcontainers-rabbitmq", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "pika", - ], - python_requires=">=3.7", -) diff --git a/redis/setup.py b/redis/setup.py deleted file mode 100644 index 2e1131e5a..000000000 --- a/redis/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Redis component of testcontainers-python." - -setup( - name="testcontainers-redis", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "redis", - ], - python_requires=">=3.7", -) diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 104dc36a3..000000000 --- a/requirements.in +++ /dev/null @@ -1,31 +0,0 @@ --e file:arangodb --e file:azurite --e file:clickhouse --e file:core --e file:elasticsearch --e file:google --e file:kafka --e file:keycloak --e file:localstack --e file:meta --e file:minio --e file:mongodb --e file:mssql --e file:mysql --e file:neo4j --e file:nginx --e file:opensearch --e file:oracle --e file:postgres --e file:rabbitmq --e file:redis --e file:selenium --e file:k3s -cryptography<37 -flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. -pg8000 -pytest -pytest-cov -sphinx -twine -wheel diff --git a/selenium/setup.py b/selenium/setup.py deleted file mode 100644 index b2fff38f3..000000000 --- a/selenium/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -from setuptools import setup, find_namespace_packages - -description = "Selenium component of testcontainers-python." - -setup( - name="testcontainers-selenium", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - "selenium", - ], - python_requires=">=3.7", -) diff --git a/setup.cfg b/setup.cfg index d5bfb97b9..d673938d4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,13 +1,3 @@ -[bdist_wheel] -universal = 1 - -[metadata] -description-file = README.rst - [flake8] max-line-length = 100 exclude = .git,__pycache__,build,dist,venv,.venv - -[tools:pytest] -log_cli_level = INFO -log_cli = true From 32713578dcf07f672a87818e00562b58874b4a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:37:34 +0100 Subject: [PATCH 275/425] fix: changed files breaks on main (#422) # change Fixes an issue with the usage of https://github.com/tj-actions/changed-files. Without a checkout, it breaks on `main`. This re-introduces that push to main. Co-authored-by: Balint Bartha --- .github/workflows/ci-community.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 4abcae752..337b90661 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -45,6 +45,8 @@ jobs: - k3s runs-on: ubuntu-latest steps: + - name: Checkout contents + uses: actions/checkout@v4 - name: Get changed files id: changes-for-module uses: tj-actions/changed-files@v42 @@ -59,8 +61,6 @@ jobs: gh run watch ${{ github.run_id }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Checkout contents - uses: actions/checkout@v4 - name: Setup Poetry run: pipx install poetry - name: Setup python ${{ matrix.python-version }} From b535ea255bcaaa546f8cda7b2b17718c1cc7f3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:37:09 +0100 Subject: [PATCH 276/425] fix: flaky garbage collection resulting in testing errors (#423) # change Fixes #399. Applied a bit of defensive coding and attempted to create some tests for it, however reproducing it with a local dev machine is not easy. I did my best to reproduce the issue with garbage collection in the new test. --------- Co-authored-by: Balint Bartha --- core/testcontainers/core/container.py | 20 +++++++++++--------- core/testcontainers/core/docker_client.py | 16 ++++++++++++---- core/tests/test_core.py | 12 +++++++++++- pyproject.toml | 2 +- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index c3825b935..4caed7e3c 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,5 +1,5 @@ import os -from typing import Iterable, Optional, Tuple +from typing import Optional, Tuple from docker.models.containers import Container @@ -23,6 +23,7 @@ class DockerContainer: >>> with DockerContainer("hello-world") as container: ... delay = wait_for_logs(container, "Hello from Docker!") """ + def __init__(self, image: str, docker_client_kw: Optional[dict] = None, **kwargs) -> None: self.env = {} self.ports = {} @@ -42,7 +43,7 @@ def with_bind_ports(self, container: int, host: int = None) -> 'DockerContainer' self.ports[container] = host return self - def with_exposed_ports(self, *ports: Iterable[int]) -> 'DockerContainer': + def with_exposed_ports(self, *ports: int) -> 'DockerContainer': for port in ports: self.ports[port] = None return self @@ -67,7 +68,7 @@ def start(self) -> 'DockerContainer': return self def stop(self, force=True, delete_volume=True) -> None: - self.get_wrapped_container().remove(force=force, v=delete_volume) + self._container.remove(force=force, v=delete_volume) def __enter__(self) -> 'DockerContainer': return self.start() @@ -77,13 +78,14 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> None: def __del__(self) -> None: """ - Try to remove the container in all circumstances + __del__ runs when Python attempts to garbage collect the object. + In case of leaky test design, we still attempt to clean up the container. """ - if self._container is not None: - try: + try: + if self._container is not None: self.stop() - except: # noqa: E722 - pass + finally: + pass def get_container_host_ip(self) -> str: # infer from docker host @@ -143,4 +145,4 @@ def get_logs(self) -> Tuple[str, str]: def exec(self, command) -> Tuple[int, str]: if not self._container: raise ContainerStartException("Container should be started before executing a command") - return self.get_wrapped_container().exec_run(command) + return self._container.exec_run(command) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 228bfd1c1..fb54838fa 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -39,14 +39,22 @@ class DockerClient: """ Thin wrapper around :class:`docker.DockerClient` for a more functional interface. """ + def __init__(self, **kwargs) -> None: self.client = docker.from_env(**kwargs) @ft.wraps(ContainerCollection.run) - def run(self, image: str, command: Union[str, List[str]] = None, - environment: Optional[dict] = None, ports: Optional[dict] = None, - detach: bool = False, stdout: bool = True, stderr: bool = False, remove: bool = False, - **kwargs) -> Container: + def run( + self, image: str, + command: Union[str, List[str]] = None, + environment: Optional[dict] = None, + ports: Optional[dict] = None, + detach: bool = False, + stdout: bool = True, + stderr: bool = False, + remove: bool = False, + **kwargs + ) -> Container: container = self.client.containers.run( image, command=command, stdout=stdout, stderr=stderr, remove=remove, detach=detach, environment=environment, ports=ports, **kwargs diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 5a6663502..01e9c97c8 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -4,12 +4,22 @@ from testcontainers.core.waiting_utils import wait_for_logs -def test_raise_timeout(): +def test_timeout_is_raised_when_waiting_for_logs(): with pytest.raises(TimeoutError): with DockerContainer("alpine").with_command("sleep 2") as container: wait_for_logs(container, "Hello from Docker!", timeout=1e-3) +def test_garbage_collection_is_defensive(): + # For more info, see https://github.com/testcontainers/testcontainers-python/issues/399 + # we simulate garbage collection: start, stop, then call `del` + container = DockerContainer("postgres:latest") + container.start() + container.stop(force=True, delete_volume=True) + delattr(container, "_container") + del container + + def test_wait_for_hello(): with DockerContainer("hello-world") as container: wait_for_logs(container, "Hello from Docker!") diff --git a/pyproject.toml b/pyproject.toml index 9bc87dfeb..9139e3b25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ priority = "primary" line-length = 120 [tool.pytest.ini_options] -addopts = "--cov-report=term --tb=short --strict-markers" +addopts = "--cov-report=term --cov-report=html --tb=short --strict-markers" log_cli = true log_cli_level = "INFO" From 386521f2f2df190b1914a94ed29cb12799a5db61 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 19 Feb 2024 23:12:59 -0500 Subject: [PATCH 277/425] #415 fix (#427) --- core/testcontainers/core/container.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 4caed7e3c..0d30a57f5 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,4 +1,5 @@ import os +from platform import system from typing import Optional, Tuple from docker.models.containers import Container @@ -92,6 +93,9 @@ def get_container_host_ip(self) -> str: host = self.get_docker_client().host() if not host: return "localhost" + # see https://github.com/testcontainers/testcontainers-python/issues/415 + if host == "localnpipe" and "Windows" == system(): + return "localhost" # check testcontainers itself runs inside docker container if inside_container() and not os.getenv("DOCKER_HOST"): From 098db159cbdb54442465f8f6060bbb264f10de46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Wed, 21 Feb 2024 09:55:36 +0100 Subject: [PATCH 278/425] chore(lint): update typing and linting (#425) # change advances #305 - replace `flake8` with `ruff` and install `pre-commit` - run linters as part of a separate job (to separate linting from testing) - add a draft of `mypy` setup but not try to fix stuff because it's a complicated procedure to fix everything - this PR already reformatted a lot of code --------- Co-authored-by: Balint Bartha Co-authored-by: David Ankin --- .github/workflows/ci-community.yml | 4 +- .github/workflows/ci-core.yml | 4 +- .github/workflows/ci-lint.yml | 28 ++ .pre-commit-config.yaml | 34 ++ Dockerfile | 3 +- Dockerfile.diagnostics | 2 +- INDEX.rst | 46 +- Makefile | 10 +- conf.py | 57 +-- core/README.rst | 2 +- core/testcontainers/core/container.py | 49 +- core/testcontainers/core/docker_client.py | 59 ++- core/testcontainers/core/generic.py | 18 +- core/testcontainers/core/utils.py | 15 +- core/testcontainers/core/waiting_utils.py | 25 +- core/tests/test_core.py | 7 +- core/tests/test_docker_client.py | 11 +- core/tests/test_docker_in_docker.py | 18 +- core/tests/test_new_docker_api.py | 6 +- diagnostics.py | 23 - get_requirements.py | 98 ---- .../testcontainers/arangodb/__init__.py | 39 +- modules/arangodb/tests/test_arangodb.py | 45 +- .../testcontainers/azurite/__init__.py | 62 ++- modules/azurite/tests/test_azurite.py | 6 +- .../testcontainers/clickhouse/__init__.py | 13 +- modules/clickhouse/tests/test_clickhouse.py | 3 +- .../testcontainers/elasticsearch/__init__.py | 16 +- .../elasticsearch/tests/test_elasticsearch.py | 7 +- .../google/testcontainers/google/__init__.py | 2 +- .../google/testcontainers/google/pubsub.py | 20 +- modules/google/tests/test_google.py | 8 +- modules/k3s/testcontainers/k3s/__init__.py | 14 +- .../kafka/testcontainers/kafka/__init__.py | 38 +- modules/kafka/tests/test_kafka.py | 8 +- .../testcontainers/keycloak/__init__.py | 33 +- modules/keycloak/tests/test_keycloak.py | 2 +- .../testcontainers/localstack/__init__.py | 26 +- modules/localstack/tests/test_localstack.py | 8 +- .../minio/testcontainers/minio/__init__.py | 22 +- .../testcontainers/mongodb/__init__.py | 21 +- modules/mongodb/tests/test_mongodb.py | 17 +- .../mssql/testcontainers/mssql/__init__.py | 21 +- modules/mssql/tests/test_mssql.py | 13 +- .../mysql/testcontainers/mysql/__init__.py | 35 +- modules/mysql/tests/test_mysql.py | 28 +- .../neo4j/testcontainers/neo4j/__init__.py | 22 +- modules/neo4j/tests/test_neo4j.py | 23 +- .../nginx/testcontainers/nginx/__init__.py | 6 +- modules/nginx/tests/test_nginx.py | 4 +- .../testcontainers/opensearch/__init__.py | 18 +- modules/opensearch/tests/test_opensearch.py | 4 +- .../oracle/testcontainers/oracle/__init__.py | 5 +- modules/oracle/tests/test_oracle.py | 15 +- .../testcontainers/postgres/__init__.py | 24 +- modules/postgres/tests/test_postgres.py | 1 + .../testcontainers/rabbitmq/__init__.py | 14 +- modules/rabbitmq/tests/test_rabbitmq.py | 21 +- .../redis/testcontainers/redis/__init__.py | 9 +- modules/redis/tests/test_redis.py | 11 +- .../testcontainers/selenium/__init__.py | 27 +- modules/selenium/tests/test_selenium.py | 5 +- poetry.lock | 111 +++-- pyproject.toml | 115 ++++- requirements/macos-latest-3.10.txt | 442 ----------------- requirements/ubuntu-latest-3.10.txt | 449 ----------------- requirements/ubuntu-latest-3.11.txt | 438 ---------------- requirements/ubuntu-latest-3.7.txt | 467 ------------------ requirements/ubuntu-latest-3.8.txt | 452 ----------------- requirements/ubuntu-latest-3.9.txt | 450 ----------------- requirements/windows-latest-3.10.txt | 453 ----------------- scripts/diagnostics.py | 25 + setup.cfg | 3 - 73 files changed, 832 insertions(+), 3808 deletions(-) create mode 100644 .github/workflows/ci-lint.yml create mode 100644 .pre-commit-config.yaml delete mode 100644 diagnostics.py delete mode 100644 get_requirements.py delete mode 100644 requirements/macos-latest-3.10.txt delete mode 100644 requirements/ubuntu-latest-3.10.txt delete mode 100644 requirements/ubuntu-latest-3.11.txt delete mode 100644 requirements/ubuntu-latest-3.7.txt delete mode 100644 requirements/ubuntu-latest-3.8.txt delete mode 100644 requirements/ubuntu-latest-3.9.txt delete mode 100644 requirements/windows-latest-3.10.txt create mode 100644 scripts/diagnostics.py delete mode 100644 setup.cfg diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 337b90661..92e58e518 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -70,7 +70,5 @@ jobs: cache: poetry - name: Install Python dependencies run: poetry install -E ${{ matrix.module }} - - name: Run linter - run: make modules/${{ matrix.module }}/lint - name: Run tests - run: make modules/${{ matrix.module }}/tests \ No newline at end of file + run: make modules/${{ matrix.module }}/tests diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 56a45cd33..c96619868 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -25,9 +25,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: poetry - name: Install Python dependencies - run: poetry install - - name: Run linter - run: make core/lint + run: poetry install --all-extras - name: Run twine check run: poetry build && poetry run twine check dist/*.tar.gz - name: Run tests diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml new file mode 100644 index 000000000..6261002b3 --- /dev/null +++ b/.github/workflows/ci-lint.yml @@ -0,0 +1,28 @@ +# Contrinuous Integration for the core package + +name: lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + all: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Poetry + run: pipx install poetry + - name: Setup python 3.9 + uses: actions/setup-python@v5 + with: + python-version: 3.9 + cache: poetry + - name: Install Python dependencies + run: poetry install + - name: Install pre-commit + run: pip install pre-commit + - name: Run linter + run: pre-commit run -a diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..0d2a53b63 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +default_language_version: + python: python3.9 + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 'v4.5.0' + hooks: + - id: check-toml + - id: trailing-whitespace + - id: end-of-file-fixer + + - repo: https://github.com/psf/black-pre-commit-mirror + rev: '24.1.1' + hooks: + - id: black + args: [ '--config', 'pyproject.toml' ] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.1.14' + hooks: + - id: ruff + # Explicitly setting config to prevent Ruff from using `pyproject.toml` in sub packages. + args: [ '--fix', '--exit-non-zero-on-fix', '--config', 'pyproject.toml' ] + +# - repo: local +# hooks: +# - id: mypy +# name: mypy +# entry: poetry run mypy +# args: ["--config-file", "pyproject.toml"] +# files: "core" # start with the core being type checked +# language: system +# types: [ python ] +# require_serial: true diff --git a/Dockerfile b/Dockerfile index fb4ad5d24..4172f86fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,7 @@ RUN pip install --upgrade pip \ && apt-get install -y \ freetds-dev \ && rm -rf /var/lib/apt/lists/* -ARG version=3.8 -COPY requirements/${version}.txt requirements.txt +COPY build/requirements.txt requirements.txt COPY setup.py README.rst ./ RUN pip install -r requirements.txt COPY . . diff --git a/Dockerfile.diagnostics b/Dockerfile.diagnostics index 9d1bba2cd..687b447c5 100644 --- a/Dockerfile.diagnostics +++ b/Dockerfile.diagnostics @@ -4,4 +4,4 @@ FROM python:${version} WORKDIR /workspace COPY core core RUN pip install --no-cache-dir -e core -COPY diagnostics.py . +COPY scripts/diagnostics.py . diff --git a/INDEX.rst b/INDEX.rst index 87c413355..be5e3d1cd 100644 --- a/INDEX.rst +++ b/INDEX.rst @@ -58,11 +58,12 @@ The snippet above will spin up a postgres database in a container. The :code:`ge Installation ------------ -The suite of testcontainers packages is available on `PyPI `_, and individual packages can be installed using :code:`pip`. We recommend installing the package you need by running :code:`pip install testcontainers-`, e.g., :code:`pip install testcontainers-postgres`. +The suite of testcontainers packages is available on `PyPI `_, +and individual packages can be installed using :code:`pip`. -.. note:: +Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsutainable to maintain ownership. - For backwards compatibility, packages can also be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[postgres]`. +Instead packages can be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[postgres]`. Docker in Docker (DinD) @@ -80,8 +81,8 @@ We recommend you use a `virtual environment /tests Package Structure ^^^^^^^^^^^^^^^^^ @@ -90,23 +91,24 @@ Testcontainers is a collection of `implicit namespace packages 'DockerContainer': + def with_env(self, key: str, value: str) -> "DockerContainer": self.env[key] = value return self - def with_bind_ports(self, container: int, host: int = None) -> 'DockerContainer': + def with_bind_ports(self, container: int, host: Optional[int] = None) -> "DockerContainer": self.ports[container] = host return self - def with_exposed_ports(self, *ports: int) -> 'DockerContainer': + def with_exposed_ports(self, *ports: int) -> "DockerContainer": for port in ports: self.ports[port] = None return self - def with_kwargs(self, **kwargs) -> 'DockerContainer': + def with_kwargs(self, **kwargs) -> "DockerContainer": self._kwargs = kwargs return self - def maybe_emulate_amd64(self) -> 'DockerContainer': + def maybe_emulate_amd64(self) -> "DockerContainer": if is_arm(): - return self.with_kwargs(platform='linux/amd64') + return self.with_kwargs(platform="linux/amd64") return self - def start(self) -> 'DockerContainer': + def start(self) -> "DockerContainer": logger.info("Pulling image %s", self.image) docker_client = self.get_docker_client() self._container = docker_client.run( - self.image, command=self._command, detach=True, environment=self.env, ports=self.ports, - name=self._name, volumes=self.volumes, **self._kwargs + self.image, + command=self._command, + detach=True, + environment=self.env, + ports=self.ports, + name=self._name, + volumes=self.volumes, + **self._kwargs ) logger.info("Container started: %s", self._container.short_id) return self @@ -71,7 +78,7 @@ def start(self) -> 'DockerContainer': def stop(self, force=True, delete_volume=True) -> None: self._container.remove(force=force, v=delete_volume) - def __enter__(self) -> 'DockerContainer': + def __enter__(self) -> "DockerContainer": return self.start() def __exit__(self, exc_type, exc_val, exc_tb) -> None: @@ -82,11 +89,9 @@ def __del__(self) -> None: __del__ runs when Python attempts to garbage collect the object. In case of leaky test design, we still attempt to clean up the container. """ - try: + with contextlib.suppress(Exception): if self._container is not None: self.stop() - finally: - pass def get_container_host_ip(self) -> str: # infer from docker host @@ -94,7 +99,7 @@ def get_container_host_ip(self) -> str: if not host: return "localhost" # see https://github.com/testcontainers/testcontainers-python/issues/415 - if host == "localnpipe" and "Windows" == system(): + if host == "localnpipe" and system() == "Windows": return "localhost" # check testcontainers itself runs inside docker container @@ -122,16 +127,16 @@ def get_exposed_port(self, port: int) -> str: return port return mapped_port - def with_command(self, command: str) -> 'DockerContainer': + def with_command(self, command: str) -> "DockerContainer": self._command = command return self - def with_name(self, name: str) -> 'DockerContainer': + def with_name(self, name: str) -> "DockerContainer": self._name = name return self - def with_volume_mapping(self, host: str, container: str, mode: str = 'ro') -> 'DockerContainer': - mapping = {'bind': container, 'mode': mode} + def with_volume_mapping(self, host: str, container: str, mode: str = "ro") -> "DockerContainer": + mapping = {"bind": container, "mode": mode} self.volumes[host] = mapping return self @@ -141,12 +146,12 @@ def get_wrapped_container(self) -> Container: def get_docker_client(self) -> DockerClient: return self._docker - def get_logs(self) -> Tuple[str, str]: + def get_logs(self) -> tuple[str, str]: if not self._container: raise ContainerStartException("Container should be started before getting logs") return self._container.logs(stderr=False), self._container.logs(stdout=False) - def exec(self, command) -> Tuple[int, str]: + def exec(self, command) -> tuple[int, str]: if not self._container: raise ContainerStartException("Container should be started before executing a command") return self._container.exec_run(command) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index fb54838fa..3c724ac3c 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -14,7 +14,7 @@ import functools as ft import os import urllib -from typing import List, Optional, Union +from typing import Optional, Union import docker from docker.errors import NotFound @@ -31,8 +31,7 @@ def _stop_container(container: Container) -> None: except NotFound: pass except Exception as ex: - LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, - container.image, ex) + LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex) class DockerClient: @@ -45,19 +44,27 @@ def __init__(self, **kwargs) -> None: @ft.wraps(ContainerCollection.run) def run( - self, image: str, - command: Union[str, List[str]] = None, - environment: Optional[dict] = None, - ports: Optional[dict] = None, - detach: bool = False, - stdout: bool = True, - stderr: bool = False, - remove: bool = False, - **kwargs + self, + image: str, + command: Optional[Union[str, list[str]]] = None, + environment: Optional[dict] = None, + ports: Optional[dict] = None, + detach: bool = False, + stdout: bool = True, + stderr: bool = False, + remove: bool = False, + **kwargs, ) -> Container: container = self.client.containers.run( - image, command=command, stdout=stdout, stderr=stderr, remove=remove, detach=detach, - environment=environment, ports=ports, **kwargs + image, + command=command, + stdout=stdout, + stderr=stderr, + remove=remove, + detach=detach, + environment=environment, + ports=ports, + **kwargs, ) if detach: atexit.register(_stop_container, container) @@ -69,17 +76,16 @@ def port(self, container_id: str, port: int) -> int: """ port_mappings = self.client.api.port(container_id, port) if not port_mappings: - raise ConnectionError(f'Port mapping for container {container_id} and port {port} is ' - 'not available') + raise ConnectionError(f"Port mapping for container {container_id} and port {port} is " "not available") return port_mappings[0]["HostPort"] def get_container(self, container_id: str) -> Container: """ Get the container with a given identifier. """ - containers = self.client.api.containers(filters={'id': container_id}) + containers = self.client.api.containers(filters={"id": container_id}) if not containers: - raise RuntimeError(f'Could not get container with id {container_id}') + raise RuntimeError(f"Could not get container with id {container_id}") return containers[0] def bridge_ip(self, container_id: str) -> str: @@ -87,14 +93,14 @@ def bridge_ip(self, container_id: str) -> str: Get the bridge ip address for a container. """ container = self.get_container(container_id) - return container['NetworkSettings']['Networks']['bridge']['IPAddress'] + return container["NetworkSettings"]["Networks"]["bridge"]["IPAddress"] def gateway_ip(self, container_id: str) -> str: """ Get the gateway ip address for a container. """ container = self.get_container(container_id) - return container['NetworkSettings']['Networks']['bridge']['Gateway'] + return container["NetworkSettings"]["Networks"]["bridge"]["Gateway"] def host(self) -> str: """ @@ -102,7 +108,7 @@ def host(self) -> str: """ # https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 # if os env TC_HOST is set, use it - host = os.environ.get('TC_HOST') + host = os.environ.get("TC_HOST") if host: return host try: @@ -110,11 +116,10 @@ def host(self) -> str: except ValueError: return None - if 'http' in url.scheme or 'tcp' in url.scheme: + if "http" in url.scheme or "tcp" in url.scheme: return url.hostname - if 'unix' in url.scheme or 'npipe' in url.scheme: - if inside_container(): - ip_address = default_gateway_ip() - if ip_address: - return ip_address + if inside_container() and ("unix" in url.scheme or "npipe" in url.scheme): + ip_address = default_gateway_ip() + if ip_address: + return ip_address return "localhost" diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index e63478064..21bf9d7e4 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -20,6 +20,7 @@ ADDITIONAL_TRANSIENT_ERRORS = [] try: from sqlalchemy.exc import DBAPIError + ADDITIONAL_TRANSIENT_ERRORS.append(DBAPIError) except ImportError: pass @@ -29,18 +30,27 @@ class DbContainer(DockerContainer): """ Generic database container. """ + @wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS) def _connect(self) -> None: import sqlalchemy + engine = sqlalchemy.create_engine(self.get_connection_url()) engine.connect() def get_connection_url(self) -> str: raise NotImplementedError - def _create_connection_url(self, dialect: str, username: str, password: str, - host: Optional[str] = None, port: Optional[int] = None, - dbname: Optional[str] = None, **kwargs) -> str: + def _create_connection_url( + self, + dialect: str, + username: str, + password: str, + host: Optional[str] = None, + port: Optional[int] = None, + dbname: Optional[str] = None, + **kwargs, + ) -> str: if raise_for_deprecated_parameter(kwargs, "db_name", "dbname"): raise ValueError(f"Unexpected arguments: {','.join(kwargs)}") if self._container is None: @@ -52,7 +62,7 @@ def _create_connection_url(self, dialect: str, username: str, password: str, url = f"{url}/{dbname}" return url - def start(self) -> 'DbContainer': + def start(self) -> "DbContainer": self._configure() super().start() self._connect() diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index 9a02747b0..5ca1c2f7d 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -29,19 +29,19 @@ def os_name() -> str: def is_mac() -> bool: - return MAC == os_name() + return os_name() == MAC def is_linux() -> bool: - return LINUX == os_name() + return os_name() == LINUX def is_windows() -> bool: - return WIN == os_name() + return os_name() == WIN def is_arm() -> bool: - return platform.machine() in ('arm64', 'aarch64') + return platform.machine() in ("arm64", "aarch64") def inside_container() -> bool: @@ -50,7 +50,7 @@ def inside_container() -> bool: https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25 """ - return os.path.exists('/.dockerenv') + return os.path.exists("/.dockerenv") def default_gateway_ip() -> str: @@ -62,11 +62,10 @@ def default_gateway_ip() -> str: """ cmd = ["sh", "-c", "ip route|awk '/default/ { print $3 }'"] try: - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ip_address = process.communicate()[0] if ip_address and process.returncode == 0: - return ip_address.decode('utf-8').strip().strip('\n') + return ip_address.decode("utf-8").strip().strip("\n") except subprocess.SubprocessError: return None diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 5e9aa33c1..ea52683d5 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -15,7 +15,7 @@ import re import time import traceback -from typing import Any, Callable, Iterable, Mapping, Optional, TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Any, Callable, Optional, Union import wrapt @@ -45,12 +45,11 @@ def wait_container_is_ready(*transient_exceptions) -> Callable: transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) @wrapt.decorator - def wrapper(wrapped: Callable, instance: Any, args: Iterable, kwargs: Mapping) -> Any: + def wrapper(wrapped: Callable, instance: Any, args: list, kwargs: dict) -> Any: from testcontainers.core.container import DockerContainer if isinstance(instance, DockerContainer): - logger.info("Waiting for container %s with image %s to be ready ...", - instance._container, instance.image) + logger.info("Waiting for container %s with image %s to be ready ...", instance._container, instance.image) else: logger.info("Waiting for %s to be ready ...", instance) @@ -59,13 +58,15 @@ def wrapper(wrapped: Callable, instance: Any, args: Iterable, kwargs: Mapping) - try: return wrapped(*args, **kwargs) except transient_exceptions as e: - logger.debug(f"Connection attempt '{attempt_no + 1}' of '{config.MAX_TRIES + 1}' " - f"failed: {traceback.format_exc()}") + logger.debug( + f"Connection attempt '{attempt_no + 1}' of '{config.MAX_TRIES + 1}' " + f"failed: {traceback.format_exc()}" + ) time.sleep(config.SLEEP_TIME) exception = e raise TimeoutError( - f'Wait time ({config.TIMEOUT}s) exceeded for {wrapped.__name__}(args: {args}, kwargs: ' - f'{kwargs}). Exception: {exception}' + f"Wait time ({config.TIMEOUT}s) exceeded for {wrapped.__name__}(args: {args}, kwargs: " + f"{kwargs}). Exception: {exception}" ) return wrapper @@ -76,8 +77,9 @@ def wait_for(condition: Callable[..., bool]) -> bool: return condition() -def wait_for_logs(container: "DockerContainer", predicate: Union[Callable, str], - timeout: Optional[float] = None, interval: float = 1) -> float: +def wait_for_logs( + container: "DockerContainer", predicate: Union[Callable, str], timeout: Optional[float] = None, interval: float = 1 +) -> float: """ Wait for the container to emit logs satisfying the predicate. @@ -102,6 +104,5 @@ def wait_for_logs(container: "DockerContainer", predicate: Union[Callable, str], if predicate(stdout) or predicate(stderr): return duration if timeout and duration > timeout: - raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " - "seconds") + raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " "seconds") time.sleep(interval) diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 01e9c97c8..a00be1f02 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -5,9 +5,8 @@ def test_timeout_is_raised_when_waiting_for_logs(): - with pytest.raises(TimeoutError): - with DockerContainer("alpine").with_command("sleep 2") as container: - wait_for_logs(container, "Hello from Docker!", timeout=1e-3) + with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container: + wait_for_logs(container, "Hello from Docker!", timeout=1e-3) def test_garbage_collection_is_defensive(): @@ -29,4 +28,4 @@ def test_can_get_logs(): with DockerContainer("hello-world") as container: wait_for_logs(container, "Hello from Docker!") stdout, stderr = container.get_logs() - assert stdout, 'There should be something on stdout' + assert stdout, "There should be something on stdout" diff --git a/core/tests/test_docker_client.py b/core/tests/test_docker_client.py index ccd640e22..23f92e9e5 100644 --- a/core/tests/test_docker_client.py +++ b/core/tests/test_docker_client.py @@ -1,14 +1,13 @@ from unittest.mock import MagicMock, patch + import docker -from testcontainers.core.docker_client import DockerClient from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient def test_docker_client_from_env(): - test_kwargs = dict( - test_kw="test_value" - ) + test_kwargs = {"test_kw": "test_value"} mock_docker = MagicMock(spec=docker) with patch("testcontainers.core.docker_client.docker", mock_docker): DockerClient(**test_kwargs) @@ -17,9 +16,7 @@ def test_docker_client_from_env(): def test_container_docker_client_kw(): - test_kwargs = dict( - test_kw="test_value" - ) + test_kwargs = {"test_kw": "test_value"} mock_docker = MagicMock(spec=docker) with patch("testcontainers.core.docker_client.docker", mock_docker): DockerContainer(image="", docker_client_kw=test_kwargs) diff --git a/core/tests/test_docker_in_docker.py b/core/tests/test_docker_in_docker.py index b048a78ab..95392408d 100644 --- a/core/tests/test_docker_in_docker.py +++ b/core/tests/test_docker_in_docker.py @@ -1,4 +1,5 @@ import pytest + from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient from testcontainers.core.waiting_utils import wait_for_logs @@ -12,7 +13,7 @@ def test_wait_for_logs_docker_in_docker(): not_really_dind = client.run( image="alpine/socat", command="tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock", - volumes={'/var/run/docker.sock': {'bind': '/var/run/docker.sock'}}, + volumes={"/var/run/docker.sock": {"bind": "/var/run/docker.sock"}}, detach=True, ) @@ -21,22 +22,17 @@ def test_wait_for_logs_docker_in_docker(): # get ip address for DOCKER_HOST # avoiding DockerContainer class here to prevent code changes affecting the test specs = client.get_container(not_really_dind.id) - docker_host_ip = specs['NetworkSettings']['Networks']['bridge']['IPAddress'] + docker_host_ip = specs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"] docker_host = f"tcp://{docker_host_ip}:2375" with DockerContainer( - image="hello-world", - docker_client_kw={ - "environment": { - "DOCKER_HOST": docker_host, - "DOCKER_CERT_PATH": "", - "DOCKER_TLS_VERIFY": "" - } - }) as container: + image="hello-world", + docker_client_kw={"environment": {"DOCKER_HOST": docker_host, "DOCKER_CERT_PATH": "", "DOCKER_TLS_VERIFY": ""}}, + ) as container: assert container.get_container_host_ip() == docker_host_ip wait_for_logs(container, "Hello from Docker!") stdout, stderr = container.get_logs() - assert stdout, 'There should be something on stdout' + assert stdout, "There should be something on stdout" not_really_dind.stop() not_really_dind.remove() diff --git a/core/tests/test_new_docker_api.py b/core/tests/test_new_docker_api.py index 22e69d19d..936efc82b 100644 --- a/core/tests/test_new_docker_api.py +++ b/core/tests/test_new_docker_api.py @@ -16,13 +16,13 @@ def test_docker_custom_image(): def test_docker_kwargs(): code_dir = Path(__file__).parent container_first = DockerContainer("nginx:latest") - container_first.with_volume_mapping(code_dir, '/code') + container_first.with_volume_mapping(code_dir, "/code") container_second = DockerContainer("nginx:latest") with container_first: container_second.with_kwargs(volumes_from=[container_first._container.short_id]) with container_second: - files_first = container_first.exec('ls /code').output.decode('utf-8').strip() - files_second = container_second.exec('ls /code').output.decode('utf-8').strip() + files_first = container_first.exec("ls /code").output.decode("utf-8").strip() + files_second = container_second.exec("ls /code").output.decode("utf-8").strip() assert files_first == files_second diff --git a/diagnostics.py b/diagnostics.py deleted file mode 100644 index 79d306fba..000000000 --- a/diagnostics.py +++ /dev/null @@ -1,23 +0,0 @@ -import json -from testcontainers.core import utils -from testcontainers.core.container import DockerContainer - - -result = { - 'is_linux': utils.is_linux(), - 'is_mac': utils.is_mac(), - 'is_windows': utils.is_windows(), - 'inside_container': utils.inside_container(), - 'default_gateway_ip': utils.default_gateway_ip(), -} - -with DockerContainer('alpine:latest') as container: - client = container.get_docker_client() - result.update({ - 'container_host_ip': container.get_container_host_ip(), - 'docker_client_gateway_ip': client.gateway_ip(container._container.id), - 'docker_client_bridge_ip': client.bridge_ip(container._container.id), - 'docker_client_host': client.host(), - }) - -print(json.dumps(result, indent=2)) diff --git a/get_requirements.py b/get_requirements.py deleted file mode 100644 index 549f05efd..000000000 --- a/get_requirements.py +++ /dev/null @@ -1,98 +0,0 @@ -import argparse -import io -import pathlib -import requests -import shutil -import tempfile -import zipfile - - -def __main__() -> None: - parser = argparse.ArgumentParser() - parser.add_argument("--owner", default="testcontainers") - parser.add_argument("--repo", default="testcontainers-python") - parser.add_argument("--run", help="GitHub Action run id") - parser.add_argument("--pr", help="GitHub PR number") - parser.add_argument("--branch", default="main") - parser.add_argument("--token", help="GitHub autentication token") - args = parser.parse_args() - - # Get an access token. - if args.token: - token = args.token - elif (path := pathlib.Path(".github-token")).is_file(): - token = path.read_text().strip() - else: - token = input("We need a GitHub access token to fetch the requirements. Please visit " - "https://github.com/settings/tokens/new, create a token with `public_repo` " - "scope, and paste it here: ").strip() - cache = input("Do you want to cache the token in a `.github-token` file [Ny]? ") - if cache.lower().startswith("y"): - path.write_text(token) - - headers = { - "Authorization": f"Bearer {token}", - } - base_url = f"https://api.github.com/repos/{args.owner}/{args.repo}" - - if args.run: # Run id was specified. - run = args.run - elif args.pr: # PR was specified, let's get the most recent run id. - print(f"Fetching most recent commit for PR #{args.pr}.") - response = requests.get(f"{base_url}/pulls/{args.pr}", headers=headers) - response.raise_for_status() - response = response.json() - head_sha = response["head"]["sha"] - else: # Nothing was specified, let's get the most recent run id on the main branch. - print(f"Fetching most recent commit for branch `{args.branch}`.") - response = requests.get(f"{base_url}/branches/{args.branch}", headers=headers) - response.raise_for_status() - response = response.json() - head_sha = response["commit"]["sha"] - - # List all completed runs and find the one that generated the requirements. - response = requests.get(f"{base_url}/actions/runs", headers=headers, params={ - "head_sha": head_sha, - "status": "success", - }) - response.raise_for_status() - response = response.json() - - # Get the requirements run. - runs = [run for run in response["workflow_runs"] if - run["path"].endswith("requirements.yml")] - if not runs: - raise RuntimeError("Could not find a workflow. Has the GitHub Action run completed? If you" - "are a first-time contributor, a contributor has to approve your changes" - "before Actions can run.") - if len(runs) != 1: - raise RuntimeError(f"Could not identify unique workflow run: {runs}") - run = runs[0]["id"] - - # Get all the artifacts. - print(f"fetching artifacts for run {run} ...") - url = f"{base_url}/actions/runs/{run}/artifacts" - response = requests.get(url, headers=headers) - response.raise_for_status() - response = response.json() - artifacts = response["artifacts"] - print(f"Discovered {len(artifacts)} artifacts.") - - # Get the content for each artifact and save it. - for artifact in artifacts: - name: str = artifact["name"] - name = name.removeprefix("requirements-") - print(f"Fetching artifact {name} ...") - response = requests.get(artifact["archive_download_url"], headers=headers) - response.raise_for_status() - with zipfile.ZipFile(io.BytesIO(response.content)) as zip, \ - tempfile.TemporaryDirectory() as tempdir: - zip.extract("requirements.txt", tempdir) - shutil.move(pathlib.Path(tempdir) / "requirements.txt", - pathlib.Path("requirements") / name) - - print("Done.") - - -if __name__ == "__main__": - __main__() diff --git a/modules/arangodb/testcontainers/arangodb/__init__.py b/modules/arangodb/testcontainers/arangodb/__init__.py index f56f1eab7..4977e79ef 100644 --- a/modules/arangodb/testcontainers/arangodb/__init__.py +++ b/modules/arangodb/testcontainers/arangodb/__init__.py @@ -1,12 +1,14 @@ """ ArangoDB container support. """ + +import typing from os import environ + from testcontainers.core.config import TIMEOUT from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_for_logs -import typing class ArangoDbContainer(DbContainer): @@ -35,13 +37,15 @@ class ArangoDbContainer(DbContainer): True """ - def __init__(self, - image: str = "arangodb:latest", - port: int = 8529, - arango_root_password: str = "passwd", - arango_no_auth: typing.Optional[bool] = None, - arango_random_root_password: typing.Optional[bool] = None, - **kwargs) -> None: + def __init__( + self, + image: str = "arangodb:latest", + port: int = 8529, + arango_root_password: str = "passwd", + arango_no_auth: typing.Optional[bool] = None, + arango_random_root_password: typing.Optional[bool] = None, + **kwargs, + ) -> None: """ Args: image: Actual docker image/tag to pull. @@ -62,14 +66,17 @@ def __init__(self, # See https://www.arangodb.com/docs/stable/deployment-single-instance-manual-start.html for # details. We convert to int then to bool because Arango uses the string literal "1" to # indicate flags. - self.arango_no_auth = bool(int(environ.get("ARANGO_NO_AUTH", 0) if arango_no_auth is None - else arango_no_auth)) - self.arango_root_password = environ.get("ARANGO_ROOT_PASSWORD") if arango_root_password is \ - None else arango_root_password - self.arango_random_root_password = bool(int( - environ.get("ARANGO_RANDOM_ROOT_PASSWORD", 0) if arango_random_root_password is None - else arango_random_root_password - )) + self.arango_no_auth = bool(int(environ.get("ARANGO_NO_AUTH", 0) if arango_no_auth is None else arango_no_auth)) + self.arango_root_password = ( + environ.get("ARANGO_ROOT_PASSWORD") if arango_root_password is None else arango_root_password + ) + self.arango_random_root_password = bool( + int( + environ.get("ARANGO_RANDOM_ROOT_PASSWORD", 0) + if arango_random_root_password is None + else arango_random_root_password + ) + ) def _configure(self) -> None: self.with_env("ARANGO_ROOT_PASSWORD", self.arango_root_password) diff --git a/modules/arangodb/tests/test_arangodb.py b/modules/arangodb/tests/test_arangodb.py index 7933c2fd1..6c06b4ca3 100644 --- a/modules/arangodb/tests/test_arangodb.py +++ b/modules/arangodb/tests/test_arangodb.py @@ -1,15 +1,17 @@ """ ArangoDB Container Tests """ + import pytest from arango import ArangoClient from arango.exceptions import DatabaseCreateError, ServerVersionError + from testcontainers.arangodb import ArangoDbContainer -ARANGODB_IMAGE_NAME = 'arangodb' +ARANGODB_IMAGE_NAME = "arangodb" -def arango_test_ops(arango_client, expeced_version, username='root', password=''): +def arango_test_ops(arango_client, expeced_version, username="root", password=""): """ Basic ArangoDB operations to test DB really up and running. """ @@ -48,38 +50,32 @@ def test_docker_run_arango(): """ Test ArangoDB container with default settings. """ - image_version = '3.9.1' - image = f'{ARANGODB_IMAGE_NAME}:{image_version}' - arango_root_password = 'passwd' + image_version = "3.9.1" + image = f"{ARANGODB_IMAGE_NAME}:{image_version}" + arango_root_password = "passwd" with ArangoDbContainer(image) as arango: client = ArangoClient(hosts=arango.get_connection_url()) # Test invalid auth + sys_db = client.db("_system", username="root", password="notTheRightPass") with pytest.raises(DatabaseCreateError): - sys_db = client.db("_system", username="root", password='notTheRightPass') sys_db.create_database("test") - arango_test_ops( - arango_client=client, - expeced_version=image_version, - password=arango_root_password) + arango_test_ops(arango_client=client, expeced_version=image_version, password=arango_root_password) def test_docker_run_arango_without_auth(): """ Test ArangoDB container with ARANGO_NO_AUTH var set. """ - image_version = '3.9.1' - image = f'{ARANGODB_IMAGE_NAME}:{image_version}' + image_version = "3.9.1" + image = f"{ARANGODB_IMAGE_NAME}:{image_version}" with ArangoDbContainer(image, arango_no_auth=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) - arango_test_ops( - arango_client=client, - expeced_version=image_version, - password='') + arango_test_ops(arango_client=client, expeced_version=image_version, password="") def test_docker_run_arango_older_version(): @@ -91,30 +87,27 @@ def test_docker_run_arango_older_version(): we must verify older image tags still supported. (without that logic - we'll face race issues where we try to create & populate DB when ArangoDB not really ready. """ - image_version = '3.1.7' - image = f'{ARANGODB_IMAGE_NAME}:{image_version}' + image_version = "3.1.7" + image = f"{ARANGODB_IMAGE_NAME}:{image_version}" with ArangoDbContainer(image, arango_no_auth=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) - arango_test_ops( - arango_client=client, - expeced_version=image_version, - password='') + arango_test_ops(arango_client=client, expeced_version=image_version, password="") def test_docker_run_arango_random_root_password(): """ Test ArangoDB container with ARANGO_RANDOM_ROOT_PASSWORD var set. """ - image_version = '3.9.1' - image = f'{ARANGODB_IMAGE_NAME}:{image_version}' - arango_root_password = 'passwd' + image_version = "3.9.1" + image = f"{ARANGODB_IMAGE_NAME}:{image_version}" + arango_root_password = "passwd" with ArangoDbContainer(image, arango_random_root_password=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) # Test invalid auth (we don't know the password in random mode) + sys_db = client.db("_system", username="root", password=arango_root_password) with pytest.raises(ServerVersionError): - sys_db = client.db("_system", username='root', password=arango_root_password) assert sys_db.version() == image_version diff --git a/modules/azurite/testcontainers/azurite/__init__.py b/modules/azurite/testcontainers/azurite/__init__.py index 847775c63..969fcf35d 100644 --- a/modules/azurite/testcontainers/azurite/__init__.py +++ b/modules/azurite/testcontainers/azurite/__init__.py @@ -40,23 +40,30 @@ class AzuriteContainer(DockerContainer): ... api_version="2019-12-12" ... ) """ - def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest", *, - blob_service_port: int = 10_000, queue_service_port: int = 10_001, - table_service_port: int = 10_002, account_name: Optional[str] = None, - account_key: Optional[str] = None, **kwargs) \ - -> None: - """ Constructs an AzuriteContainer. + + def __init__( + self, + image: str = "mcr.microsoft.com/azure-storage/azurite:latest", + *, + blob_service_port: int = 10_000, + queue_service_port: int = 10_001, + table_service_port: int = 10_002, + account_name: Optional[str] = None, + account_key: Optional[str] = None, + **kwargs, + ) -> None: + """Constructs an AzuriteContainer. Args: image: Expects an image with tag. **kwargs: Keyword arguments passed to super class. """ super().__init__(image=image, **kwargs) - self.account_name = account_name or os.environ.get( - "AZURITE_ACCOUNT_NAME", "devstoreaccount1") + self.account_name = account_name or os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") self.account_key = account_key or os.environ.get( - "AZURITE_ACCOUNT_KEY", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/" - "K1SZFPTOtr/KBHBeksoGMGw==") + "AZURITE_ACCOUNT_KEY", + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/" "K1SZFPTOtr/KBHBeksoGMGw==", + ) raise_for_deprecated_parameter(kwargs, "ports_to_expose", "container.with_exposed_ports") self.blob_service_port = blob_service_port @@ -68,28 +75,34 @@ def __init__(self, image: str = "mcr.microsoft.com/azure-storage/azurite:latest" def get_connection_string(self) -> str: host_ip = self.get_container_host_ip() - connection_string = f"DefaultEndpointsProtocol=http;" \ - f"AccountName={self.account_name};" \ - f"AccountKey={self.account_key};" + connection_string = ( + f"DefaultEndpointsProtocol=http;" f"AccountName={self.account_name};" f"AccountKey={self.account_key};" + ) if self.blob_service_port in self.ports: - connection_string += f"BlobEndpoint=http://{host_ip}:" \ - f"{self.get_exposed_port(self.blob_service_port)}" \ - f"/{self.account_name};" + connection_string += ( + f"BlobEndpoint=http://{host_ip}:" + f"{self.get_exposed_port(self.blob_service_port)}" + f"/{self.account_name};" + ) if self.queue_service_port in self.ports: - connection_string += f"QueueEndpoint=http://{host_ip}:" \ - f"{self.get_exposed_port(self.queue_service_port)}" \ - f"/{self.account_name};" + connection_string += ( + f"QueueEndpoint=http://{host_ip}:" + f"{self.get_exposed_port(self.queue_service_port)}" + f"/{self.account_name};" + ) if self.table_service_port in self.ports: - connection_string += f"TableEndpoint=http://{host_ip}:" \ - f"{self.get_exposed_port(self.table_service_port)}" \ - f"/{self.account_name};" + connection_string += ( + f"TableEndpoint=http://{host_ip}:" + f"{self.get_exposed_port(self.table_service_port)}" + f"/{self.account_name};" + ) return connection_string - def start(self) -> 'AzuriteContainer': + def start(self) -> "AzuriteContainer": super().start() self._connect() return self @@ -97,5 +110,4 @@ def start(self) -> 'AzuriteContainer': @wait_container_is_ready(OSError) def _connect(self) -> None: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((self.get_container_host_ip(), - int(self.get_exposed_port(next(iter(self.ports)))))) + s.connect((self.get_container_host_ip(), int(self.get_exposed_port(next(iter(self.ports)))))) diff --git a/modules/azurite/tests/test_azurite.py b/modules/azurite/tests/test_azurite.py index 5c92e48ed..74230ab14 100644 --- a/modules/azurite/tests/test_azurite.py +++ b/modules/azurite/tests/test_azurite.py @@ -1,12 +1,12 @@ -from testcontainers.azurite import AzuriteContainer from azure.storage.blob import BlobServiceClient +from testcontainers.azurite import AzuriteContainer + def test_docker_run_azurite(): with AzuriteContainer() as azurite_container: blob_service_client = BlobServiceClient.from_connection_string( - azurite_container.get_connection_string(), - api_version="2019-12-12" + azurite_container.get_connection_string(), api_version="2019-12-12" ) blob_service_client.create_container("test-container") diff --git a/modules/clickhouse/testcontainers/clickhouse/__init__.py b/modules/clickhouse/testcontainers/clickhouse/__init__.py index b78c509cd..147940199 100644 --- a/modules/clickhouse/testcontainers/clickhouse/__init__.py +++ b/modules/clickhouse/testcontainers/clickhouse/__init__.py @@ -40,9 +40,16 @@ class ClickHouseContainer(DbContainer): ... client.execute("select 'working'") [('working',)] """ - 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") diff --git a/modules/clickhouse/tests/test_clickhouse.py b/modules/clickhouse/tests/test_clickhouse.py index 32ac046f7..23e5f4686 100644 --- a/modules/clickhouse/tests/test_clickhouse.py +++ b/modules/clickhouse/tests/test_clickhouse.py @@ -1,4 +1,5 @@ import clickhouse_driver + from testcontainers.clickhouse import ClickHouseContainer @@ -8,4 +9,4 @@ def test_docker_run_clickhouse(): client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) result = client.execute("select 'working'") - assert result == [('working',)] + assert result == [("working",)] diff --git a/modules/elasticsearch/testcontainers/elasticsearch/__init__.py b/modules/elasticsearch/testcontainers/elasticsearch/__init__.py index 546dd9df1..1b943916a 100644 --- a/modules/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/modules/elasticsearch/testcontainers/elasticsearch/__init__.py @@ -13,7 +13,6 @@ import logging import re import urllib -from typing import Dict from urllib.error import URLError from testcontainers.core.container import DockerContainer @@ -32,14 +31,15 @@ def _major_version_from_image_name(image_name: str) -> int: version_string = image_name.split(":")[-1] regex_match = re.compile(r"(\d+)\.\d+\.\d+").match(version_string) if not regex_match: - logging.warning("Could not determine major version from image name '%s'. Will use %s", - image_name, _FALLBACK_VERSION) + logging.warning( + "Could not determine major version from image name '%s'. Will use %s", image_name, _FALLBACK_VERSION + ) return _FALLBACK_VERSION else: return int(regex_match.group(1)) -def _environment_by_version(version: int) -> Dict[str, str]: +def _environment_by_version(version: int) -> dict[str, str]: """Returns environment variables required for each major version to work.""" if version == 6: # This setting is needed to avoid the check for the kernel parameter @@ -76,11 +76,11 @@ class ElasticSearchContainer(DockerContainer): def __init__(self, image: str = "elasticsearch", port: int = 9200, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(ElasticSearchContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) - self.with_env('transport.host', '127.0.0.1') - self.with_env('http.host', '0.0.0.0') + self.with_env("transport.host", "127.0.0.1") + self.with_env("http.host", "0.0.0.0") major_version = _major_version_from_image_name(image) for key, value in _environment_by_version(major_version).items(): @@ -95,7 +95,7 @@ def _connect(self) -> None: def get_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) - return f'http://{host}:{port}' + return f"http://{host}:{port}" def start(self) -> "ElasticSearchContainer": super().start() diff --git a/modules/elasticsearch/tests/test_elasticsearch.py b/modules/elasticsearch/tests/test_elasticsearch.py index 924dfeb88..e174ec47b 100644 --- a/modules/elasticsearch/tests/test_elasticsearch.py +++ b/modules/elasticsearch/tests/test_elasticsearch.py @@ -1,13 +1,14 @@ import json import urllib.request + import pytest from testcontainers.elasticsearch import ElasticSearchContainer # The versions below were the current supported versions at time of writing (2022-08-11) -@pytest.mark.parametrize('version', ['6.8.23', '7.17.5', '8.3.3']) +@pytest.mark.parametrize("version", ["6.8.23", "7.17.5", "8.3.3"]) def test_docker_run_elasticsearch(version): - with ElasticSearchContainer(f'elasticsearch:{version}', mem_limit='3G') as es: + with ElasticSearchContainer(f"elasticsearch:{version}", mem_limit="3G") as es: resp = urllib.request.urlopen(es.get_url()) - assert json.loads(resp.read().decode())['version']['number'] == version + assert json.loads(resp.read().decode())["version"]["number"] == version diff --git a/modules/google/testcontainers/google/__init__.py b/modules/google/testcontainers/google/__init__.py index 71665bea6..b28f2ed48 100644 --- a/modules/google/testcontainers/google/__init__.py +++ b/modules/google/testcontainers/google/__init__.py @@ -1 +1 @@ -from .pubsub import PubSubContainer # noqa +from .pubsub import PubSubContainer # noqa: F401 diff --git a/modules/google/testcontainers/google/pubsub.py b/modules/google/testcontainers/google/pubsub.py index bdce91c64..78c6929e2 100644 --- a/modules/google/testcontainers/google/pubsub.py +++ b/modules/google/testcontainers/google/pubsub.py @@ -10,12 +10,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from google.cloud import pubsub import os -from testcontainers.core.container import DockerContainer -from typing import Type from unittest.mock import patch +from google.cloud import pubsub +from testcontainers.core.container import DockerContainer + class PubSubContainer(DockerContainer): """ @@ -38,20 +38,20 @@ class PubSubContainer(DockerContainer): ... topic_path = publisher.topic_path(pubsub.project, "my-topic") ... topic = publisher.create_topic(name=topic_path) """ - def __init__(self, image: str = "google/cloud-sdk:emulators", project: str = "test-project", - port: int = 8432, **kwargs) -> None: - super(PubSubContainer, self).__init__(image=image, **kwargs) + + def __init__( + self, image: str = "google/cloud-sdk:emulators", project: str = "test-project", port: int = 8432, **kwargs + ) -> None: + super().__init__(image=image, **kwargs) self.project = project self.port = port self.with_exposed_ports(self.port) - self.with_command( - f"gcloud beta emulators pubsub start --project={project} --host-port=0.0.0.0:{port}" - ) + self.with_command(f"gcloud beta emulators pubsub start --project={project} --host-port=0.0.0.0:{port}") def get_pubsub_emulator_host(self) -> str: return f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" - def _get_client(self, cls: Type, **kwargs) -> dict: + def _get_client(self, cls: type, **kwargs) -> dict: with patch.dict(os.environ, PUBSUB_EMULATOR_HOST=self.get_pubsub_emulator_host()): return cls(**kwargs) diff --git a/modules/google/tests/test_google.py b/modules/google/tests/test_google.py index 6fa506e26..780f5fdd6 100644 --- a/modules/google/tests/test_google.py +++ b/modules/google/tests/test_google.py @@ -1,7 +1,8 @@ -from testcontainers.google import PubSubContainer -from testcontainers.core.waiting_utils import wait_for_logs from queue import Queue +from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.google import PubSubContainer + def test_pubsub_container(): pubsub: PubSubContainer @@ -14,8 +15,7 @@ def test_pubsub_container(): # Create a subscription subscriber = pubsub.get_subscriber_client() - subscription_path = subscriber.subscription_path(pubsub.project, - "my-subscription") + subscription_path = subscriber.subscription_path(pubsub.project, "my-subscription") subscriber.create_subscription(name=subscription_path, topic=topic_path) # Publish a message diff --git a/modules/k3s/testcontainers/k3s/__init__.py b/modules/k3s/testcontainers/k3s/__init__.py index 48c9d0959..045e2eb5d 100644 --- a/modules/k3s/testcontainers/k3s/__init__.py +++ b/modules/k3s/testcontainers/k3s/__init__.py @@ -38,9 +38,9 @@ class K3SContainer(DockerContainer): RANCHER_WEBHOOK_PORT = 8443 def __init__(self, image="rancher/k3s:latest", **kwargs) -> None: - super(K3SContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.with_exposed_ports(self.KUBE_SECURE_PORT, self.RANCHER_WEBHOOK_PORT) - self.with_env("K3S_URL", f'https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}') + self.with_env("K3S_URL", f"https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}") self.with_command("server --disable traefik --tls-san=" + self.get_container_host_ip()) self.with_kwargs(privileged=True, tmpfs={"/run": "", "/var/run": ""}) self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw") @@ -57,9 +57,9 @@ def config_yaml(self) -> str: """This function returns the kubernetes config yaml which can be used to initialise k8s client """ - execution = self.get_wrapped_container().exec_run(['cat', '/etc/rancher/k3s/k3s.yaml']) - config_yaml = execution.output.decode('utf-8') \ - .replace(f'https://127.0.0.1:{self.KUBE_SECURE_PORT}', - f'https://{self.get_container_host_ip()}:' - f'{self.get_exposed_port(self.KUBE_SECURE_PORT)}') + execution = self.get_wrapped_container().exec_run(["cat", "/etc/rancher/k3s/k3s.yaml"]) + config_yaml = execution.output.decode("utf-8").replace( + f"https://127.0.0.1:{self.KUBE_SECURE_PORT}", + f"https://{self.get_container_host_ip()}:" f"{self.get_exposed_port(self.KUBE_SECURE_PORT)}", + ) return config_yaml diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index 49c362c20..399839433 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -4,8 +4,7 @@ from textwrap import dedent from kafka import KafkaConsumer -from kafka.errors import KafkaError, UnrecognizedBrokerVersion, NoBrokersAvailable - +from kafka.errors import KafkaError, NoBrokersAvailable, UnrecognizedBrokerVersion from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -24,42 +23,41 @@ class KafkaContainer(DockerContainer): >>> with KafkaContainer() as kafka: ... connection = kafka.get_bootstrap_server() """ - TC_START_SCRIPT = '/tc-start.sh' - def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port: int = 9093, **kwargs) \ - -> None: + TC_START_SCRIPT = "/tc-start.sh" + + def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port: int = 9093, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(KafkaContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) - listeners = f'PLAINTEXT://0.0.0.0:{self.port},BROKER://0.0.0.0:9092' - self.with_env('KAFKA_LISTENERS', listeners) - self.with_env('KAFKA_LISTENER_SECURITY_PROTOCOL_MAP', - 'BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT') - self.with_env('KAFKA_INTER_BROKER_LISTENER_NAME', 'BROKER') + listeners = f"PLAINTEXT://0.0.0.0:{self.port},BROKER://0.0.0.0:9092" + self.with_env("KAFKA_LISTENERS", listeners) + self.with_env("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT") + self.with_env("KAFKA_INTER_BROKER_LISTENER_NAME", "BROKER") - self.with_env('KAFKA_BROKER_ID', '1') - self.with_env('KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR', '1') - self.with_env('KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS', '1') - self.with_env('KAFKA_LOG_FLUSH_INTERVAL_MESSAGES', '10000000') - self.with_env('KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS', '0') + self.with_env("KAFKA_BROKER_ID", "1") + self.with_env("KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR", "1") + self.with_env("KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS", "1") + self.with_env("KAFKA_LOG_FLUSH_INTERVAL_MESSAGES", "10000000") + self.with_env("KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS", "0") def get_bootstrap_server(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) - return f'{host}:{port}' + return f"{host}:{port}" @wait_container_is_ready(UnrecognizedBrokerVersion, NoBrokersAvailable, KafkaError, ValueError) def _connect(self) -> None: bootstrap_server = self.get_bootstrap_server() - consumer = KafkaConsumer(group_id='test', bootstrap_servers=[bootstrap_server]) + consumer = KafkaConsumer(group_id="test", bootstrap_servers=[bootstrap_server]) if not consumer.bootstrap_connected(): raise KafkaError("Unable to connect with kafka container!") def tc_start(self) -> None: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) - listeners = f'PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092' + listeners = f"PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092" data = ( dedent( f""" @@ -76,7 +74,7 @@ def tc_start(self) -> None: """ ) .strip() - .encode('utf-8') + .encode("utf-8") ) self.create_file(data, KafkaContainer.TC_START_SCRIPT) diff --git a/modules/kafka/tests/test_kafka.py b/modules/kafka/tests/test_kafka.py index 5ebe99296..c47aa111d 100644 --- a/modules/kafka/tests/test_kafka.py +++ b/modules/kafka/tests/test_kafka.py @@ -1,4 +1,5 @@ from kafka import KafkaConsumer, KafkaProducer, TopicPartition + from testcontainers.kafka import KafkaContainer @@ -14,12 +15,12 @@ def test_kafka_producer_consumer_custom_port(): def test_kafka_confluent_7_1_3(): - with KafkaContainer(image='confluentinc/cp-kafka:7.1.3') as container: + with KafkaContainer(image="confluentinc/cp-kafka:7.1.3") as container: produce_and_consume_kafka_message(container) def produce_and_consume_kafka_message(container): - topic = 'test-topic' + topic = "test-topic" bootstrap_server = container.get_bootstrap_server() producer = KafkaProducer(bootstrap_servers=[bootstrap_server]) @@ -31,5 +32,4 @@ def produce_and_consume_kafka_message(container): tp = TopicPartition(topic, 0) consumer.assign([tp]) consumer.seek_to_beginning() - assert consumer.end_offsets([tp])[tp] == 1, \ - "Expected exactly one test message to be present on test topic !" + assert consumer.end_offsets([tp])[tp] == 1, "Expected exactly one test message to be present on test topic !" diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index aeb9a4c78..2e8f77383 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -11,13 +11,13 @@ # License for the specific language governing permissions and limitations # under the License. import os +from typing import Optional + import requests from keycloak import KeycloakAdmin - from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready -from typing import Optional class KeycloakContainer(DockerContainer): @@ -33,9 +33,15 @@ class KeycloakContainer(DockerContainer): >>> with KeycloakContainer() as kc: ... keycloak = kc.get_client() """ - def __init__(self, image="jboss/keycloak:latest", username: Optional[str] = None, - password: Optional[str] = None, port: int = 8080) -> None: - super(KeycloakContainer, self).__init__(image=image) + + def __init__( + self, + image="jboss/keycloak:latest", + username: Optional[str] = None, + password: Optional[str] = None, + port: int = 8080, + ) -> None: + super().__init__(image=image) self.username = username or os.environ.get("KEYCLOAK_USER", "test") self.password = password or os.environ.get("KEYCLOAK_PASSWORD", "test") self.port = port @@ -63,15 +69,12 @@ def start(self) -> "KeycloakContainer": return self def get_client(self, **kwargs) -> KeycloakAdmin: - default_kwargs = dict( - server_url=f"{self.get_url()}/auth/", - username=self.username, - password=self.password, - realm_name="master", - verify=True, - ) - kwargs = { - **default_kwargs, - **kwargs + default_kwargs = { + "server_url": f"{self.get_url()}/auth/", + "username": self.username, + "password": self.password, + "realm_name": "master", + "verify": True, } + kwargs = {**default_kwargs, **kwargs} return KeycloakAdmin(**kwargs) diff --git a/modules/keycloak/tests/test_keycloak.py b/modules/keycloak/tests/test_keycloak.py index 900ee0ddf..70eff57cb 100644 --- a/modules/keycloak/tests/test_keycloak.py +++ b/modules/keycloak/tests/test_keycloak.py @@ -5,5 +5,5 @@ @pytest.mark.parametrize("version", ["16.1.1"]) def test_docker_run_keycloak(version: str): - with KeycloakContainer(f'jboss/keycloak:{version}') as kc: + with KeycloakContainer(f"jboss/keycloak:{version}") as kc: kc.get_client().users_count() diff --git a/modules/localstack/testcontainers/localstack/__init__.py b/modules/localstack/testcontainers/localstack/__init__.py index 470d78a00..15cabeab6 100644 --- a/modules/localstack/testcontainers/localstack/__init__.py +++ b/modules/localstack/testcontainers/localstack/__init__.py @@ -10,13 +10,15 @@ # 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 boto3 import functools as ft import os -from testcontainers.core.waiting_utils import wait_for_logs -from testcontainers.core.container import DockerContainer from typing import Any, Optional +import boto3 + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + class LocalStackContainer(DockerContainer): """ @@ -34,9 +36,15 @@ class LocalStackContainer(DockerContainer): >>> tables {'TableNames': [], ...} """ - def __init__(self, image: str = 'localstack/localstack:2.0.1', edge_port: int = 4566, - region_name: Optional[str] = None, **kwargs) -> None: - super(LocalStackContainer, self).__init__(image, **kwargs) + + def __init__( + self, + image: str = "localstack/localstack:2.0.1", + edge_port: int = 4566, + region_name: Optional[str] = None, + **kwargs, + ) -> None: + super().__init__(image, **kwargs) self.edge_port = edge_port self.region_name = region_name or os.environ.get("AWS_DEFAULT_REGION", "us-west-1") self.with_exposed_ports(self.edge_port) @@ -54,7 +62,7 @@ def with_services(self, *services) -> "LocalStackContainer": Returns: self: Container to allow chaining of 'with_*' calls. """ - return self.with_env('SERVICES', ','.join(services)) + return self.with_env("SERVICES", ",".join(services)) def get_url(self) -> str: """ @@ -64,7 +72,7 @@ def get_url(self) -> str: """ host = self.get_container_host_ip() port = self.get_exposed_port(self.edge_port) - return f'http://{host}:{port}' + return f"http://{host}:{port}" @ft.wraps(boto3.client) def get_client(self, name, **kwargs) -> Any: @@ -79,5 +87,5 @@ def get_client(self, name, **kwargs) -> Any: def start(self, timeout: float = 60) -> "LocalStackContainer": super().start() - wait_for_logs(self, r'Ready\.\n', timeout=timeout) + wait_for_logs(self, r"Ready\.\n", timeout=timeout) return self diff --git a/modules/localstack/tests/test_localstack.py b/modules/localstack/tests/test_localstack.py index f587c41db..6801aefdb 100644 --- a/modules/localstack/tests/test_localstack.py +++ b/modules/localstack/tests/test_localstack.py @@ -6,13 +6,13 @@ def test_docker_run_localstack(): with LocalStackContainer() as localstack: - resp = urllib.request.urlopen(f'{localstack.get_url()}/health') - services = json.loads(resp.read().decode())['services'] + resp = urllib.request.urlopen(f"{localstack.get_url()}/health") + services = json.loads(resp.read().decode())["services"] # Check that all services are running - assert all(value == 'available' for value in services.values()) + assert all(value == "available" for value in services.values()) # Check that some of the services keys - assert all(test_service in services for test_service in ['dynamodb', 'sns', 'sqs']) + assert all(test_service in services for test_service in ["dynamodb", "sns", "sqs"]) def test_localstack_boto3(): diff --git a/modules/minio/testcontainers/minio/__init__.py b/modules/minio/testcontainers/minio/__init__.py index 87b91d942..51a7094e3 100644 --- a/modules/minio/testcontainers/minio/__init__.py +++ b/modules/minio/testcontainers/minio/__init__.py @@ -1,10 +1,15 @@ -from minio import Minio -from requests import ConnectionError, Response, get +from typing import TYPE_CHECKING + +from requests import ConnectionError, get +from minio import Minio from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready +if TYPE_CHECKING: + from requests import Response + class MinioContainer(DockerContainer): """ @@ -35,9 +40,14 @@ class MinioContainer(DockerContainer): ... retrieved_content = client.get_object("test", "testfile.txt").data """ - def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", - port: int = 9000, access_key: str = "minioadmin", - secret_key: str = "minioadmin", **kwargs) -> None: + def __init__( + self, + image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", + port: int = 9000, + access_key: str = "minioadmin", + secret_key: str = "minioadmin", + **kwargs, + ) -> None: """ Args: image: Docker image to use for the MinIO container. @@ -46,7 +56,7 @@ def __init__(self, image: str = "minio/minio:RELEASE.2022-12-02T19-19-22Z", secret_key: Secret key for client connections. """ raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(MinioContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.access_key = access_key self.secret_key = secret_key diff --git a/modules/mongodb/testcontainers/mongodb/__init__.py b/modules/mongodb/testcontainers/mongodb/__init__.py index 97db1a3e2..1ff029258 100644 --- a/modules/mongodb/testcontainers/mongodb/__init__.py +++ b/modules/mongodb/testcontainers/mongodb/__init__.py @@ -11,11 +11,13 @@ # License for the specific language governing permissions and limitations # under the License. import os +from typing import Optional + from pymongo import MongoClient + from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready -from typing import Optional class MongoDbContainer(DbContainer): @@ -48,11 +50,18 @@ class MongoDbContainer(DbContainer): ... # Find the restaurant document ... cursor = db.restaurants.find({"borough": "Manhattan"}) """ - def __init__(self, image: str = "mongo:latest", port: int = 27017, - username: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, **kwargs) -> None: + + def __init__( + self, + image: str = "mongo:latest", + port: int = 27017, + username: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + **kwargs + ) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(MongoDbContainer, self).__init__(image=image, **kwargs) + super().__init__(image=image, **kwargs) self.username = username or os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") self.password = password or os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") self.dbname = dbname or os.environ.get("MONGO_DB", "test") @@ -66,7 +75,7 @@ def _configure(self) -> None: def get_connection_url(self) -> str: return self._create_connection_url( - dialect='mongodb', + dialect="mongodb", username=self.username, password=self.password, port=self.port, diff --git a/modules/mongodb/tests/test_mongodb.py b/modules/mongodb/tests/test_mongodb.py index c778a0100..5b9d6be21 100644 --- a/modules/mongodb/tests/test_mongodb.py +++ b/modules/mongodb/tests/test_mongodb.py @@ -1,6 +1,7 @@ +import pytest from pymongo import MongoClient from pymongo.errors import OperationFailure -import pytest + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for from testcontainers.mongodb import MongoDbContainer @@ -8,6 +9,7 @@ def test_docker_generic_db(): with DockerContainer("mongo:latest").with_bind_ports(27017, 27017) as mongo_container: + def connect(): host = mongo_container.get_container_host_ip() port = mongo_container.get_exposed_port(27017) @@ -20,12 +22,12 @@ def connect(): "street": "2 Avenue", "zipcode": "10075", "building": "1480", - "coord": [-73.9557413, 40.7720266] + "coord": [-73.9557413, 40.7720266], }, "borough": "Manhattan", "cuisine": "Italian", "name": "Vella", - "restaurant_id": "41704620" + "restaurant_id": "41704620", } ) assert result.inserted_id @@ -42,22 +44,21 @@ def test_docker_run_mongodb(): "street": "2 Avenue", "zipcode": "10075", "building": "1480", - "coord": [-73.9557413, 40.7720266] + "coord": [-73.9557413, 40.7720266], }, "borough": "Manhattan", "cuisine": "Italian", "name": "Vella", - "restaurant_id": "41704620" + "restaurant_id": "41704620", } db.restaurants.insert_one(doc) cursor = db.restaurants.find({"borough": "Manhattan"}) - assert cursor.next()['restaurant_id'] == doc['restaurant_id'] + assert cursor.next()["restaurant_id"] == doc["restaurant_id"] def test_docker_run_mongodb_connect_without_credentials(): with MongoDbContainer() as mongo: - connection_url = f"mongodb://{mongo.get_container_host_ip()}:" \ - f"{mongo.get_exposed_port(mongo.port)}" + connection_url = f"mongodb://{mongo.get_container_host_ip()}:" f"{mongo.get_exposed_port(mongo.port)}" db = MongoClient(connection_url).test with pytest.raises(OperationFailure): db.restaurants.insert_one({}) diff --git a/modules/mssql/testcontainers/mssql/__init__.py b/modules/mssql/testcontainers/mssql/__init__.py index 9de6edf00..98b668269 100644 --- a/modules/mssql/testcontainers/mssql/__init__.py +++ b/modules/mssql/testcontainers/mssql/__init__.py @@ -1,5 +1,6 @@ from os import environ from typing import Optional + from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -21,11 +22,18 @@ class SqlServerContainer(DbContainer): ... result = connection.execute(sqlalchemy.text("select @@VERSION")) """ - def __init__(self, image: str = "mcr.microsoft.com/mssql/server:2019-latest", - username: str = "SA", password: Optional[str] = None, port: int = 1433, - dbname: str = "tempdb", dialect: str = 'mssql+pymssql', **kwargs) -> None: + def __init__( + self, + image: str = "mcr.microsoft.com/mssql/server:2019-latest", + username: str = "SA", + password: Optional[str] = None, + port: int = 1433, + dbname: str = "tempdb", + dialect: str = "mssql+pymssql", + **kwargs + ) -> None: raise_for_deprecated_parameter(kwargs, "user", "username") - super(SqlServerContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) @@ -39,10 +47,9 @@ def _configure(self) -> None: self.with_env("SA_PASSWORD", self.password) self.with_env("SQLSERVER_USER", self.username) self.with_env("SQLSERVER_DBNAME", self.dbname) - self.with_env("ACCEPT_EULA", 'Y') + self.with_env("ACCEPT_EULA", "Y") def get_connection_url(self) -> str: return super()._create_connection_url( - dialect=self.dialect, username=self.username, password=self.password, - dbname=self.dbname, port=self.port + dialect=self.dialect, username=self.username, password=self.password, dbname=self.dbname, port=self.port ) diff --git a/modules/mssql/tests/test_mssql.py b/modules/mssql/tests/test_mssql.py index b615f1fff..6f48f0a13 100644 --- a/modules/mssql/tests/test_mssql.py +++ b/modules/mssql/tests/test_mssql.py @@ -1,20 +1,21 @@ import sqlalchemy + from testcontainers.mssql import SqlServerContainer def test_docker_run_mssql(): - image = 'mcr.microsoft.com/azure-sql-edge' - dialect = 'mssql+pymssql' + image = "mcr.microsoft.com/azure-sql-edge" + dialect = "mssql+pymssql" with SqlServerContainer(image, dialect=dialect) as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) with engine.begin() as connection: - result = connection.execute(sqlalchemy.text('select @@servicename')) + result = connection.execute(sqlalchemy.text("select @@servicename")) for row in result: - assert row[0] == 'MSSQLSERVER' + assert row[0] == "MSSQLSERVER" with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) with engine.begin() as connection: - result = connection.execute(sqlalchemy.text('select @@servicename')) + result = connection.execute(sqlalchemy.text("select @@servicename")) for row in result: - assert row[0] == 'MSSQLSERVER' + assert row[0] == "MSSQLSERVER" diff --git a/modules/mysql/testcontainers/mysql/__init__.py b/modules/mysql/testcontainers/mysql/__init__.py index 6234540bc..65b317b0c 100644 --- a/modules/mysql/testcontainers/mysql/__init__.py +++ b/modules/mysql/testcontainers/mysql/__init__.py @@ -12,6 +12,7 @@ # under the License. from os import environ from typing import Optional + from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -38,23 +39,31 @@ class MySqlContainer(DbContainer): ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() """ - def __init__(self, image: str = "mysql:latest", username: Optional[str] = None, - root_password: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, port: int = 3306, **kwargs) -> None: + + def __init__( + self, + image: str = "mysql:latest", + username: Optional[str] = None, + root_password: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + port: int = 3306, + **kwargs + ) -> None: raise_for_deprecated_parameter(kwargs, "MYSQL_USER", "username") raise_for_deprecated_parameter(kwargs, "MYSQL_ROOT_PASSWORD", "root_password") raise_for_deprecated_parameter(kwargs, "MYSQL_PASSWORD", "password") raise_for_deprecated_parameter(kwargs, "MYSQL_DATABASE", "dbname") - super(MySqlContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) - self.username = username or environ.get('MYSQL_USER', 'test') - self.root_password = root_password or environ.get('MYSQL_ROOT_PASSWORD', 'test') - self.password = password or environ.get('MYSQL_PASSWORD', 'test') - self.dbname = dbname or environ.get('MYSQL_DATABASE', 'test') + self.username = username or environ.get("MYSQL_USER", "test") + self.root_password = root_password or environ.get("MYSQL_ROOT_PASSWORD", "test") + self.password = password or environ.get("MYSQL_PASSWORD", "test") + self.dbname = dbname or environ.get("MYSQL_DATABASE", "test") - if self.username == 'root': + if self.username == "root": self.root_password = self.password def _configure(self) -> None: @@ -66,8 +75,6 @@ def _configure(self) -> None: self.with_env("MYSQL_PASSWORD", self.password) def get_connection_url(self) -> str: - return super()._create_connection_url(dialect="mysql+pymysql", - username=self.username, - password=self.password, - dbname=self.dbname, - port=self.port) + return super()._create_connection_url( + dialect="mysql+pymysql", username=self.username, password=self.password, dbname=self.dbname, port=self.port + ) diff --git a/modules/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py index 274207b92..a84df4d13 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/modules/mysql/tests/test_mysql.py @@ -1,31 +1,33 @@ import re -import sqlalchemy +from unittest import mock + import pytest +import sqlalchemy + from testcontainers.core.utils import is_arm from testcontainers.mysql import MySqlContainer -from unittest import mock -@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') +@pytest.mark.skipif(is_arm(), reason="mysql container not available for ARM") def test_docker_run_mysql(): - config = MySqlContainer('mysql:5.7.17') + config = MySqlContainer("mysql:5.7.17") with config as mysql: engine = sqlalchemy.create_engine(mysql.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select version()")) for row in result: - assert row[0].startswith('5.7.17') + assert row[0].startswith("5.7.17") -@pytest.mark.skipif(is_arm(), reason='mysql container not available for ARM') +@pytest.mark.skipif(is_arm(), reason="mysql container not available for ARM") def test_docker_run_mysql_8(): - config = MySqlContainer('mysql:8') + config = MySqlContainer("mysql:8") with config as mysql: engine = sqlalchemy.create_engine(mysql.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select version()")) for row in result: - assert row[0].startswith('8') + assert row[0].startswith("8") def test_docker_run_mariadb(): @@ -34,13 +36,13 @@ def test_docker_run_mariadb(): with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select version()")) for row in result: - assert row[0].startswith('10.6.5') + assert row[0].startswith("10.6.5") def test_docker_env_variables(): - with mock.patch.dict("os.environ", MYSQL_USER="demo", MYSQL_DATABASE="custom_db"), \ - MySqlContainer("mariadb:10.6.5").with_bind_ports(3306, 32785).maybe_emulate_amd64() \ - as container: + with mock.patch.dict("os.environ", MYSQL_USER="demo", MYSQL_DATABASE="custom_db"), MySqlContainer( + "mariadb:10.6.5" + ).with_bind_ports(3306, 32785).maybe_emulate_amd64() as container: url = container.get_connection_url() - pattern = r'mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db' + pattern = r"mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db" assert re.match(pattern, url) diff --git a/modules/neo4j/testcontainers/neo4j/__init__.py b/modules/neo4j/testcontainers/neo4j/__init__.py index cf76df501..26f46dc61 100644 --- a/modules/neo4j/testcontainers/neo4j/__init__.py +++ b/modules/neo4j/testcontainers/neo4j/__init__.py @@ -12,14 +12,13 @@ # under the License. import os +from typing import Optional from neo4j import Driver, GraphDatabase - from testcontainers.core.config import TIMEOUT from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs -from typing import Optional class Neo4jContainer(DbContainer): @@ -38,10 +37,17 @@ class Neo4jContainer(DbContainer): ... result = session.run("MATCH (n) RETURN n LIMIT 1") ... record = result.single() """ - def __init__(self, image: str = "neo4j:latest", port: int = 7687, - password: Optional[str] = None, username: Optional[str] = None, **kwargs) -> None: + + def __init__( + self, + image: str = "neo4j:latest", + port: int = 7687, + password: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> None: raise_for_deprecated_parameter(kwargs, "bolt_port", "port") - super(Neo4jContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.username = username or os.environ.get("NEO4J_USER", "neo4j") self.password = password or os.environ.get("NEO4J_PASSWORD", "password") self.port = port @@ -65,8 +71,4 @@ def _connect(self) -> None: driver.verify_connectivity() def get_driver(self, **kwargs) -> Driver: - return GraphDatabase.driver( - self.get_connection_url(), - auth=(self.username, self.password), - **kwargs - ) + return GraphDatabase.driver(self.get_connection_url(), auth=(self.username, self.password), **kwargs) diff --git a/modules/neo4j/tests/test_neo4j.py b/modules/neo4j/tests/test_neo4j.py index 8c90ca0e9..6058d34c2 100644 --- a/modules/neo4j/tests/test_neo4j.py +++ b/modules/neo4j/tests/test_neo4j.py @@ -2,15 +2,14 @@ def test_docker_run_neo4j_latest(): - with Neo4jContainer() as neo4j: - with neo4j.get_driver() as driver: - with driver.session() as session: - result = session.run( - """ - CALL dbms.components() - YIELD name, versions, edition - UNWIND versions as version - RETURN name, version, edition - """) - record = result.single() - assert record["name"].startswith("Neo4j") + with Neo4jContainer() as neo4j, neo4j.get_driver() as driver, driver.session() as session: + result = session.run( + """ + CALL dbms.components() + YIELD name, versions, edition + UNWIND versions as version + RETURN name, version, edition + """ + ) + record = result.single() + assert record["name"].startswith("Neo4j") diff --git a/modules/nginx/testcontainers/nginx/__init__.py b/modules/nginx/testcontainers/nginx/__init__.py index d0680f19a..ecf4c072e 100644 --- a/modules/nginx/testcontainers/nginx/__init__.py +++ b/modules/nginx/testcontainers/nginx/__init__.py @@ -22,11 +22,11 @@ class NginxContainer(DockerContainer): def __init__(self, image: str = "nginx:latest", port: int = 80, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(NginxContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) - def start(self) -> 'NginxContainer': + def start(self) -> "NginxContainer": super().start() host = self.get_container_host_ip() @@ -37,5 +37,5 @@ def start(self) -> 'NginxContainer': @wait_container_is_ready(urllib.error.URLError) def _connect(self, host: str, port: str) -> None: - url = urllib.parse.urlunsplit(('http', f'{host}:{port}', '', '', '')) + url = urllib.parse.urlunsplit(("http", f"{host}:{port}", "", "", "")) urllib.request.urlopen(url, timeout=1) diff --git a/modules/nginx/tests/test_nginx.py b/modules/nginx/tests/test_nginx.py index 0d369bf71..39fba5e97 100644 --- a/modules/nginx/tests/test_nginx.py +++ b/modules/nginx/tests/test_nginx.py @@ -8,5 +8,5 @@ def test_docker_run_nginx(): with nginx_container as nginx: url = f"http://{nginx.get_container_host_ip()}:{nginx.get_exposed_port(nginx.port)}/" r = requests.get(url) - assert (r.status_code == 200) - assert ('Welcome to nginx!' in r.text) + assert r.status_code == 200 + assert "Welcome to nginx!" in r.text diff --git a/modules/opensearch/testcontainers/opensearch/__init__.py b/modules/opensearch/testcontainers/opensearch/__init__.py index 567ba264d..f889c9934 100644 --- a/modules/opensearch/testcontainers/opensearch/__init__.py +++ b/modules/opensearch/testcontainers/opensearch/__init__.py @@ -30,8 +30,13 @@ class OpenSearchContainer(DockerContainer): ... search_result = client.search(index="test", body={"query": {"match_all": {}}}) """ - def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", - port: int = 9200, security_enabled: bool = False, **kwargs) -> None: + def __init__( + self, + image: str = "opensearchproject/opensearch:2.4.0", + port: int = 9200, + security_enabled: bool = False, + **kwargs + ) -> None: """ Args: image: Docker image to use for the container. @@ -39,7 +44,7 @@ def __init__(self, image: str = "opensearchproject/opensearch:2.4.0", security_enabled: :code:`False` disables the security plugin in OpenSearch. """ raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(OpenSearchContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.security_enabled = security_enabled @@ -85,12 +90,7 @@ def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: **kwargs, ) - @wait_container_is_ready( - ConnectionError, - TransportError, - ProtocolError, - ConnectionResetError - ) + @wait_container_is_ready(ConnectionError, TransportError, ProtocolError, ConnectionResetError) def _healthcheck(self) -> None: """This is an internal method used to check if the OpenSearch container is healthy and ready to receive requests.""" diff --git a/modules/opensearch/tests/test_opensearch.py b/modules/opensearch/tests/test_opensearch.py index f5fb411e1..a287563ed 100644 --- a/modules/opensearch/tests/test_opensearch.py +++ b/modules/opensearch/tests/test_opensearch.py @@ -20,9 +20,7 @@ def test_docker_run_opensearch_v1(): def test_docker_run_opensearch_v1_with_security(): - with OpenSearchContainer( - image="opensearchproject/opensearch:1.3.6", security_enabled=True - ) as opensearch: + with OpenSearchContainer(image="opensearchproject/opensearch:1.3.6", security_enabled=True) as opensearch: client = opensearch.get_client() assert client.cluster.health()["status"] == "green" diff --git a/modules/oracle/testcontainers/oracle/__init__.py b/modules/oracle/testcontainers/oracle/__init__.py index 3bd736076..c0a5e657c 100644 --- a/modules/oracle/testcontainers/oracle/__init__.py +++ b/modules/oracle/testcontainers/oracle/__init__.py @@ -19,15 +19,14 @@ class OracleDbContainer(DbContainer): """ def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) -> None: - super(OracleDbContainer, self).__init__(image=image, **kwargs) + super().__init__(image=image, **kwargs) self.container_port = 1521 self.with_exposed_ports(self.container_port) self.with_env("ORACLE_ALLOW_REMOTE", "true") def get_connection_url(self) -> str: return super()._create_connection_url( - dialect="oracle", username="system", password="oracle", port=self.container_port, - dbname="xe" + dialect="oracle", username="system", password="oracle", port=self.container_port, dbname="xe" ) def _configure(self) -> None: diff --git a/modules/oracle/tests/test_oracle.py b/modules/oracle/tests/test_oracle.py index ccbcc4b69..32d58b461 100644 --- a/modules/oracle/tests/test_oracle.py +++ b/modules/oracle/tests/test_oracle.py @@ -1,15 +1,18 @@ -import sqlalchemy import pytest +import sqlalchemy + from testcontainers.oracle import OracleDbContainer @pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") def test_docker_run_oracle(): - versions = {'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production', - 'PL/SQL Release 11.2.0.2.0 - Production', - 'CORE\t11.2.0.2.0\tProduction', - 'TNS for Linux: Version 11.2.0.2.0 - Production', - 'NLSRTL Version 11.2.0.2.0 - Production'} + versions = { + "Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production", + "PL/SQL Release 11.2.0.2.0 - Production", + "CORE\t11.2.0.2.0\tProduction", + "TNS for Linux: Version 11.2.0.2.0 - Production", + "NLSRTL Version 11.2.0.2.0 - Production", + } with OracleDbContainer() as oracledb: engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index 85e0bac80..a61ad2cf8 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -12,6 +12,7 @@ # under the License. import os from typing import Optional + from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -39,11 +40,19 @@ class PostgresContainer(DbContainer): >>> version 'PostgreSQL 9.5...' """ - def __init__(self, image: str = "postgres:latest", port: int = 5432, - username: Optional[str] = None, password: Optional[str] = None, - dbname: Optional[str] = None, driver: str = "psycopg2", **kwargs) -> None: + + def __init__( + self, + image: str = "postgres:latest", + port: int = 5432, + username: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + driver: str = "psycopg2", + **kwargs, + ) -> None: raise_for_deprecated_parameter(kwargs, "user", "username") - super(PostgresContainer, self).__init__(image=image, **kwargs) + super().__init__(image=image, **kwargs) self.username = username or os.environ.get("POSTGRES_USER", "test") self.password = password or os.environ.get("POSTGRES_PASSWORD", "test") self.dbname = dbname or os.environ.get("POSTGRES_DB", "test") @@ -59,7 +68,10 @@ def _configure(self) -> None: def get_connection_url(self, host=None) -> str: return super()._create_connection_url( - dialect=f"postgresql+{self.driver}", username=self.username, - password=self.password, dbname=self.dbname, host=host, + dialect=f"postgresql+{self.driver}", + username=self.username, + password=self.password, + dbname=self.dbname, + host=host, port=self.port, ) diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index c00c1b3fe..c1963531c 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -1,4 +1,5 @@ import sqlalchemy + from testcontainers.postgres import PostgresContainer diff --git a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py index ebdb96351..6c26518c8 100644 --- a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -2,6 +2,7 @@ from typing import Optional import pika + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -23,8 +24,15 @@ class RabbitMqContainer(DockerContainer): ... connection = pika.BlockingConnection(rabbitmq.get_connection_params()) ... channel = connection.channel() """ - def __init__(self, image: str = "rabbitmq:latest", port: Optional[int] = None, - username: Optional[str] = None, password: Optional[str] = None, **kwargs) -> None: + + def __init__( + self, + image: str = "rabbitmq:latest", + port: Optional[int] = None, + username: Optional[str] = None, + password: Optional[str] = None, + **kwargs + ) -> None: """Initialize the RabbitMQ test container. Args: @@ -33,7 +41,7 @@ def __init__(self, image: str = "rabbitmq:latest", port: Optional[int] = None, username: RabbitMQ username. password: RabbitMQ password. """ - super(RabbitMqContainer, self).__init__(image=image, **kwargs) + super().__init__(image=image, **kwargs) self.port = port or int(os.environ.get("RABBITMQ_NODE_PORT", 5672)) self.username = username or os.environ.get("RABBITMQ_DEFAULT_USER", "guest") self.password = password or os.environ.get("RABBITMQ_DEFAULT_PASS", "guest") diff --git a/modules/rabbitmq/tests/test_rabbitmq.py b/modules/rabbitmq/tests/test_rabbitmq.py index 08427a861..25c0fbbb9 100644 --- a/modules/rabbitmq/tests/test_rabbitmq.py +++ b/modules/rabbitmq/tests/test_rabbitmq.py @@ -1,8 +1,9 @@ -from typing import Optional import json +from typing import Optional import pika import pytest + from testcontainers.rabbitmq import RabbitMqContainer QUEUE = "test-q" @@ -12,18 +13,14 @@ @pytest.mark.parametrize( - "port,username,password", - [ - (None, None, None), # use the defaults - (5673, None, None), # test with custom port - (None, "my_test_user", "my_secret_password"), # test with custom credentials - ] + argnames=["port", "username", "password"], + argvalues=[ + [None, None, None], # use the defaults + [5673, None, None], # test with custom port + [None, "my_test_user", "my_secret_password"], # test with custom credentials + ], ) -def test_docker_run_rabbitmq( - port: Optional[int], - username: Optional[str], - password: Optional[str] -): +def test_docker_run_rabbitmq(port: Optional[int], username: Optional[str], password: Optional[str]): """Run rabbitmq test container and use it to deliver a simple message.""" kwargs = {} if port is not None: diff --git a/modules/redis/testcontainers/redis/__init__.py b/modules/redis/testcontainers/redis/__init__.py index 12d473644..fba24be15 100644 --- a/modules/redis/testcontainers/redis/__init__.py +++ b/modules/redis/testcontainers/redis/__init__.py @@ -11,11 +11,12 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import Optional + import redis from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready -from typing import Optional class RedisContainer(DockerContainer): @@ -31,10 +32,10 @@ class RedisContainer(DockerContainer): >>> with RedisContainer() as redis_container: ... redis_client = redis_container.get_client() """ - def __init__(self, image: str = "redis:latest", port: int = 6379, - password: Optional[str] = None, **kwargs) -> None: + + def __init__(self, image: str = "redis:latest", port: int = 6379, password: Optional[str] = None, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super(RedisContainer, self).__init__(image, **kwargs) + super().__init__(image, **kwargs) self.port = port self.password = password self.with_exposed_ports(self.port) diff --git a/modules/redis/tests/test_redis.py b/modules/redis/tests/test_redis.py index 9bf946442..7dc56aa46 100644 --- a/modules/redis/tests/test_redis.py +++ b/modules/redis/tests/test_redis.py @@ -8,11 +8,11 @@ def test_docker_run_redis(): with config as redis: client = redis.get_client() p = client.pubsub() - p.subscribe('test') - client.publish('test', 'new_msg') + p.subscribe("test") + client.publish("test", "new_msg") msg = wait_for_message(p) - assert 'data' in msg - assert b'new_msg', msg['data'] + assert "data" in msg + assert b"new_msg", msg["data"] def test_docker_run_redis_with_password(): @@ -27,8 +27,7 @@ def wait_for_message(pubsub, timeout=1, ignore_subscribe_messages=True): now = time.time() timeout = now + timeout while now < timeout: - message = pubsub.get_message( - ignore_subscribe_messages=ignore_subscribe_messages) + message = pubsub.get_message(ignore_subscribe_messages=ignore_subscribe_messages) if message is not None: return message time.sleep(0.01) diff --git a/modules/selenium/testcontainers/selenium/__init__.py b/modules/selenium/testcontainers/selenium/__init__.py index 29caf296b..b46d46155 100644 --- a/modules/selenium/testcontainers/selenium/__init__.py +++ b/modules/selenium/testcontainers/selenium/__init__.py @@ -11,22 +11,20 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import Optional + +import urllib3 + from selenium import webdriver from selenium.webdriver.common.options import ArgOptions from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready -from typing import Optional -import urllib3 - -IMAGES = { - "firefox": "selenium/standalone-firefox-debug:latest", - "chrome": "selenium/standalone-chrome-debug:latest" -} +IMAGES = {"firefox": "selenium/standalone-firefox-debug:latest", "chrome": "selenium/standalone-chrome-debug:latest"} def get_image_name(capabilities: str) -> str: - return IMAGES[capabilities['browserName']] + return IMAGES[capabilities["browserName"]] class BrowserWebDriverContainer(DockerContainer): @@ -46,13 +44,14 @@ class BrowserWebDriverContainer(DockerContainer): You can easily change browser by passing :code:`DesiredCapabilities.FIREFOX` instead. """ - def __init__(self, capabilities: str, image: Optional[str] = None, port: int = 4444, - vnc_port: int = 5900, **kwargs) -> None: + def __init__( + self, capabilities: str, image: Optional[str] = None, port: int = 4444, vnc_port: int = 5900, **kwargs + ) -> None: self.capabilities = capabilities self.image = image or get_image_name(capabilities) self.port = port self.vnc_port = vnc_port - super(BrowserWebDriverContainer, self).__init__(image=self.image, **kwargs) + super().__init__(image=self.image, **kwargs) self.with_exposed_ports(self.port, self.vnc_port) def _configure(self) -> None: @@ -64,9 +63,7 @@ def _connect(self) -> webdriver.Remote: options = ArgOptions() for key, value in self.capabilities.items(): options.set_capability(key, value) - return webdriver.Remote( - command_executor=(self.get_connection_url()), - options=options) + return webdriver.Remote(command_executor=(self.get_connection_url()), options=options) def get_driver(self) -> webdriver.Remote: return self._connect() @@ -74,4 +71,4 @@ def get_driver(self) -> webdriver.Remote: def get_connection_url(self) -> str: ip = self.get_container_host_ip() port = self.get_exposed_port(self.port) - return f'http://{ip}:{port}/wd/hub' + return f"http://{ip}:{port}/wd/hub" diff --git a/modules/selenium/tests/test_selenium.py b/modules/selenium/tests/test_selenium.py index 94cbaac35..61c1bb326 100644 --- a/modules/selenium/tests/test_selenium.py +++ b/modules/selenium/tests/test_selenium.py @@ -1,14 +1,15 @@ import pytest from selenium.webdriver import DesiredCapabilities from selenium.webdriver.common.by import By -from testcontainers.selenium import BrowserWebDriverContainer + from testcontainers.core.utils import is_arm +from testcontainers.selenium import BrowserWebDriverContainer @pytest.mark.parametrize("caps", [DesiredCapabilities.CHROME, DesiredCapabilities.FIREFOX]) def test_webdriver_container_container(caps): if is_arm(): - pytest.skip('https://github.com/SeleniumHQ/docker-selenium/issues/1076') + pytest.skip("https://github.com/SeleniumHQ/docker-selenium/issues/1076") with BrowserWebDriverContainer(caps).maybe_emulate_amd64() as chrome: webdriver = chrome.get_driver() diff --git a/poetry.lock b/poetry.lock index c69c14480..41eda34bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -760,22 +760,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "flake8" -version = "6.1.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, - {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" -pyflakes = ">=3.1.0,<3.2.0" - [[package]] name = "google-api-core" version = "2.15.0" @@ -1335,17 +1319,6 @@ files = [ {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "mdurl" version = "0.1.2" @@ -1386,6 +1359,64 @@ files = [ {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, ] +[[package]] +name = "mypy" +version = "1.7.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, + {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, + {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, + {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, + {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, + {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, + {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, + {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, + {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, + {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, + {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, + {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, + {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, + {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, + {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, + {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, + {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, + {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "neo4j" version = "5.16.0" @@ -1694,17 +1725,6 @@ files = [ [package.dependencies] pyasn1 = ">=0.4.6,<0.6.0" -[[package]] -name = "pycodestyle" -version = "2.11.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, -] - [[package]] name = "pycparser" version = "2.21" @@ -1757,17 +1777,6 @@ files = [ {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] -[[package]] -name = "pyflakes" -version = "3.1.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, - {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, -] - [[package]] name = "pygments" version = "2.17.2" @@ -2771,7 +2780,7 @@ urllib3 = ">=1.26.0" name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, @@ -2995,4 +3004,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "9581bf8b84748e77f2c480e320307fe223cedc7eee614512b9ee5de8fd562bd3" +content-hash = "89891a5aeea49686e42fd95780d6aa703a1a13d8483a1a965aa32603b39f3a3d" diff --git a/pyproject.toml b/pyproject.toml index 9139e3b25..b825d8ebd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,9 +111,9 @@ selenium = ["selenium"] pytest = "7.4.3" pytest-cov = "4.1.0" sphinx = "^7.2.6" -flake8 = "^6.1.0" pg8000 = "*" twine = "^4.0.2" +mypy = "1.7.1" [[tool.poetry.source]] name = "PyPI" @@ -139,6 +139,119 @@ exclude_lines = [ "raise NotImplementedError" # TODO: used in core/generic.py, not sure we need DbContainer ] +[tool.ruff.flake8-type-checking] +strict = true + +[tool.ruff] +target-version = "py39" +line-length = 120 +fix = true +fixable = ["I"] +src = ["core", "modules/*"] +exclude = ["**/tests/**/*.py"] +select = [ + # flake8-2020 + "YTT", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-debugger + "T10", + # flake8-print + "T20", + # flake8-pytest-style + "PT", + # flake8-simplify + "SIM", + # flake8-tidy-imports + "TID", + # flake8-type-checking + "TCH", + # isort + "I", + # mccabe + "C90", + # pycodestyle + "E", "W", + # pyflakes + "F", + # pygrep-hooks + "PGH", + # pyupgrade + "UP", + # ruff + "RUF", + # TODO: security, enable via line below + # "S", +] +ignore = [ + # line too long (already checked by black) + "E501", + # the must-have __init__.py (we are using package namespaces) + "INP001" +] + + +[tool.mypy] +python_version = "3.9" +namespace_packages = true +explicit_package_bases = true +pretty = true +show_error_codes = true +strict = true +fast_module_lookup = true +modules = ["testcontainers.core"] +mypy_path = [ + "core", +# "modules/arangodb", +# "modules/azurite", +# "modules/clickhouse", +# "modules/elasticsearch", +# "modules/google", +# "modules/k3s", +# "modules/kafka", +# "modules/keycloak", +# "modules/localstack", +# "modules/minio", +# "modules/mongodb", +# "modules/mssql", +# "modules/mysql", +# "modules/neo4j", +# "modules/nginx", +# "modules/opensearch", +# "modules/oracle", +# "modules/postgres", +# "modules/rabbitmq", +# "modules/redis", +# "modules/selenium" +] +enable_error_code = [ + "ignore-without-code", + "redundant-expr", + "truthy-bool", +] + +[[tool.mypy.overrides]] +module = ['tests.*'] +# in pytest we allow fixtures to be more relaxed, though we check the untyped functions +check_untyped_defs = true +disable_error_code = [ + 'no-untyped-def' +] + +[[tool.mypy.overrides]] +module = ['docker.*'] +# docker still doesn't have type annotations (not even 7.0) +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = ['wrapt.*'] +# wrapt doesn't have type annotations +ignore_missing_imports = true + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/requirements/macos-latest-3.10.txt b/requirements/macos-latest-3.10.txt deleted file mode 100644 index d36feeeb6..000000000 --- a/requirements/macos-latest-3.10.txt +++ /dev/null @@ -1,442 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --output-file=requirements.txt -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.3 - # via redis -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.16.0 - # via - # argon2-cffi-bindings - # cryptography -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -coverage[toml]==7.3.2 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.4.2 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.20.1 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.8.0 - # via - # keyring - # python-arango - # twine -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.3.0 - # via keyring -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.3.0 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==10.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -nh3==0.2.14 - # via readme-renderer -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # python-arango - # sphinx -pg8000==1.30.3 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.3.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.25.1 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.8.1 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.7.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # clickhouse-driver - # neo4j -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==42.0 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -selenium==4.15.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==7.2.6 - # via - # -r requirements.in - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml -sphinxcontrib-applehelp==1.0.7 - # via sphinx -sphinxcontrib-devhelp==1.0.5 - # via sphinx -sphinxcontrib-htmlhelp==2.0.4 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.6 - # via sphinx -sphinxcontrib-serializinghtml==1.1.9 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -tomli==2.0.1 - # via - # coverage - # pytest -trio==0.23.1 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.8.0 - # via - # azure-core - # azure-storage-blob - # sqlalchemy -tzlocal==5.2 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -websocket-client==1.6.4 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/ubuntu-latest-3.10.txt b/requirements/ubuntu-latest-3.10.txt deleted file mode 100644 index bc349e7c9..000000000 --- a/requirements/ubuntu-latest-3.10.txt +++ /dev/null @@ -1,449 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --output-file=requirements.txt -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.3 - # via redis -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.16.0 - # via - # argon2-cffi-bindings - # cryptography -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -coverage[toml]==7.3.2 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql - # secretstorage -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.4.2 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.20.1 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.8.0 - # via - # keyring - # python-arango - # twine -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.3.0 - # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.3.0 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==10.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -nh3==0.2.14 - # via readme-renderer -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # python-arango - # sphinx -pg8000==1.30.3 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.3.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.25.1 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.8.1 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.7.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # clickhouse-driver - # neo4j -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==42.0 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -secretstorage==3.3.3 - # via keyring -selenium==4.15.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==7.2.6 - # via - # -r requirements.in - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml -sphinxcontrib-applehelp==1.0.7 - # via sphinx -sphinxcontrib-devhelp==1.0.5 - # via sphinx -sphinxcontrib-htmlhelp==2.0.4 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.6 - # via sphinx -sphinxcontrib-serializinghtml==1.1.9 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -tomli==2.0.1 - # via - # coverage - # pytest -trio==0.23.1 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.8.0 - # via - # azure-core - # azure-storage-blob - # sqlalchemy -tzlocal==5.2 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -websocket-client==1.6.4 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/ubuntu-latest-3.11.txt b/requirements/ubuntu-latest-3.11.txt deleted file mode 100644 index 6dfda74d1..000000000 --- a/requirements/ubuntu-latest-3.11.txt +++ /dev/null @@ -1,438 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --output-file=requirements.txt -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.16.0 - # via - # argon2-cffi-bindings - # cryptography -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -coverage[toml]==7.3.2 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql - # secretstorage -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.4.2 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.20.1 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.8.0 - # via - # keyring - # python-arango - # twine -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.3.0 - # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.3.0 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==10.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -nh3==0.2.14 - # via readme-renderer -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # python-arango - # sphinx -pg8000==1.30.3 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.3.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.25.1 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.8.1 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.7.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # clickhouse-driver - # neo4j -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==42.0 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -secretstorage==3.3.3 - # via keyring -selenium==4.15.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==7.2.6 - # via - # -r requirements.in - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml -sphinxcontrib-applehelp==1.0.7 - # via sphinx -sphinxcontrib-devhelp==1.0.5 - # via sphinx -sphinxcontrib-htmlhelp==2.0.4 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.6 - # via sphinx -sphinxcontrib-serializinghtml==1.1.9 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -trio==0.23.1 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.8.0 - # via - # azure-core - # azure-storage-blob - # sqlalchemy -tzlocal==5.2 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -websocket-client==1.6.4 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/ubuntu-latest-3.7.txt b/requirements/ubuntu-latest-3.7.txt deleted file mode 100644 index c3ebc37bb..000000000 --- a/requirements/ubuntu-latest-3.7.txt +++ /dev/null @@ -1,467 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.7 -# by the following command: -# -# pip-compile --output-file=requirements.txt --resolver=backtracking -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.3 - # via redis -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -backports-zoneinfo==0.2.1 - # via tzlocal -bleach==6.0.0 - # via readme-renderer -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.15.1 - # via - # argon2-cffi-bindings - # cryptography -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -coverage[toml]==7.2.7 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql - # secretstorage -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.3.0 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.19 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.7.0 - # via - # attrs - # keyring - # pg8000 - # pluggy - # pytest - # redis - # scramp - # sphinx - # sqlalchemy - # twine -importlib-resources==5.12.0 - # via keyring -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.2.3 - # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.1.1 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==2.2.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==9.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # sphinx -pg8000==1.29.8 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.2.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.24.4 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.5.6 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.6.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # babel - # clickhouse-driver - # neo4j -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==37.3 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -secretstorage==3.3.3 - # via keyring -selenium==4.11.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # bleach - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==5.3.0 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.2 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.0 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -tomli==2.0.1 - # via - # coverage - # pytest -trio==0.22.2 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.7.1 - # via - # argon2-cffi - # async-timeout - # azure-core - # azure-storage-blob - # h11 - # importlib-metadata - # markdown-it-py - # pyjwt - # redis - # rich - # sqlalchemy -tzlocal==5.1 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -webencodings==0.5.1 - # via bleach -websocket-client==1.6.1 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.15.0 - # via - # importlib-metadata - # importlib-resources - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/ubuntu-latest-3.8.txt b/requirements/ubuntu-latest-3.8.txt deleted file mode 100644 index 605b3cf1c..000000000 --- a/requirements/ubuntu-latest-3.8.txt +++ /dev/null @@ -1,452 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --output-file=requirements.txt -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.3 - # via redis -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -backports-zoneinfo==0.2.1 - # via tzlocal -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.16.0 - # via - # argon2-cffi-bindings - # cryptography -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -coverage[toml]==7.3.2 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql - # secretstorage -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.4.2 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.20.1 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.8.0 - # via - # keyring - # python-arango - # sphinx - # twine -importlib-resources==6.1.1 - # via keyring -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.3.0 - # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.3.0 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==10.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -nh3==0.2.14 - # via readme-renderer -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # python-arango - # sphinx -pg8000==1.30.3 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.3.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.25.1 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.8.1 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.7.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # babel - # clickhouse-driver - # neo4j -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==42.0 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -secretstorage==3.3.3 - # via keyring -selenium==4.15.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==7.1.2 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.1 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -tomli==2.0.1 - # via - # coverage - # pytest -trio==0.23.1 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.8.0 - # via - # azure-core - # azure-storage-blob - # rich - # sqlalchemy -tzlocal==5.2 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -websocket-client==1.6.4 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via - # importlib-metadata - # importlib-resources - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/ubuntu-latest-3.9.txt b/requirements/ubuntu-latest-3.9.txt deleted file mode 100644 index 970a09211..000000000 --- a/requirements/ubuntu-latest-3.9.txt +++ /dev/null @@ -1,450 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --output-file=requirements.txt -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.3 - # via redis -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.16.0 - # via - # argon2-cffi-bindings - # cryptography -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -coverage[toml]==7.3.2 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql - # secretstorage -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.4.2 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.20.1 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.8.0 - # via - # keyring - # python-arango - # sphinx - # twine -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.3.0 - # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.3.0 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==10.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -nh3==0.2.14 - # via readme-renderer -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # python-arango - # sphinx -pg8000==1.30.3 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.3.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.25.1 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.8.1 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.7.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # clickhouse-driver - # neo4j -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==42.0 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -secretstorage==3.3.3 - # via keyring -selenium==4.15.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==7.2.6 - # via - # -r requirements.in - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml -sphinxcontrib-applehelp==1.0.7 - # via sphinx -sphinxcontrib-devhelp==1.0.5 - # via sphinx -sphinxcontrib-htmlhelp==2.0.4 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.6 - # via sphinx -sphinxcontrib-serializinghtml==1.1.9 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -tomli==2.0.1 - # via - # coverage - # pytest -trio==0.23.1 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.8.0 - # via - # azure-core - # azure-storage-blob - # sqlalchemy -tzlocal==5.2 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -websocket-client==1.6.4 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements/windows-latest-3.10.txt b/requirements/windows-latest-3.10.txt deleted file mode 100644 index b9f41c654..000000000 --- a/requirements/windows-latest-3.10.txt +++ /dev/null @@ -1,453 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --output-file=requirements.txt -# --e file:meta - # via -r requirements.in --e file:arangodb - # via -r requirements.in --e file:azurite - # via -r requirements.in --e file:clickhouse - # via -r requirements.in --e file:core - # via - # -r requirements.in - # testcontainers - # testcontainers-arangodb - # testcontainers-azurite - # testcontainers-clickhouse - # testcontainers-elasticsearch - # testcontainers-gcp - # testcontainers-k3s - # testcontainers-kafka - # testcontainers-keycloak - # testcontainers-localstack - # testcontainers-minio - # testcontainers-mongodb - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-neo4j - # testcontainers-nginx - # testcontainers-opensearch - # testcontainers-oracle - # testcontainers-postgres - # testcontainers-rabbitmq - # testcontainers-redis - # testcontainers-selenium --e file:elasticsearch - # via -r requirements.in --e file:google - # via -r requirements.in --e file:k3s - # via -r requirements.in --e file:kafka - # via -r requirements.in --e file:keycloak - # via -r requirements.in --e file:localstack - # via -r requirements.in --e file:minio - # via -r requirements.in --e file:mongodb - # via -r requirements.in --e file:mssql - # via -r requirements.in --e file:mysql - # via -r requirements.in --e file:neo4j - # via -r requirements.in --e file:nginx - # via -r requirements.in --e file:opensearch - # via -r requirements.in --e file:oracle - # via -r requirements.in --e file:postgres - # via -r requirements.in --e file:rabbitmq - # via -r requirements.in --e file:redis - # via -r requirements.in --e file:selenium - # via -r requirements.in -alabaster==0.7.13 - # via sphinx -argon2-cffi==23.1.0 - # via minio -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -asn1crypto==1.5.1 - # via scramp -async-timeout==4.0.3 - # via redis -attrs==23.1.0 - # via - # outcome - # trio -azure-core==1.29.5 - # via azure-storage-blob -azure-storage-blob==12.19.0 - # via testcontainers-azurite -babel==2.13.1 - # via sphinx -boto3==1.33.1 - # via testcontainers-localstack -botocore==1.33.1 - # via - # boto3 - # s3transfer -cachetools==5.3.2 - # via google-auth -certifi==2023.11.17 - # via - # kubernetes - # minio - # opensearch-py - # requests - # selenium -cffi==1.16.0 - # via - # argon2-cffi-bindings - # cryptography - # trio -charset-normalizer==3.3.2 - # via requests -clickhouse-driver==0.2.6 - # via testcontainers-clickhouse -colorama==0.4.6 - # via - # pytest - # sphinx -coverage[toml]==7.3.2 - # via - # coverage - # pytest-cov -cryptography==36.0.2 - # via - # -r requirements.in - # azure-storage-blob - # pymysql -cx-oracle==8.3.0 - # via testcontainers-oracle -deprecation==2.1.0 - # via python-keycloak -dnspython==2.4.2 - # via pymongo -docker==6.1.3 - # via testcontainers-core -docutils==0.20.1 - # via - # readme-renderer - # sphinx -ecdsa==0.18.0 - # via python-jose -entrypoints==0.3 - # via flake8 -exceptiongroup==1.2.0 - # via - # pytest - # trio - # trio-websocket -flake8==3.7.9 - # via -r requirements.in -google-api-core[grpc]==2.14.0 - # via - # google-api-core - # google-cloud-pubsub -google-auth==2.23.4 - # via - # google-api-core - # kubernetes -google-cloud-pubsub==2.18.4 - # via testcontainers-gcp -googleapis-common-protos[grpc]==1.61.0 - # via - # google-api-core - # grpc-google-iam-v1 - # grpcio-status -greenlet==3.0.1 - # via sqlalchemy -grpc-google-iam-v1==0.12.7 - # via google-cloud-pubsub -grpcio==1.59.3 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status -grpcio-status==1.59.3 - # via - # google-api-core - # google-cloud-pubsub -h11==0.14.0 - # via wsproto -idna==3.6 - # via - # requests - # trio -imagesize==1.4.1 - # via sphinx -importlib-metadata==6.8.0 - # via - # keyring - # python-arango - # twine -iniconfig==2.0.0 - # via pytest -isodate==0.6.1 - # via azure-storage-blob -jaraco-classes==3.3.0 - # via keyring -jinja2==3.1.2 - # via sphinx -jmespath==1.0.1 - # via - # boto3 - # botocore -kafka-python==2.0.2 - # via testcontainers-kafka -keyring==24.3.0 - # via twine -kubernetes==28.1.0 - # via testcontainers-k3s -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 - # via markdown-it-py -minio==7.2.0 - # via testcontainers-minio -more-itertools==10.1.0 - # via jaraco-classes -neo4j==5.15.0 - # via testcontainers-neo4j -nh3==0.2.14 - # via readme-renderer -oauthlib==3.2.2 - # via - # kubernetes - # requests-oauthlib -opensearch-py==2.4.2 - # via testcontainers-opensearch -outcome==1.3.0.post0 - # via trio -packaging==23.2 - # via - # deprecation - # docker - # pytest - # python-arango - # sphinx -pg8000==1.30.3 - # via -r requirements.in -pika==1.3.2 - # via testcontainers-rabbitmq -pkginfo==1.9.6 - # via twine -pluggy==1.3.0 - # via pytest -proto-plus==1.22.3 - # via google-cloud-pubsub -protobuf==4.25.1 - # via - # google-api-core - # google-cloud-pubsub - # googleapis-common-protos - # grpc-google-iam-v1 - # grpcio-status - # proto-plus -psycopg2-binary==2.9.9 - # via testcontainers-postgres -pyasn1==0.5.1 - # via - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.5.0 - # via flake8 -pycparser==2.21 - # via cffi -pycryptodome==3.19.0 - # via minio -pyflakes==2.1.1 - # via flake8 -pygments==2.17.2 - # via - # readme-renderer - # rich - # sphinx -pyjwt==2.8.0 - # via python-arango -pymongo==4.6.0 - # via testcontainers-mongodb -pymssql==2.2.10 - # via testcontainers-mssql -pymysql[rsa]==1.1.0 - # via testcontainers-mysql -pysocks==1.7.1 - # via urllib3 -pytest==7.4.3 - # via - # -r requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r requirements.in -python-arango==7.8.1 - # via testcontainers-arangodb -python-dateutil==2.8.2 - # via - # botocore - # kubernetes - # opensearch-py - # pg8000 -python-jose==3.3.0 - # via python-keycloak -python-keycloak==3.7.0 - # via testcontainers-keycloak -pytz==2023.3.post1 - # via - # clickhouse-driver - # neo4j -pywin32==306 - # via docker -pywin32-ctypes==0.2.2 - # via keyring -pyyaml==6.0.1 - # via - # kubernetes - # testcontainers-k3s -readme-renderer==42.0 - # via twine -redis==5.0.1 - # via testcontainers-redis -requests==2.31.0 - # via - # azure-core - # docker - # google-api-core - # kubernetes - # opensearch-py - # python-arango - # python-keycloak - # requests-oauthlib - # requests-toolbelt - # sphinx - # twine -requests-oauthlib==1.3.1 - # via kubernetes -requests-toolbelt==1.0.0 - # via - # python-arango - # python-keycloak - # twine -rfc3986==2.0.0 - # via twine -rich==13.7.0 - # via twine -rsa==4.9 - # via - # google-auth - # python-jose -s3transfer==0.8.0 - # via boto3 -scramp==1.4.4 - # via pg8000 -selenium==4.15.2 - # via testcontainers-selenium -six==1.16.0 - # via - # azure-core - # ecdsa - # isodate - # kubernetes - # opensearch-py - # python-dateutil -sniffio==1.3.0 - # via trio -snowballstemmer==2.2.0 - # via sphinx -sortedcontainers==2.4.0 - # via trio -sphinx==7.2.6 - # via - # -r requirements.in - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml -sphinxcontrib-applehelp==1.0.7 - # via sphinx -sphinxcontrib-devhelp==1.0.5 - # via sphinx -sphinxcontrib-htmlhelp==2.0.4 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.6 - # via sphinx -sphinxcontrib-serializinghtml==1.1.9 - # via sphinx -sqlalchemy==2.0.23 - # via - # testcontainers-mssql - # testcontainers-mysql - # testcontainers-oracle - # testcontainers-postgres -tomli==2.0.1 - # via - # coverage - # pytest -trio==0.23.1 - # via - # selenium - # trio-websocket -trio-websocket==0.11.1 - # via selenium -twine==4.0.2 - # via -r requirements.in -typing-extensions==4.8.0 - # via - # azure-core - # azure-storage-blob - # sqlalchemy -tzdata==2023.3 - # via tzlocal -tzlocal==5.2 - # via clickhouse-driver -urllib3[socks]==1.26.18 - # via - # botocore - # docker - # kubernetes - # minio - # opensearch-py - # python-arango - # requests - # selenium - # testcontainers-core - # twine -websocket-client==1.6.4 - # via - # docker - # kubernetes -wheel==0.42.0 - # via -r requirements.in -wrapt==1.16.0 - # via testcontainers-core -wsproto==1.2.0 - # via trio-websocket -zipp==3.17.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/scripts/diagnostics.py b/scripts/diagnostics.py new file mode 100644 index 000000000..ef8bb6507 --- /dev/null +++ b/scripts/diagnostics.py @@ -0,0 +1,25 @@ +import json + +from testcontainers.core import utils +from testcontainers.core.container import DockerContainer + +result = { + "is_linux": utils.is_linux(), + "is_mac": utils.is_mac(), + "is_windows": utils.is_windows(), + "inside_container": utils.inside_container(), + "default_gateway_ip": utils.default_gateway_ip(), +} + +with DockerContainer("alpine:latest") as container: + client = container.get_docker_client() + result.update( + { + "container_host_ip": container.get_container_host_ip(), + "docker_client_gateway_ip": client.gateway_ip(container._container.id), + "docker_client_bridge_ip": client.bridge_ip(container._container.id), + "docker_client_host": client.host(), + } + ) + +print(json.dumps(result, indent=2)) # noqa: T201 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d673938d4..000000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 100 -exclude = .git,__pycache__,build,dist,venv,.venv From fc1111960164e949ee71d2b85889aa71d7172019 Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Wed, 28 Feb 2024 21:04:40 -0500 Subject: [PATCH 279/425] Integrate poetry into devcontainers (#431) see #431 --- .devcontainer/commands/post-create-command.sh | 5 +++++ .devcontainer/devcontainer.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100755 .devcontainer/commands/post-create-command.sh diff --git a/.devcontainer/commands/post-create-command.sh b/.devcontainer/commands/post-create-command.sh new file mode 100755 index 000000000..001872909 --- /dev/null +++ b/.devcontainer/commands/post-create-command.sh @@ -0,0 +1,5 @@ +echo "Running post-create-command.sh" + +curl -sSL https://install.python-poetry.org | python3 - + +poetry install --all-extras \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 69edb9363..1c300bcc7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,7 +18,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "pip install --user -r requirements/ubuntu-latest-3.11.txt", + "postCreateCommand": ".devcontainer/commands/post-create-command.sh", "customizations": { "vscode": { "extensions": [ From d912dfb8736c28ae0056ff8f5c139be060364ae7 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Thu, 29 Feb 2024 01:30:49 -0500 Subject: [PATCH 280/425] fix the #430 fix in #431 (#432) fixes the #430 fix in #431: makes the linter happy? --- .devcontainer/commands/post-create-command.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/commands/post-create-command.sh b/.devcontainer/commands/post-create-command.sh index 001872909..c3229490f 100755 --- a/.devcontainer/commands/post-create-command.sh +++ b/.devcontainer/commands/post-create-command.sh @@ -2,4 +2,4 @@ echo "Running post-create-command.sh" curl -sSL https://install.python-poetry.org | python3 - -poetry install --all-extras \ No newline at end of file +poetry install --all-extras From 30f859eb1535acd6e93c331213426e1319ee9a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:13:38 +0100 Subject: [PATCH 281/425] feat(release): automate release via release-please (#429) # changes - add `release-please` as the release automation tool - refactor "install python" into a reusable local action - build and test with Python `3.12` - seems it didn't take much extra effort # notes - previously, the actual "releases" here on GitHub was a mess - `3.7.1` is the latest on PyPI - only exists as a tag on this repo, no release notes - the latest "release" is put out later than some of the higher version tags - `.github/.release-please-manifest.json` -> went with the sha from `3.7.1` which then `release-please` will take as "latest live version", also made `3.7.1` in the manifest --------- Co-authored-by: Balint Bartha --- .github/.release-please-manifest.json | 3 ++ .github/actions/setup-env/action.yml | 20 +++++++++ .github/release-please-config.json | 10 +++++ .../workflows/attention-label.yml.disabled | 30 ------------- .github/workflows/ci-community.yml | 7 +-- .github/workflows/ci-core.yml | 10 ++--- .github/workflows/ci-lint.yml | 11 ++--- .github/workflows/docs.yml | 9 ++-- .github/workflows/pr-lint.yml | 19 ++++++++ .github/workflows/release-please.yml | 35 +++++++++++++++ .github/workflows/requirements.yml.disabled | 43 ------------------- .github/workflows/triage-label.yml.disabled | 12 ------ poetry.lock | 4 +- pyproject.toml | 2 +- 14 files changed, 103 insertions(+), 112 deletions(-) create mode 100644 .github/.release-please-manifest.json create mode 100644 .github/actions/setup-env/action.yml create mode 100644 .github/release-please-config.json delete mode 100644 .github/workflows/attention-label.yml.disabled create mode 100644 .github/workflows/pr-lint.yml create mode 100644 .github/workflows/release-please.yml delete mode 100644 .github/workflows/requirements.yml.disabled delete mode 100644 .github/workflows/triage-label.yml.disabled diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json new file mode 100644 index 000000000..698886db5 --- /dev/null +++ b/.github/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "3.7.1" +} diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml new file mode 100644 index 000000000..de8700062 --- /dev/null +++ b/.github/actions/setup-env/action.yml @@ -0,0 +1,20 @@ +name: setup-env +description: set up the python environment + +inputs: + python-version: + description: "The python version to install and use" + default: "3.12" # we default to latest supported + required: false + +runs: + using: composite + steps: + - name: Setup Poetry + run: pipx install poetry + shell: bash + - name: Setup python ${{ inputs.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + cache: poetry diff --git a/.github/release-please-config.json b/.github/release-please-config.json new file mode 100644 index 000000000..1bd96f93c --- /dev/null +++ b/.github/release-please-config.json @@ -0,0 +1,10 @@ +{ + "release-type": "python", + "bootstrap-sha": "28e3a471c32c1036dd5e37df13cdde3b1ba91000", + "packages": { + ".": { + "package-name": "testcontainers" + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} diff --git a/.github/workflows/attention-label.yml.disabled b/.github/workflows/attention-label.yml.disabled deleted file mode 100644 index 97d384443..000000000 --- a/.github/workflows/attention-label.yml.disabled +++ /dev/null @@ -1,30 +0,0 @@ -name: Automatically add or remove labels -on: - issue_comment: - types: - - created -jobs: - show-metadata: - runs-on: ubuntu-latest - steps: - # Use environment variable to print the metadata (cf. https://github.com/actions/runner/issues/1656#issuecomment-1030077729). - - name: Show metadata - run: echo $JSON - env: - JSON: ${{ toJson(github.event) }} - add-label: - if: ${{ github.event.comment.user.login != 'tillahoffmann' }} - runs-on: ubuntu-latest - steps: - - name: Add label - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: '👀 requires attention' - remove-label: - if: ${{ github.event.comment.user.login == 'tillahoffmann' }} - runs-on: ubuntu-latest - steps: - - name: Remove label - uses: actions-ecosystem/action-remove-labels@v1 - with: - labels: '👀 requires attention' diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 92e58e518..b5b1c65f2 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -61,13 +61,10 @@ jobs: gh run watch ${{ github.run_id }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Poetry - run: pipx install poetry - - name: Setup python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - name: Set up Python + uses: ./.github/actions/setup-env with: python-version: ${{ matrix.python-version }} - cache: poetry - name: Install Python dependencies run: poetry install -E ${{ matrix.module }} - name: Run tests diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index c96619868..65bb23884 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -11,19 +11,17 @@ on: jobs: test: strategy: + fail-fast: false matrix: os: [ ubuntu ] - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v4 - - name: Setup Poetry - run: pipx install poetry - - name: Setup python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - name: Set up Python + uses: ./.github/actions/setup-env with: python-version: ${{ matrix.python-version }} - cache: poetry - name: Install Python dependencies run: poetry install --all-extras - name: Run twine check diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 6261002b3..0a9f4e265 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -9,17 +9,14 @@ on: branches: [main] jobs: - all: + python: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup Poetry - run: pipx install poetry - - name: Setup python 3.9 - uses: actions/setup-python@v5 + - name: Setup Env + uses: ./.github/actions/setup-env with: - python-version: 3.9 - cache: poetry + python-version: "3.9" # the pre-commit is hooked in as 3.9 - name: Install Python dependencies run: poetry install - name: Install pre-commit diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 75f71e21d..1dfb6c711 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,14 +10,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Setup Poetry - run: pipx install poetry - - name: Setup python - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + - name: Set up Python + uses: ./.github/actions/setup-env with: python-version: "3.11" - cache: poetry - name: Install Python dependencies run: poetry install --all-extras - name: Build documentation diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 000000000..b99c3074a --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,19 @@ +name: lint-pr + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + validate: + name: validate-pull-request-title + runs-on: ubuntu-latest + steps: + - name: validate pull request title + uses: kontrolplane/pull-request-title-validator@ab2b54babb5337246f4b55cf8e0a1ecb0575e46d #v1 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..a5a9821a2 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,35 @@ +name: Release Please + +on: + push: + branches: [ main ] + +jobs: + release: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.track-release.outputs.release_created }} + steps: + - uses: google-github-actions/release-please-action@v4 + id: track-release + with: + manifest-file: .github/.release-please-manifest.json + config-file: .github/release-please-config.json + publish: + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + needs: + - release + if: ${{ needs.release.outputs.release_created }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: ./.github/actions/setup-env + - name: build package + run: poetry build + # this action uploads packages from the `dist/` directory, which poetry has built in the previous step + # usable once we set up trusted publishing, see https://docs.pypi.org/trusted-publishers/using-a-publisher/ + - name: push package + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/requirements.yml.disabled b/.github/workflows/requirements.yml.disabled deleted file mode 100644 index 72c41f302..000000000 --- a/.github/workflows/requirements.yml.disabled +++ /dev/null @@ -1,43 +0,0 @@ -name: testcontainers requirements -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - requirements: - strategy: - fail-fast: false - matrix: - runtime: - - machine: ubuntu-latest - python-version: "3.7" - - machine: ubuntu-latest - python-version: "3.8" - - machine: ubuntu-latest - python-version: "3.9" - - machine: ubuntu-latest - python-version: "3.10" - - machine: ubuntu-latest - python-version: "3.11" - - machine: windows-latest - python-version: "3.10" - - machine: macos-latest - python-version: "3.10" - runs-on: ${{ matrix.runtime.machine }} - steps: - - uses: actions/checkout@v3 - - name: Setup python ${{ matrix.runtime.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.runtime.python-version }} - - name: Update pip and install pip-tools - run: pip install --upgrade pip pip-tools - - name: Build requirements - run: pip-compile --resolver=backtracking -v --upgrade -o requirements.txt - - name: Store requirements as artifact - uses: actions/upload-artifact@v3 - with: - name: requirements-${{ matrix.runtime.machine }}-${{ matrix.runtime.python-version }}.txt - path: requirements.txt diff --git a/.github/workflows/triage-label.yml.disabled b/.github/workflows/triage-label.yml.disabled deleted file mode 100644 index a5ecf38a9..000000000 --- a/.github/workflows/triage-label.yml.disabled +++ /dev/null @@ -1,12 +0,0 @@ -on: - issues: - types: - - opened -jobs: - add-label: - runs-on: ubuntu-latest - steps: - - name: Add label - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: '🔀 requires triage' diff --git a/poetry.lock b/poetry.lock index 41eda34bb..5cf56c8cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3003,5 +3003,5 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" -python-versions = ">=3.9,<3.12" -content-hash = "89891a5aeea49686e42fd95780d6aa703a1a13d8483a1a965aa32603b39f3a3d" +python-versions = ">=3.9,<4.0" +content-hash = "0316bbdbb2824f6086c32cb9ed00a23507896bf48494a38d086e281faf1f8189" diff --git a/pyproject.toml b/pyproject.toml index b825d8ebd..2059926c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ packages = [ "Issue Tracker" = "https://github.com/testcontainers/testcontainers-python/issues" [tool.poetry.dependencies] -python = ">=3.9,<3.12" +python = ">=3.9,<4.0" docker = "*" # ">=4.0" urllib3 = "*" # "<2.0" wrapt = "*" # "^1.16.0" From 9e240d09491e251fe618108844d5feaf6e3039dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:40:57 +0100 Subject: [PATCH 282/425] chore(settings): fix the perceived issues with settings (#435) # change Trying to fix the settings sync process https://probot.github.io/apps/settings/ Co-authored-by: Balint Bartha --- .github/settings.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index f191b87d3..e72584e6f 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -7,10 +7,7 @@ repository: name: testcontainers-python # A short description of the repository that will show up on GitHub - description: >- - Testcontainers is a Python library that providing a friendly - API to run Docker container. It is designed to create runtime environment - to use during your automatic tests. + description: "Python library that providing a friendly API to run Docker containers when from tests." # A URL with more information about the repository homepage: https://testcontainers-python.readthedocs.io/en/latest @@ -141,10 +138,9 @@ branches: # Required. Require status checks to pass before merging. Set to null to disable required_status_checks: # Required. Require branches to be up to date before merging. - strict: false + strict: true # Required. The list of status checks to require in order to merge into this branch - contexts: - - "core / test (ubuntu, 3.11)" + contexts: ["core"] # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. enforce_admins: false # Prevent merge commits from being pushed to matching branches From 2db8e6d123d42b57309408dd98ba9a06acc05c4b Mon Sep 17 00:00:00 2001 From: Kevin Wittek Date: Fri, 1 Mar 2024 17:48:04 +0100 Subject: [PATCH 283/425] feat(core)!: add support for `tc.host` and de-prioritise `docker:dind` (#388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What does this PR do? Adds support for reading the `tc.host` property from the `~/.testcontainers.properties` file. This config will have the highest priority for configuring the Docker client. ## Why is it important This brings [Testcontainers Desktop](https://testcontainers.com/desktop/) support to testcontainers-python and aligns this language with the `TestcontainersHostStrategy` found it testcontainers-java. ## Misc I wasn't able to get a working testcontainers-python development environment set up on my M2 MacBook. This seems to be related to some kind of Cython 3.0.0 issue, that I don't fully understand and trying to fix it breaks the installation of further dependencies (like pymssql). So I was only able to test it in an Ubuntu Codespaces environment (also required some weird Cython workarounds). ## Breaking Changes - To prioritise `tc.host` this PR prioritised having that over true `docker:dind` use cases - This means we stopped trying to automatically infer the container host IP when running inside a `docker:dind` container - When using `-v /var/run/docker.sock:/var/run/docker.sock` you should be unaffected since your containers run on the original host --------- Co-authored-by: Bálint Bartha <39852431+totallyzen@users.noreply.github.com> Co-authored-by: David Ankin --- core/testcontainers/core/container.py | 25 ++++++----- core/testcontainers/core/docker_client.py | 51 ++++++++++++++++++----- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 25a818e49..6ecc384bd 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,5 +1,4 @@ import contextlib -import os from platform import system from typing import Optional @@ -102,18 +101,18 @@ def get_container_host_ip(self) -> str: if host == "localnpipe" and system() == "Windows": return "localhost" - # check testcontainers itself runs inside docker container - if inside_container() and not os.getenv("DOCKER_HOST"): - # If newly spawned container's gateway IP address from the docker - # "bridge" network is equal to detected host address, we should use - # container IP address, otherwise fall back to detected host - # address. Even it's inside container, we need to double check, - # because docker host might be set to docker:dind, usually in CI/CD environment - gateway_ip = self.get_docker_client().gateway_ip(self._container.id) - - if gateway_ip == host: - return self.get_docker_client().bridge_ip(self._container.id) - return gateway_ip + # # check testcontainers itself runs inside docker container + # if inside_container() and not os.getenv("DOCKER_HOST") and not host.startswith("http://"): + # # If newly spawned container's gateway IP address from the docker + # # "bridge" network is equal to detected host address, we should use + # # container IP address, otherwise fall back to detected host + # # address. Even it's inside container, we need to double check, + # # because docker host might be set to docker:dind, usually in CI/CD environment + # gateway_ip = self.get_docker_client().gateway_ip(self._container.id) + + # if gateway_ip == host: + # return self.get_docker_client().bridge_ip(self._container.id) + # return gateway_ip return host @wait_container_is_ready() diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 3c724ac3c..05d5377a3 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -14,6 +14,8 @@ import functools as ft import os import urllib +from os.path import exists +from pathlib import Path from typing import Optional, Union import docker @@ -23,15 +25,8 @@ from .utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) - - -def _stop_container(container: Container) -> None: - try: - container.stop() - except NotFound: - pass - except Exception as ex: - LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex) +TC_FILE = ".testcontainers.properties" +TC_GLOBAL = Path.home() / TC_FILE class DockerClient: @@ -40,7 +35,13 @@ class DockerClient: """ def __init__(self, **kwargs) -> None: - self.client = docker.from_env(**kwargs) + docker_host = read_tc_properties().get("tc.host") + + if docker_host: + LOGGER.info(f"using host {docker_host}") + self.client = docker.DockerClient(base_url=docker_host) + else: + self.client = docker.from_env(**kwargs) @ft.wraps(ContainerCollection.run) def run( @@ -123,3 +124,33 @@ def host(self) -> str: if ip_address: return ip_address return "localhost" + + +@ft.cache +def read_tc_properties() -> dict[str, str]: + """ + Read the .testcontainers.properties for settings. (see the Java implementation for details) + Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later. + + :return: the merged properties from the sources. + """ + tc_files = [item for item in [TC_GLOBAL] if exists(item)] + if not tc_files: + return {} + settings = {} + + for file in tc_files: + tuples = [] + with open(file) as contents: + tuples = [line.split("=") for line in contents.readlines() if "=" in line] + settings = {**settings, **{item[0]: item[1] for item in tuples}} + return settings + + +def _stop_container(container: Container) -> None: + try: + container.stop() + except NotFound: + pass + except Exception as ex: + LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex) From 7358b4919c1010315a384a8f0fe2860e5a0ca6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:21:44 +0100 Subject: [PATCH 284/425] fix(build): early exit strategy for modules (#437) # changes Fix build where cancelling the builds were reported as failures (`gh run cancel` isn't just cancel but fail too). Now we produce a dynamic matrix instead and safely skip the test AND report that we've skipped. :pray: --- .github/workflows/ci-community.yml | 72 +++++++++++++----------------- modules/README.md | 3 ++ 2 files changed, 33 insertions(+), 42 deletions(-) create mode 100644 modules/README.md diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index b5b1c65f2..5188b9d43 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -4,63 +4,51 @@ name: modules on: push: - branches: [main] + branches: [ main ] paths: - "modules/**" pull_request: - branches: [main] + branches: [ main ] paths: - "modules/**" -permissions: - actions: write # needed for self-cancellation - jobs: - test: - strategy: - fail-fast: false - matrix: - python-version: ["3.11"] - module: - - arangodb - - azurite - - clickhouse - - elasticsearch - - google - - kafka - - keycloak - - localstack - - minio - - mongodb - - mssql - - mysql - - neo4j - - nginx - - opensearch - - oracle - - postgres - - rabbitmq - - redis - - selenium - - k3s + track-modules: runs-on: ubuntu-latest steps: - name: Checkout contents uses: actions/checkout@v4 + with: + fetch-depth: 0 # recommended by tj-actions/changed-files - name: Get changed files - id: changes-for-module + id: changed-files uses: tj-actions/changed-files@v42 with: - files: | - modules/${{ matrix.module }}/** - - name: Exit early, nothing to do - if: ${{ steps.changes-for-module.outputs.any_changed == 'false' }} + path: "./modules" + diff_relative: true + dir_names: true + dir_names_exclude_current_dir: true + json: true + - name: Compute modules from files + id: compute-changes run: | - # cancel and wait for run to end - gh run cancel ${{ github.run_id }} - gh run watch ${{ github.run_id }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + modules=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | jq '.[] | split("/") | first' | jq -s -c '. | unique') + echo "computed_modules=$modules" + echo "computed_modules=$modules" >> $GITHUB_OUTPUT + outputs: + changed_modules: ${{ steps.compute-changes.outputs.computed_modules }} + test: + needs: [track-modules] + if: ${{ needs.track-modules.outputs.changed_modules != '[]' }} + strategy: + fail-fast: false + matrix: + python-version: [ "3.11" ] + module: ${{ fromJSON(needs.track-modules.outputs.changed_modules) }} + runs-on: ubuntu-latest + steps: + - name: Checkout contents + uses: actions/checkout@v4 - name: Set up Python uses: ./.github/actions/setup-env with: diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 000000000..012c3f552 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,3 @@ +# Modules + +The modules directory contains all the community-supported containers that see common use cases and merit their own easy-access container. From 1223583d8fc3a1ab95441d82c7e1ece57f026fbf Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Mon, 4 Mar 2024 09:49:05 +0100 Subject: [PATCH 285/425] fix(build): add `pre-commit` as a dev dependency to simplify local dev and CI (#438) I could not install pre-commit handler because the package was missing in [tool.poetry.group.dev.dependencies]. I also updated the configuration for the pre-commit handler as black and ruff packages were outdated. Also removed deprecation for ruff v0.3.0 config in pyproject.toml. --------- Co-authored-by: Balint Bartha <39852431+totallyzen@users.noreply.github.com> --- .github/workflows/ci-lint.yml | 8 +-- .pre-commit-config.yaml | 6 +- poetry.lock | 125 +++++++++++++++++++++++++++++++++- pyproject.toml | 14 ++-- 4 files changed, 136 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 0a9f4e265..a02136ece 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -18,8 +18,6 @@ jobs: with: python-version: "3.9" # the pre-commit is hooked in as 3.9 - name: Install Python dependencies - run: poetry install - - name: Install pre-commit - run: pip install pre-commit - - name: Run linter - run: pre-commit run -a + run: poetry install --no-interaction + - name: Execute pre-commit handler + run: poetry run pre-commit run -a diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d2a53b63..c5b94bdde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: python3.9 + python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks @@ -10,13 +10,13 @@ repos: - id: end-of-file-fixer - repo: https://github.com/psf/black-pre-commit-mirror - rev: '24.1.1' + rev: '24.2.0' hooks: - id: black args: [ '--config', 'pyproject.toml' ] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.1.14' + rev: 'v0.3.0' hooks: - id: ruff # Explicitly setting config to prevent Ruff from using `pyproject.toml` in sub packages. diff --git a/poetry.lock b/poetry.lock index 5cf56c8cb..0d457dfe0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -289,6 +289,17 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -676,6 +687,17 @@ files = [ [package.dependencies] packaging = "*" +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + [[package]] name = "dnspython" version = "2.5.0" @@ -760,6 +782,22 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] name = "google-api-core" version = "2.15.0" @@ -1036,6 +1074,20 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "identify" +version = "2.5.35" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.6" @@ -1460,6 +1512,20 @@ files = [ {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "oauthlib" version = "3.2.2" @@ -1570,6 +1636,21 @@ files = [ [package.extras] testing = ["pytest", "pytest-cov"] +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + [[package]] name = "pluggy" version = "1.4.0" @@ -1585,6 +1666,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.6.2" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, + {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "proto-plus" version = "1.23.0" @@ -2186,7 +2285,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -optional = true +optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, @@ -2435,7 +2534,7 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} name = "setuptools" version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, @@ -2854,6 +2953,26 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "virtualenv" +version = "20.25.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "websocket-client" version = "1.7.0" @@ -3004,4 +3123,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "0316bbdbb2824f6086c32cb9ed00a23507896bf48494a38d086e281faf1f8189" +content-hash = "6c7e7a7b9269c6b071cbd92eaf4b7254b4b44b804179fec32aa66b43a1671bda" diff --git a/pyproject.toml b/pyproject.toml index 2059926c9..a221da2e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,12 +108,13 @@ redis = ["redis"] selenium = ["selenium"] [tool.poetry.group.dev.dependencies] +mypy = "1.7.1" +pre-commit = "^3.6" +pg8000 = "*" pytest = "7.4.3" pytest-cov = "4.1.0" sphinx = "^7.2.6" -pg8000 = "*" twine = "^4.0.2" -mypy = "1.7.1" [[tool.poetry.source]] name = "PyPI" @@ -139,15 +140,14 @@ exclude_lines = [ "raise NotImplementedError" # TODO: used in core/generic.py, not sure we need DbContainer ] -[tool.ruff.flake8-type-checking] -strict = true - [tool.ruff] target-version = "py39" line-length = 120 fix = true -fixable = ["I"] src = ["core", "modules/*"] + +[tool.ruff.lint] +fixable = ["I"] exclude = ["**/tests/**/*.py"] select = [ # flake8-2020 @@ -194,6 +194,8 @@ ignore = [ "INP001" ] +[tool.ruff.lint.flake8-type-checking] +strict = true [tool.mypy] python_version = "3.9" From 750e12a41172ce4aaf045c61dec33d318dc3c2f6 Mon Sep 17 00:00:00 2001 From: Vinicius Morais Dutra Date: Tue, 5 Mar 2024 00:00:52 -0300 Subject: [PATCH 286/425] docs: Sphinx - Add title to each doc page (#443) It may be useful for Google SEO to index the pages **Before:** ![Screenshot 2024-01-31 at 11 23 38](https://github.com/testcontainers/testcontainers-python/assets/16025055/f9795d8f-07f0-43f6-b087-73a331ff588d) **After:** ![Screenshot 2024-01-31 at 11 31 09](https://github.com/testcontainers/testcontainers-python/assets/16025055/cea026ac-afa0-41b2-a098-9377be8e0e1b) Co-authored-by: Vinicius Morais Dutra --- modules/arangodb/README.rst | 1 + modules/azurite/README.rst | 1 + modules/clickhouse/README.rst | 1 + modules/elasticsearch/README.rst | 1 + modules/google/README.rst | 1 + modules/k3s/README.rst | 1 + modules/kafka/README.rst | 1 + modules/keycloak/README.rst | 1 + modules/localstack/README.rst | 1 + modules/minio/README.rst | 1 + modules/mongodb/README.rst | 1 + modules/mssql/README.rst | 1 + modules/mysql/README.rst | 1 + modules/neo4j/README.rst | 1 + modules/nginx/README.rst | 1 + modules/opensearch/README.rst | 1 + modules/oracle/README.rst | 1 + modules/postgres/README.rst | 1 + modules/rabbitmq/README.rst | 1 + modules/redis/README.rst | 1 + modules/selenium/README.rst | 1 + 21 files changed, 21 insertions(+) diff --git a/modules/arangodb/README.rst b/modules/arangodb/README.rst index 7f2837f2a..d1a6cd255 100644 --- a/modules/arangodb/README.rst +++ b/modules/arangodb/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.arangodb.ArangoDbContainer +.. title:: testcontainers.arangodb.ArangoDbContainer diff --git a/modules/azurite/README.rst b/modules/azurite/README.rst index 793b5124a..b6cf724c9 100644 --- a/modules/azurite/README.rst +++ b/modules/azurite/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.azurite.AzuriteContainer +.. title:: testcontainers.azurite.AzuriteContainer diff --git a/modules/clickhouse/README.rst b/modules/clickhouse/README.rst index 9075cf628..0835d4c04 100644 --- a/modules/clickhouse/README.rst +++ b/modules/clickhouse/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.clickhouse.ClickHouseContainer +.. title:: testcontainers.clickhouse.ClickHouseContainer diff --git a/modules/elasticsearch/README.rst b/modules/elasticsearch/README.rst index 5c6555d32..c2ce1930f 100644 --- a/modules/elasticsearch/README.rst +++ b/modules/elasticsearch/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer +.. title:: testcontainers.elasticsearch.ElasticSearchContainer diff --git a/modules/google/README.rst b/modules/google/README.rst index dc03a080e..2f8c14d8f 100644 --- a/modules/google/README.rst +++ b/modules/google/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.google.PubSubContainer +.. title:: testcontainers.google.PubSubContainer diff --git a/modules/k3s/README.rst b/modules/k3s/README.rst index 51e4c5020..11f7adc05 100644 --- a/modules/k3s/README.rst +++ b/modules/k3s/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.k3s.K3SContainer +.. title:: testcontainers.k3s.K3SContainer diff --git a/modules/kafka/README.rst b/modules/kafka/README.rst index a4846e335..144c0fc2a 100644 --- a/modules/kafka/README.rst +++ b/modules/kafka/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.kafka.KafkaContainer +.. title:: testcontainers.kafka.KafkaContainer diff --git a/modules/keycloak/README.rst b/modules/keycloak/README.rst index 3cce2d062..6eb045f4a 100644 --- a/modules/keycloak/README.rst +++ b/modules/keycloak/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.keycloak.KeycloakContainer +.. title:: testcontainers.keycloak.KeycloakContainer diff --git a/modules/localstack/README.rst b/modules/localstack/README.rst index 05df74274..66cbf4d3c 100644 --- a/modules/localstack/README.rst +++ b/modules/localstack/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.localstack.LocalStackContainer +.. title:: testcontainers.localstack.LocalStackContainer diff --git a/modules/minio/README.rst b/modules/minio/README.rst index 6be8abb79..409151787 100644 --- a/modules/minio/README.rst +++ b/modules/minio/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.minio.MinioContainer +.. title:: testcontainers.minio.MinioContainer diff --git a/modules/mongodb/README.rst b/modules/mongodb/README.rst index d8f9cdf58..37e836406 100644 --- a/modules/mongodb/README.rst +++ b/modules/mongodb/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.mongodb.MongoDbContainer +.. title:: testcontainers.mongodb.MongoDbContainer diff --git a/modules/mssql/README.rst b/modules/mssql/README.rst index 8a2f026d2..56368ca71 100644 --- a/modules/mssql/README.rst +++ b/modules/mssql/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.mssql.SqlServerContainer +.. title:: testcontainers.mssql.SqlServerContainer diff --git a/modules/mysql/README.rst b/modules/mysql/README.rst index d5b52d1d5..d69785cd7 100644 --- a/modules/mysql/README.rst +++ b/modules/mysql/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.mysql.MySqlContainer +.. title:: testcontainers.mysql.MySqlContainer diff --git a/modules/neo4j/README.rst b/modules/neo4j/README.rst index 42691ba0e..885d4a598 100644 --- a/modules/neo4j/README.rst +++ b/modules/neo4j/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.neo4j.Neo4jContainer +.. title:: testcontainers.neo4j.Neo4jContainer diff --git a/modules/nginx/README.rst b/modules/nginx/README.rst index ff1504759..a949a93d1 100644 --- a/modules/nginx/README.rst +++ b/modules/nginx/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.nginx.NginxContainer +.. title:: testcontainers.nginx.NginxContainer diff --git a/modules/opensearch/README.rst b/modules/opensearch/README.rst index 8848f0c98..fdc450fd6 100644 --- a/modules/opensearch/README.rst +++ b/modules/opensearch/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.opensearch.OpenSearchContainer +.. title:: testcontainers.opensearch.OpenSearchContainer diff --git a/modules/oracle/README.rst b/modules/oracle/README.rst index 390c331a0..bbd5d3b5d 100644 --- a/modules/oracle/README.rst +++ b/modules/oracle/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.oracle.OracleDbContainer +.. title:: testcontainers.oracle.OracleDbContainer diff --git a/modules/postgres/README.rst b/modules/postgres/README.rst index 7dc4a8a90..bce939244 100644 --- a/modules/postgres/README.rst +++ b/modules/postgres/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.postgres.PostgresContainer +.. title:: testcontainers.postgres.PostgresContainer diff --git a/modules/rabbitmq/README.rst b/modules/rabbitmq/README.rst index 15c66224b..1b31362d3 100644 --- a/modules/rabbitmq/README.rst +++ b/modules/rabbitmq/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.rabbitmq.RabbitMqContainer +.. title:: testcontainers.rabbitmq.RabbitMqContainer diff --git a/modules/redis/README.rst b/modules/redis/README.rst index 333cb246f..1dfcf9e35 100644 --- a/modules/redis/README.rst +++ b/modules/redis/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.redis.RedisContainer +.. title:: testcontainers.redis.RedisContainer diff --git a/modules/selenium/README.rst b/modules/selenium/README.rst index 9308568e9..5beef79cd 100644 --- a/modules/selenium/README.rst +++ b/modules/selenium/README.rst @@ -1 +1,2 @@ .. autoclass:: testcontainers.selenium.BrowserWebDriverContainer +.. title:: testcontainers.selenium.BrowserWebDriverContainer From 71cb75b281df55ece4d5caf5d487059a7f38c34f Mon Sep 17 00:00:00 2001 From: jondan <48798545+jonathandannenberg@users.noreply.github.com> Date: Tue, 5 Mar 2024 23:08:49 +0100 Subject: [PATCH 287/425] fix: rabbitmq readiness probe (#375) Add pika AMQPConnectionError to transient errors fixes #348 --------- Co-authored-by: David Ankin --- modules/rabbitmq/testcontainers/rabbitmq/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py index 6c26518c8..0a5486025 100644 --- a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -51,7 +51,7 @@ def __init__( self.with_env("RABBITMQ_DEFAULT_USER", self.username) self.with_env("RABBITMQ_DEFAULT_PASS", self.password) - @wait_container_is_ready(pika.exceptions.IncompatibleProtocolError) + @wait_container_is_ready(pika.exceptions.IncompatibleProtocolError, pika.exceptions.AMQPConnectionError) def readiness_probe(self) -> bool: """Test if the RabbitMQ broker is ready.""" connection = pika.BlockingConnection(self.get_connection_params()) From 87b5873c1ec3a3e4e74742417d6068fa86cf1762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:59:47 +0100 Subject: [PATCH 288/425] fix(release): prove that the release process updates the version (#444) # change Set the version to the latest actual release candidate done by @alexanderankin. # Context In the first release-please PR #433 , we're not setting the version on the `pyproject.toml`, suspecting that it still works, but want to prove there are no issues with the automation. The easiest proof of this is to set the value in the file to the actual release candidate. `release-please` in the future will be able to do release candidates by running `release-please` from the command line (documentation to be made on this) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a221da2e5..909d538e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.0.0" # auto-incremented by release-please +version = "4.0.0rc2" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 5356caf2de056313a5b3f2805ed80e6a23b027a8 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 6 Mar 2024 08:19:27 -0500 Subject: [PATCH 289/425] feat(compose)!: implement compose v2 with improved typing (#426) relates to #306, #358 --- core/testcontainers/compose/__init__.py | 8 + core/testcontainers/compose/compose.py | 406 ++++++++++++++++++ core/testcontainers/core/exceptions.py | 4 + .../basic/docker-compose.yaml | 10 + .../port_multiple/compose.yaml | 28 ++ .../compose_fixtures/port_single/compose.yaml | 14 + core/tests/test_compose.py | 243 +++++++++++ 7 files changed, 713 insertions(+) create mode 100644 core/testcontainers/compose/__init__.py create mode 100644 core/testcontainers/compose/compose.py create mode 100644 core/tests/compose_fixtures/basic/docker-compose.yaml create mode 100644 core/tests/compose_fixtures/port_multiple/compose.yaml create mode 100644 core/tests/compose_fixtures/port_single/compose.yaml create mode 100644 core/tests/test_compose.py diff --git a/core/testcontainers/compose/__init__.py b/core/testcontainers/compose/__init__.py new file mode 100644 index 000000000..9af994f30 --- /dev/null +++ b/core/testcontainers/compose/__init__.py @@ -0,0 +1,8 @@ +# flake8: noqa +from testcontainers.compose.compose import ( + ContainerIsNotRunning, + NoSuchPortExposed, + PublishedPort, + ComposeContainer, + DockerCompose, +) diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py new file mode 100644 index 000000000..e72824bd1 --- /dev/null +++ b/core/testcontainers/compose/compose.py @@ -0,0 +1,406 @@ +import subprocess +from dataclasses import dataclass, field, fields +from functools import cached_property +from json import loads +from os import PathLike +from re import split +from typing import Callable, Literal, Optional, TypeVar, Union +from urllib.error import HTTPError, URLError +from urllib.request import urlopen + +from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed +from testcontainers.core.waiting_utils import wait_container_is_ready + +_IPT = TypeVar("_IPT") + + +def _ignore_properties(cls: type[_IPT], dict_: any) -> _IPT: + """omits extra fields like @JsonIgnoreProperties(ignoreUnknown = true) + + https://gist.github.com/alexanderankin/2a4549ac03554a31bef6eaaf2eaf7fd5""" + if isinstance(dict_, cls): + return dict_ + class_fields = {f.name for f in fields(cls)} + filtered = {k: v for k, v in dict_.items() if k in class_fields} + return cls(**filtered) + + +@dataclass +class PublishedPort: + """ + Class that represents the response we get from compose when inquiring status + via `DockerCompose.get_running_containers()`. + """ + + URL: Optional[str] = None + TargetPort: Optional[str] = None + PublishedPort: Optional[str] = None + Protocol: Optional[str] = None + + +OT = TypeVar("OT") + + +def get_only_element_or_raise(array: list[OT], exception: Callable[[], Exception]) -> OT: + if len(array) != 1: + e = exception() + raise e + return array[0] + + +@dataclass +class ComposeContainer: + """ + A container class that represents a container managed by compose. + It is not a true testcontainers.core.container.DockerContainer, + but you can use the id with DockerClient to get that one too. + """ + + ID: Optional[str] = None + Name: Optional[str] = None + Command: Optional[str] = None + Project: Optional[str] = None + Service: Optional[str] = None + State: Optional[str] = None + Health: Optional[str] = None + ExitCode: Optional[str] = None + Publishers: list[PublishedPort] = field(default_factory=list) + + def __post_init__(self): + if self.Publishers: + self.Publishers = [_ignore_properties(PublishedPort, p) for p in self.Publishers] + + def get_publisher( + self, + by_port: Optional[int] = None, + by_host: Optional[str] = None, + prefer_ip_version: Literal["IPV4", "IPv6"] = "IPv4", + ) -> PublishedPort: + remaining_publishers = self.Publishers + + remaining_publishers = [r for r in remaining_publishers if self._matches_protocol(prefer_ip_version, r)] + + if by_port: + remaining_publishers = [item for item in remaining_publishers if by_port == item.TargetPort] + if by_host: + remaining_publishers = [item for item in remaining_publishers if by_host == item.URL] + if len(remaining_publishers) == 0: + raise NoSuchPortExposed(f"Could not find publisher for for service {self.Service}") + return get_only_element_or_raise( + remaining_publishers, + lambda: NoSuchPortExposed( + "get_publisher failed because there is " + f"not exactly 1 publisher for service {self.Service}" + f" when filtering by_port={by_port}, by_host={by_host}" + f" (but {len(remaining_publishers)})" + ), + ) + + @staticmethod + def _matches_protocol(prefer_ip_version, r): + return (":" in r.URL) is (prefer_ip_version == "IPv6") + + +@dataclass +class DockerCompose: + """ + Manage docker compose environments. + + Args: + context: + The docker context. It corresponds to the directory containing + the docker compose configuration file. + compose_file_name: + Optional. File name of the docker compose configuration file. + If specified, you need to also specify the overrides if any. + pull: + Pull images before launching environment. + build: + Run `docker compose build` before running the environment. + wait: + Wait for the services to be healthy + (as per healthcheck definitions in the docker compose configuration) + env_file: + Path to an '.env' file containing environment variables + to pass to docker compose. + services: + The list of services to use from this DockerCompose. + client_args: + arguments to pass to docker.from_env() + + Example: + + This example spins up chrome and firefox containers using docker compose. + + .. doctest:: + + >>> from testcontainers.compose import DockerCompose + + >>> compose = DockerCompose("compose/tests", compose_file_name="docker-compose-4.yml", + ... pull=True) + >>> with compose: + ... stdout, stderr = compose.get_logs() + >>> b"Hello from Docker!" in stdout + True + + .. code-block:: yaml + + services: + hello-world: + image: "hello-world" + """ + + context: Union[str, PathLike] + compose_file_name: Optional[Union[str, list[str]]] = None + pull: bool = False + build: bool = False + wait: bool = True + env_file: Optional[str] = None + services: Optional[list[str]] = None + + def __post_init__(self): + if isinstance(self.compose_file_name, str): + self.compose_file_name = [self.compose_file_name] + + def __enter__(self) -> "DockerCompose": + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.stop() + + def docker_compose_command(self) -> list[str]: + """ + Returns command parts used for the docker compose commands + + Returns: + cmd: Docker compose command parts. + """ + return self.compose_command_property + + @cached_property + def compose_command_property(self) -> list[str]: + docker_compose_cmd = ["docker", "compose"] + if self.compose_file_name: + for file in self.compose_file_name: + docker_compose_cmd += ["-f", file] + if self.env_file: + docker_compose_cmd += ["--env-file", self.env_file] + return docker_compose_cmd + + def start(self) -> None: + """ + Starts the docker compose environment. + """ + base_cmd = self.compose_command_property or [] + + # pull means running a separate command before starting + if self.pull: + pull_cmd = [*base_cmd, "pull"] + self._call_command(cmd=pull_cmd) + + up_cmd = [*base_cmd, "up"] + + # build means modifying the up command + if self.build: + up_cmd.append("--build") + + if self.wait: + up_cmd.append("--wait") + else: + # we run in detached mode instead of blocking + up_cmd.append("--detach") + + if self.services: + up_cmd.extend(self.services) + + self._call_command(cmd=up_cmd) + + def stop(self, down=True) -> None: + """ + Stops the docker compose environment. + """ + down_cmd = self.compose_command_property[:] + if down: + down_cmd += ["down", "--volumes"] + else: + down_cmd += ["stop"] + self._call_command(cmd=down_cmd) + + def get_logs(self, *services: str) -> tuple[str, str]: + """ + Returns all log output from stdout and stderr of a specific container. + + :param services: which services to get the logs for (or omit, for all) + + Returns: + stdout: Standard output stream. + stderr: Standard error stream. + """ + logs_cmd = [*self.compose_command_property, "logs", *services] + + result = subprocess.run( + logs_cmd, + cwd=self.context, + capture_output=True, + ) + return result.stdout.decode("utf-8"), result.stderr.decode("utf-8") + + def get_containers(self, include_all=False) -> list[ComposeContainer]: + """ + Fetch information about running containers via `docker compose ps --format json`. + Available only in V2 of compose. + + Returns: + The list of running containers. + + """ + + cmd = [*self.compose_command_property, "ps", "--format", "json"] + if include_all: + cmd = [*cmd, "-a"] + result = subprocess.run(cmd, cwd=self.context, check=True, stdout=subprocess.PIPE) + stdout = split(r"\r?\n", result.stdout.decode("utf-8")) + + containers = [] + # one line per service in docker 25, single array for docker 24.0.2 + for line in stdout: + if not line: + continue + data = loads(line) + if isinstance(data, list): + containers += [_ignore_properties(ComposeContainer, d) for d in data] + else: + containers.append(_ignore_properties(ComposeContainer, data)) + + return containers + + def get_container( + self, + service_name: Optional[str] = None, + include_all: bool = False, + ) -> ComposeContainer: + if not service_name: + containers = self.get_containers(include_all=include_all) + return get_only_element_or_raise( + containers, + lambda: ContainerIsNotRunning( + "get_container failed because no service_name given " + f"and there is not exactly 1 container (but {len(containers)})" + ), + ) + + matching_containers = [ + item for item in self.get_containers(include_all=include_all) if item.Service == service_name + ] + + if not matching_containers: + raise ContainerIsNotRunning(f"{service_name} is not running in the compose context") + + return matching_containers[0] + + def exec_in_container( + self, + command: list[str], + service_name: Optional[str] = None, + ) -> tuple[str, str, int]: + """ + Executes a command in the container of one of the services. + + Args: + service_name: Name of the docker compose service to run the command in. + command: Command to execute. + + :param service_name: specify the service name + :param command: the command to run in the container + + Returns: + stdout: Standard output stream. + stderr: Standard error stream. + exit_code: The command's exit code. + """ + if not service_name: + service_name = self.get_container().Service + exec_cmd = [*self.compose_command_property, "exec", "-T", service_name, *command] + result = subprocess.run( + exec_cmd, + cwd=self.context, + capture_output=True, + check=True, + ) + + return (result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode) + + def _call_command( + self, + cmd: Union[str, list[str]], + context: Optional[str] = None, + ) -> None: + context = context or self.context + subprocess.call(cmd, cwd=context) + + def get_service_port( + self, + service_name: Optional[str] = None, + port: Optional[int] = None, + ): + """ + Returns the mapped port for one of the services. + + Parameters + ---------- + service_name: str + Name of the docker compose service + port: int + The internal port to get the mapping for + + Returns + ------- + str: + The mapped port on the host + """ + return self.get_container(service_name).get_publisher(by_port=port).PublishedPort + + def get_service_host( + self, + service_name: Optional[str] = None, + port: Optional[int] = None, + ): + """ + Returns the host for one of the services. + + Parameters + ---------- + service_name: str + Name of the docker compose service + port: int + The internal port to get the host for + + Returns + ------- + str: + The hostname for the service + """ + return self.get_container(service_name).get_publisher(by_port=port).URL + + def get_service_host_and_port( + self, + service_name: Optional[str] = None, + port: Optional[int] = None, + ): + publisher = self.get_container(service_name).get_publisher(by_port=port) + return publisher.URL, publisher.PublishedPort + + @wait_container_is_ready(HTTPError, URLError) + def wait_for(self, url: str) -> "DockerCompose": + """ + Waits for a response from a given URL. This is typically used to block until a service in + the environment has started and is responding. Note that it does not assert any sort of + return code, only check that the connection was successful. + + Args: + url: URL from one of the services in the environment to use to wait on. + """ + with urlopen(url) as response: + response.read() + return self diff --git a/core/testcontainers/core/exceptions.py b/core/testcontainers/core/exceptions.py index 8bf027630..6694e598b 100644 --- a/core/testcontainers/core/exceptions.py +++ b/core/testcontainers/core/exceptions.py @@ -16,5 +16,9 @@ class ContainerStartException(RuntimeError): pass +class ContainerIsNotRunning(RuntimeError): + pass + + class NoSuchPortExposed(RuntimeError): pass diff --git a/core/tests/compose_fixtures/basic/docker-compose.yaml b/core/tests/compose_fixtures/basic/docker-compose.yaml new file mode 100644 index 000000000..ff3f74220 --- /dev/null +++ b/core/tests/compose_fixtures/basic/docker-compose.yaml @@ -0,0 +1,10 @@ +version: '3.0' + +services: + alpine: + image: alpine:latest + init: true + command: + - sh + - -c + - 'while true; do sleep 0.1 ; date -Ins; done' diff --git a/core/tests/compose_fixtures/port_multiple/compose.yaml b/core/tests/compose_fixtures/port_multiple/compose.yaml new file mode 100644 index 000000000..65717fc4a --- /dev/null +++ b/core/tests/compose_fixtures/port_multiple/compose.yaml @@ -0,0 +1,28 @@ +version: '3.0' + +services: + alpine: + image: nginx:alpine-slim + init: true + ports: + - '81' + - '82' + - target: 80 + host_ip: 127.0.0.1 + protocol: tcp + command: + - sh + - -c + - 'd=/etc/nginx/conf.d; echo "server { listen 81; location / { return 202; } }" > $$d/81.conf && echo "server { listen 82; location / { return 204; } }" > $$d/82.conf && nginx -g "daemon off;"' + + alpine2: + image: nginx:alpine-slim + init: true + ports: + - target: 80 + host_ip: 127.0.0.1 + protocol: tcp + command: + - sh + - -c + - 'd=/etc/nginx/conf.d; echo "server { listen 81; location / { return 202; } }" > $$d/81.conf && echo "server { listen 82; location / { return 204; } }" > $$d/82.conf && nginx -g "daemon off;"' diff --git a/core/tests/compose_fixtures/port_single/compose.yaml b/core/tests/compose_fixtures/port_single/compose.yaml new file mode 100644 index 000000000..d1bf9eb45 --- /dev/null +++ b/core/tests/compose_fixtures/port_single/compose.yaml @@ -0,0 +1,14 @@ +version: '3.0' + +services: + alpine: + image: nginx:alpine-slim + init: true + ports: + - target: 80 + host_ip: 127.0.0.1 + protocol: tcp + command: + - sh + - -c + - 'nginx -g "daemon off;"' diff --git a/core/tests/test_compose.py b/core/tests/test_compose.py new file mode 100644 index 000000000..0a244220b --- /dev/null +++ b/core/tests/test_compose.py @@ -0,0 +1,243 @@ +from pathlib import Path +from re import split +from time import sleep +from typing import Union +from urllib.request import urlopen, Request + +import pytest + +from testcontainers.compose import DockerCompose, ContainerIsNotRunning, NoSuchPortExposed + +FIXTURES = Path(__file__).parent.joinpath("compose_fixtures") + + +def test_compose_no_file_name(): + basic = DockerCompose(context=FIXTURES / "basic") + assert basic.compose_file_name is None + + +def test_compose_str_file_name(): + basic = DockerCompose(context=FIXTURES / "basic", compose_file_name="docker-compose.yaml") + assert basic.compose_file_name == ["docker-compose.yaml"] + + +def test_compose_list_file_name(): + basic = DockerCompose(context=FIXTURES / "basic", compose_file_name=["docker-compose.yaml"]) + assert basic.compose_file_name == ["docker-compose.yaml"] + + +def test_compose_stop(): + basic = DockerCompose(context=FIXTURES / "basic") + basic.stop() + + +def test_compose_start_stop(): + basic = DockerCompose(context=FIXTURES / "basic") + basic.start() + basic.stop() + + +def test_compose(): + """stream-of-consciousness e2e test""" + basic = DockerCompose(context=FIXTURES / "basic") + try: + # first it does not exist + containers = basic.get_containers(include_all=True) + assert len(containers) == 0 + + # then we create it and it exists + basic.start() + containers = basic.get_containers(include_all=True) + assert len(containers) == 1 + containers = basic.get_containers() + assert len(containers) == 1 + + # test that get_container returns the same object, value assertions, etc + from_all = containers[0] + assert from_all.State == "running" + assert from_all.Service == "alpine" + + by_name = basic.get_container("alpine") + + assert by_name.Name == from_all.Name + assert by_name.Service == from_all.Service + assert by_name.State == from_all.State + assert by_name.ID == from_all.ID + + assert by_name.ExitCode == 0 + + # what if you want to get logs after it crashes: + basic.stop(down=False) + + with pytest.raises(ContainerIsNotRunning): + assert basic.get_container("alpine") is None + + # what it looks like after it exits + stopped = basic.get_container("alpine", include_all=True) + assert stopped.State == "exited" + finally: + basic.stop() + + +def test_compose_logs(): + basic = DockerCompose(context=FIXTURES / "basic") + with basic: + sleep(1) # generate some logs every 200ms + stdout, stderr = basic.get_logs() + container = basic.get_container() + + assert not stderr + assert stdout + lines = split(r"\r?\n", stdout) + + assert len(lines) > 5 # actually 10 + for line in lines[1:]: + # either the line is blank or the first column (|-separated) contains the service name + # this is a safe way to split the string + # docker changes the prefix between versions 24 and 25 + assert not line or container.Service in next(iter(line.split("|")), None) + + +# noinspection HttpUrlsUsage +def test_compose_ports(): + # fairly straight forward - can we get the right port to request it + single = DockerCompose(context=FIXTURES / "port_single") + with single: + host, port = single.get_service_host_and_port() + endpoint = f"http://{host}:{port}" + single.wait_for(endpoint) + code, response = fetch(Request(method="GET", url=endpoint)) + assert code == 200 + assert "

" in response + + +# noinspection HttpUrlsUsage +def test_compose_multiple_containers_and_ports(): + """test for the logic encapsulated in 'one' function + + assert correctness of multiple logic + """ + multiple = DockerCompose(context=FIXTURES / "port_multiple") + with multiple: + with pytest.raises(ContainerIsNotRunning) as e: + multiple.get_container() + e.match("get_container failed") + e.match("not exactly 1 container") + + assert multiple.get_container("alpine") + assert multiple.get_container("alpine2") + + a2p = multiple.get_service_port("alpine2") + assert a2p > 0 # > 1024 + + with pytest.raises(NoSuchPortExposed) as e: + multiple.get_service_port("alpine") + e.match("not exactly 1") + with pytest.raises(NoSuchPortExposed) as e: + multiple.get_container("alpine").get_publisher(by_host="example.com") + e.match("not exactly 1") + with pytest.raises(NoSuchPortExposed) as e: + multiple.get_container("alpine").get_publisher(by_host="localhost") + e.match("not exactly 1") + + try: + # this fails when ipv6 is enabled and docker is forwarding for both 4 + 6 + multiple.get_container(service_name="alpine").get_publisher(by_port=81, prefer_ip_version="IPv6") + except: # noqa + pass + + ports = [ + ( + 80, + multiple.get_service_host(service_name="alpine", port=80), + multiple.get_service_port(service_name="alpine", port=80), + ), + ( + 81, + multiple.get_service_host(service_name="alpine", port=81), + multiple.get_service_port(service_name="alpine", port=81), + ), + ( + 82, + multiple.get_service_host(service_name="alpine", port=82), + multiple.get_service_port(service_name="alpine", port=82), + ), + ] + + # test correctness of port lookup + for target, host, mapped in ports: + assert mapped, f"we have a mapped port for target port {target}" + url = f"http://{host}:{mapped}" + code, body = fetch(Request(method="GET", url=url)) + + expected_code = { + 80: 200, + 81: 202, + 82: 204, + }.get(code, None) + + if not expected_code: + continue + + message = f"response '{body}' ({code}) from url {url} should have code {expected_code}" + assert code == expected_code, message + + +# noinspection HttpUrlsUsage +def test_exec_in_container(): + """we test that we can manipulate a container via exec""" + single = DockerCompose(context=FIXTURES / "port_single") + with single: + url = f"http://{single.get_service_host()}:{single.get_service_port()}" + single.wait_for(url) + + # unchanged + code, body = fetch(url) + assert code == 200 + assert "test_exec_in_container" not in body + + # change it + single.exec_in_container( + command=["sh", "-c", 'echo "test_exec_in_container" > /usr/share/nginx/html/index.html'] + ) + + # and it is changed + code, body = fetch(url) + assert code == 200 + assert "test_exec_in_container" in body + + +# noinspection HttpUrlsUsage +def test_exec_in_container_multiple(): + """same as above, except we exec into a particular service""" + multiple = DockerCompose(context=FIXTURES / "port_multiple") + with multiple: + sn = "alpine2" # service name + host, port = multiple.get_service_host_and_port(service_name=sn) + url = f"http://{host}:{port}" + multiple.wait_for(url) + + # unchanged + code, body = fetch(url) + assert code == 200 + assert "test_exec_in_container" not in body + + # change it + multiple.exec_in_container( + command=["sh", "-c", 'echo "test_exec_in_container" > /usr/share/nginx/html/index.html'], service_name=sn + ) + + # and it is changed + code, body = fetch(url) + assert code == 200 + assert "test_exec_in_container" in body + + +def fetch(req: Union[Request, str]): + if isinstance(req, str): + req = Request(method="GET", url=req) + with urlopen(req) as res: + body = res.read().decode("utf-8") + if 200 < res.getcode() >= 400: + raise Exception(f"HTTP Error: {res.getcode()} - {res.reason}: {body}") + return res.getcode(), body From cc4cb3762802dc75b0801727d8b1f1a1c56b7f50 Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Wed, 6 Mar 2024 11:11:34 -0500 Subject: [PATCH 290/425] feat(redis): support AsyncRedisContainer (#442) Co-authored-by: bstrausser --- .../redis/testcontainers/redis/__init__.py | 27 +++++++++++ modules/redis/tests/test_redis.py | 46 ++++++++++++++++++- poetry.lock | 42 +++++++++++++++-- pyproject.toml | 1 + 4 files changed, 112 insertions(+), 4 deletions(-) diff --git a/modules/redis/testcontainers/redis/__init__.py b/modules/redis/testcontainers/redis/__init__.py index fba24be15..7a4d46613 100644 --- a/modules/redis/testcontainers/redis/__init__.py +++ b/modules/redis/testcontainers/redis/__init__.py @@ -14,6 +14,7 @@ from typing import Optional import redis +from redis.asyncio import Redis as asyncRedis from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -69,3 +70,29 @@ def start(self) -> "RedisContainer": super().start() self._connect() return self + + +class AsyncRedisContainer(RedisContainer): + """ + Redis container. + + Example + ------- + .. doctest:: + + >>> from testcontainers.redis import AsyncRedisContainer + + >>> with AsyncRedisContainer() as redis_container: + ... redis_client =await redis_container.get_async_client() + """ + + def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs): + super().__init__(image, port_to_expose, password, **kwargs) + + async def get_async_client(self, **kwargs): + return await asyncRedis( + host=self.get_container_host_ip(), + port=self.get_exposed_port(self.port), + password=self.password, + **kwargs, + ) diff --git a/modules/redis/tests/test_redis.py b/modules/redis/tests/test_redis.py index 7dc56aa46..bd8e244c5 100644 --- a/modules/redis/tests/test_redis.py +++ b/modules/redis/tests/test_redis.py @@ -1,6 +1,7 @@ import time -from testcontainers.redis import RedisContainer +from testcontainers.redis import RedisContainer, AsyncRedisContainer +import pytest def test_docker_run_redis(): @@ -23,6 +24,49 @@ def test_docker_run_redis_with_password(): assert client.get("hello") == "world" +pytest.mark.usefixtures("anyio_backend") + + +@pytest.mark.parametrize("anyio_backend", ["asyncio"]) +async def test_key_set_in_async_redis(anyio_backend): + with AsyncRedisContainer() as container: + async_redis_client: redis.Redis = await container.get_async_client(decode_responses=True) + key = "key" + expected_value = 1 + await async_redis_client.set(key, expected_value) + actual_value = await async_redis_client.get(key) + assert int(actual_value) == expected_value + + +pytest.mark.usefixtures("anyio_backend") + + +@pytest.mark.parametrize("anyio_backend", ["asyncio"]) +@pytest.mark.skip(reason="Need to sort out async pub/sub") +async def test_docker_run_async_redis(anyio_backend): + config = AsyncRedisContainer() + with config as container: + client: redis.Redis = await container.get_async_client(decode_responses=True) + p = await client.pubsub() + await p.subscribe("test") + await client.publish("test", "new_msg") + msg = wait_for_message(p) + assert "data" in msg + assert b"new_msg", msg["data"] + + +pytest.mark.usefixtures("anyio_backend") + + +@pytest.mark.parametrize("anyio_backend", ["asyncio"]) +async def test_docker_run_async_redis_with_password(anyio_backend): + config = AsyncRedisContainer(password="mypass") + with config as container: + client: redis.Redis = await container.get_async_client(decode_responses=True) + await client.set("hello", "world") + assert await client.get("hello") == "world" + + def wait_for_message(pubsub, timeout=1, ignore_subscribe_messages=True): now = time.time() timeout = now + timeout diff --git a/poetry.lock b/poetry.lock index 0d457dfe0..f97690266 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -11,6 +11,28 @@ files = [ {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "argon2-cffi" version = "23.1.0" @@ -1754,6 +1776,7 @@ files = [ {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, @@ -1762,6 +1785,8 @@ files = [ {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, @@ -2293,6 +2318,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2300,8 +2326,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2318,6 +2352,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2325,6 +2360,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2561,7 +2597,7 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, @@ -3123,4 +3159,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "6c7e7a7b9269c6b071cbd92eaf4b7254b4b44b804179fec32aa66b43a1671bda" +content-hash = "f4cb027301e265217ccb581b0ddd06fe6d91319fbcfbc3d20504a1fdbc45d7b1" diff --git a/pyproject.toml b/pyproject.toml index 909d538e6..7f2511feb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,6 +115,7 @@ pytest = "7.4.3" pytest-cov = "4.1.0" sphinx = "^7.2.6" twine = "^4.0.2" +anyio = "^4.3.0" [[tool.poetry.source]] name = "PyPI" From ed3b9faf46c8e04fef3b4d784b31605cf4189ffd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:40:19 +0100 Subject: [PATCH 291/425] chore(main): release testcontainers 4.0.0 (#433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :robot: I have created a release *beep* *boop* --- ## [4.0.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v3.7.1...testcontainers-v4.0.0) (2024-03-06) ### Release Notes The breaking changes are the ones we were able to easily track. If you spot any new issues between `3.7.1` and `4.0.0`, please do report it and we'll do our best to fix everything. The release is now Some kudos from @totallyzen to folks who helped a great deal in starting things again: - kudos to @alexanderankin for his contribution on #426 - kudos to @jankatins for feedback on various PRs including - kudos to @max-pfeiffer and @bearrito for their contributions as well ### ⚠ BREAKING CHANGES * **compose:** implement compose v2 with improved typing ([#426](https://github.com/testcontainers/testcontainers-python/issues/426)) * **core:** add support for `tc.host` and de-prioritise `docker:dind` ([#388](https://github.com/testcontainers/testcontainers-python/issues/388)) ### Features * **build:** use poetry and organise modules ([#408](https://github.com/testcontainers/testcontainers-python/issues/408)) ([6c69583](https://github.com/testcontainers/testcontainers-python/commit/6c695835520bdcbf9824e8cefa00f7613d2a7cb9)) * **compose:** allow running specific services in compose ([f61dcda](https://github.com/testcontainers/testcontainers-python/commit/f61dcda8bd7ea329cd3c836b6d6e2f0bd990335d)) * **compose:** implement compose v2 with improved typing ([#426](https://github.com/testcontainers/testcontainers-python/issues/426)) ([5356caf](https://github.com/testcontainers/testcontainers-python/commit/5356caf2de056313a5b3f2805ed80e6a23b027a8)) * **core:** add support for `tc.host` and de-prioritise `docker:dind` ([#388](https://github.com/testcontainers/testcontainers-python/issues/388)) ([2db8e6d](https://github.com/testcontainers/testcontainers-python/commit/2db8e6d123d42b57309408dd98ba9a06acc05c4b)) * **redis:** support AsyncRedisContainer ([#442](https://github.com/testcontainers/testcontainers-python/issues/442)) ([cc4cb37](https://github.com/testcontainers/testcontainers-python/commit/cc4cb3762802dc75b0801727d8b1f1a1c56b7f50)) * **release:** automate release via release-please ([#429](https://github.com/testcontainers/testcontainers-python/issues/429)) ([30f859e](https://github.com/testcontainers/testcontainers-python/commit/30f859eb1535acd6e93c331213426e1319ee9a47)) ### Bug Fixes * Added URLError to exceptions to wait for in elasticsearch ([0f9ad24](https://github.com/testcontainers/testcontainers-python/commit/0f9ad24f2c0df362ee15b81ce8d7d36b9f98e6e1)) * **build:** add `pre-commit` as a dev dependency to simplify local dev and CI ([#438](https://github.com/testcontainers/testcontainers-python/issues/438)) ([1223583](https://github.com/testcontainers/testcontainers-python/commit/1223583d8fc3a1ab95441d82c7e1ece57f026fbf)) * **build:** early exit strategy for modules ([#437](https://github.com/testcontainers/testcontainers-python/issues/437)) ([7358b49](https://github.com/testcontainers/testcontainers-python/commit/7358b4919c1010315a384a8f0fe2860e5a0ca6b4)) * changed files breaks on main ([#422](https://github.com/testcontainers/testcontainers-python/issues/422)) ([3271357](https://github.com/testcontainers/testcontainers-python/commit/32713578dcf07f672a87818e00562b58874b4a52)) * flaky garbage collection resulting in testing errors ([#423](https://github.com/testcontainers/testcontainers-python/issues/423)) ([b535ea2](https://github.com/testcontainers/testcontainers-python/commit/b535ea255bcaaa546f8cda7b2b17718c1cc7f3ca)) * rabbitmq readiness probe ([#375](https://github.com/testcontainers/testcontainers-python/issues/375)) ([71cb75b](https://github.com/testcontainers/testcontainers-python/commit/71cb75b281df55ece4d5caf5d487059a7f38c34f)) * **release:** prove that the release process updates the version ([#444](https://github.com/testcontainers/testcontainers-python/issues/444)) ([87b5873](https://github.com/testcontainers/testcontainers-python/commit/87b5873c1ec3a3e4e74742417d6068fa86cf1762)) * test linting issue ([427c9b8](https://github.com/testcontainers/testcontainers-python/commit/427c9b841c2f6f516ec6cb74d5bd2839cb1939f4)) ### Documentation * Sphinx - Add title to each doc page ([#443](https://github.com/testcontainers/testcontainers-python/issues/443)) ([750e12a](https://github.com/testcontainers/testcontainers-python/commit/750e12a41172ce4aaf045c61dec33d318dc3c2f6)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 35 +++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 698886db5..4d204362b 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.7.1" + ".": "4.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..033a9c686 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +## [4.0.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v3.7.1...testcontainers-v4.0.0) (2024-03-06) + + +### ⚠ BREAKING CHANGES + +* **compose:** implement compose v2 with improved typing ([#426](https://github.com/testcontainers/testcontainers-python/issues/426)) +* **core:** add support for `tc.host` and de-prioritise `docker:dind` ([#388](https://github.com/testcontainers/testcontainers-python/issues/388)) + +### Features + +* **build:** use poetry and organise modules ([#408](https://github.com/testcontainers/testcontainers-python/issues/408)) ([6c69583](https://github.com/testcontainers/testcontainers-python/commit/6c695835520bdcbf9824e8cefa00f7613d2a7cb9)) +* **compose:** allow running specific services in compose ([f61dcda](https://github.com/testcontainers/testcontainers-python/commit/f61dcda8bd7ea329cd3c836b6d6e2f0bd990335d)) +* **compose:** implement compose v2 with improved typing ([#426](https://github.com/testcontainers/testcontainers-python/issues/426)) ([5356caf](https://github.com/testcontainers/testcontainers-python/commit/5356caf2de056313a5b3f2805ed80e6a23b027a8)) +* **core:** add support for `tc.host` and de-prioritise `docker:dind` ([#388](https://github.com/testcontainers/testcontainers-python/issues/388)) ([2db8e6d](https://github.com/testcontainers/testcontainers-python/commit/2db8e6d123d42b57309408dd98ba9a06acc05c4b)) +* **redis:** support AsyncRedisContainer ([#442](https://github.com/testcontainers/testcontainers-python/issues/442)) ([cc4cb37](https://github.com/testcontainers/testcontainers-python/commit/cc4cb3762802dc75b0801727d8b1f1a1c56b7f50)) +* **release:** automate release via release-please ([#429](https://github.com/testcontainers/testcontainers-python/issues/429)) ([30f859e](https://github.com/testcontainers/testcontainers-python/commit/30f859eb1535acd6e93c331213426e1319ee9a47)) + + +### Bug Fixes + +* Added URLError to exceptions to wait for in elasticsearch ([0f9ad24](https://github.com/testcontainers/testcontainers-python/commit/0f9ad24f2c0df362ee15b81ce8d7d36b9f98e6e1)) +* **build:** add `pre-commit` as a dev dependency to simplify local dev and CI ([#438](https://github.com/testcontainers/testcontainers-python/issues/438)) ([1223583](https://github.com/testcontainers/testcontainers-python/commit/1223583d8fc3a1ab95441d82c7e1ece57f026fbf)) +* **build:** early exit strategy for modules ([#437](https://github.com/testcontainers/testcontainers-python/issues/437)) ([7358b49](https://github.com/testcontainers/testcontainers-python/commit/7358b4919c1010315a384a8f0fe2860e5a0ca6b4)) +* changed files breaks on main ([#422](https://github.com/testcontainers/testcontainers-python/issues/422)) ([3271357](https://github.com/testcontainers/testcontainers-python/commit/32713578dcf07f672a87818e00562b58874b4a52)) +* flaky garbage collection resulting in testing errors ([#423](https://github.com/testcontainers/testcontainers-python/issues/423)) ([b535ea2](https://github.com/testcontainers/testcontainers-python/commit/b535ea255bcaaa546f8cda7b2b17718c1cc7f3ca)) +* rabbitmq readiness probe ([#375](https://github.com/testcontainers/testcontainers-python/issues/375)) ([71cb75b](https://github.com/testcontainers/testcontainers-python/commit/71cb75b281df55ece4d5caf5d487059a7f38c34f)) +* **release:** prove that the release process updates the version ([#444](https://github.com/testcontainers/testcontainers-python/issues/444)) ([87b5873](https://github.com/testcontainers/testcontainers-python/commit/87b5873c1ec3a3e4e74742417d6068fa86cf1762)) +* test linting issue ([427c9b8](https://github.com/testcontainers/testcontainers-python/commit/427c9b841c2f6f516ec6cb74d5bd2839cb1939f4)) + + +### Documentation + +* Sphinx - Add title to each doc page ([#443](https://github.com/testcontainers/testcontainers-python/issues/443)) ([750e12a](https://github.com/testcontainers/testcontainers-python/commit/750e12a41172ce4aaf045c61dec33d318dc3c2f6)) diff --git a/pyproject.toml b/pyproject.toml index 7f2511feb..7afb4cd96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.0.0rc2" # auto-incremented by release-please +version = "4.0.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 701b23a7a0e4632db13e29c52141f9efc67467a1 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Sat, 9 Mar 2024 12:13:32 +0100 Subject: [PATCH 292/425] fix: failing tests for elasticsearch on machines with ARM CPU (#454) - updated the image versions used for tests with the latest supported tags: https://hub.docker.com/_/elasticsearch - fixes https://github.com/testcontainers/testcontainers-python/issues/452 ![Screenshot 2024-03-09 at 09 49 53](https://github.com/testcontainers/testcontainers-python/assets/13573675/84384190-3cc2-47c3-b795-a86efc062e3c) --- modules/elasticsearch/tests/test_elasticsearch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/elasticsearch/tests/test_elasticsearch.py b/modules/elasticsearch/tests/test_elasticsearch.py index e174ec47b..661a550c6 100644 --- a/modules/elasticsearch/tests/test_elasticsearch.py +++ b/modules/elasticsearch/tests/test_elasticsearch.py @@ -6,8 +6,8 @@ from testcontainers.elasticsearch import ElasticSearchContainer -# The versions below were the current supported versions at time of writing (2022-08-11) -@pytest.mark.parametrize("version", ["6.8.23", "7.17.5", "8.3.3"]) +# The versions below should reflect the latest stable releases +@pytest.mark.parametrize("version", ["7.17.18", "8.12.2"]) def test_docker_run_elasticsearch(version): with ElasticSearchContainer(f"elasticsearch:{version}", mem_limit="3G") as es: resp = urllib.request.urlopen(es.get_url()) From 902a5a3d5112317782db6a9a91d9fc4bfe5701af Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 9 Mar 2024 06:14:19 -0500 Subject: [PATCH 293/425] fix(clickhouse): clickhouse waiting (#428) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit im not actually sure if this fixes it but does: make clickhouse waiting look more like java-tc many other discussions about removing dependency on sqlalchemy + drivers --------- Co-authored-by: Bálint Bartha <39852431+totallyzen@users.noreply.github.com> --- .../testcontainers/clickhouse/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/clickhouse/testcontainers/clickhouse/__init__.py b/modules/clickhouse/testcontainers/clickhouse/__init__.py index 147940199..fbc8fab65 100644 --- a/modules/clickhouse/testcontainers/clickhouse/__init__.py +++ b/modules/clickhouse/testcontainers/clickhouse/__init__.py @@ -12,9 +12,8 @@ # under the License. import os from typing import Optional - -import clickhouse_driver -from clickhouse_driver.errors import Error +from urllib.error import HTTPError, URLError +from urllib.request import urlopen from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -48,7 +47,7 @@ def __init__( username: Optional[str] = None, password: Optional[str] = None, dbname: Optional[str] = None, - **kwargs + **kwargs, ) -> None: raise_for_deprecated_parameter(kwargs, "user", "username") super().__init__(image=image, **kwargs) @@ -57,11 +56,14 @@ def __init__( self.dbname = dbname or os.environ.get("CLICKHOUSE_DB", "test") self.port = port self.with_exposed_ports(self.port) + self.with_exposed_ports(8123) - @wait_container_is_ready(Error, EOFError) + @wait_container_is_ready(HTTPError, URLError) def _connect(self) -> None: - with clickhouse_driver.Client.from_url(self.get_connection_url()) as client: - client.execute("SELECT version()") + # noinspection HttpUrlsUsage + url = f"http://{self.get_container_host_ip()}:{self.get_exposed_port(8123)}" + with urlopen(url) as r: + assert b"Ok" in r.read() def _configure(self) -> None: self.with_env("CLICKHOUSE_USER", self.username) From cd90aa7310142059cb00f66bbc3693aedf5ddcb2 Mon Sep 17 00:00:00 2001 From: Shai Nagar Date: Sat, 9 Mar 2024 13:22:58 +0200 Subject: [PATCH 294/425] fix: unclosed socket warning in db containers (#378) This PR fixes #379 --- core/testcontainers/core/generic.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 21bf9d7e4..a3bff96e2 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -36,7 +36,10 @@ def _connect(self) -> None: import sqlalchemy engine = sqlalchemy.create_engine(self.get_connection_url()) - engine.connect() + try: + engine.connect() + finally: + engine.dispose() def get_connection_url(self) -> str: raise NotImplementedError From efb16832dc0be75014c7388f9b241ae0be36ddd4 Mon Sep 17 00:00:00 2001 From: Rodrigo Santa Cruz Ortega Date: Sat, 9 Mar 2024 09:07:48 -0500 Subject: [PATCH 295/425] fix: Close docker client when stopping the docker container (#380) Fixes the following warning ``` sys:1: ResourceWarning: unclosed ``` Related to #379 Co-authored-by: David Ankin --- core/testcontainers/core/container.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 6ecc384bd..b21feabc2 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -76,6 +76,7 @@ def start(self) -> "DockerContainer": def stop(self, force=True, delete_volume=True) -> None: self._container.remove(force=force, v=delete_volume) + self.get_docker_client().client.close() def __enter__(self) -> "DockerContainer": return self.start() From 5bef18a51360a2d74ba393f86b753abdf9ec5636 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 9 Mar 2024 15:21:50 +0100 Subject: [PATCH 296/425] fix: Update the copyright header for readthedocs (#341) The current one references 2017, and it is confusing since the website may be perceived as outdated one --------- Co-authored-by: Till Hoffmann Co-authored-by: David Ankin --- conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf.py b/conf.py index 8ac304a25..5887d3a70 100644 --- a/conf.py +++ b/conf.py @@ -52,7 +52,7 @@ # General information about the project. project = "testcontainers" -copyright = "2017, Sergey Pirogov" # noqa: A001 +copyright = "2017-2024, Sergey Pirogov and Testcontainers Python contributors" # noqa: A001 author = "Sergey Pirogov" # The version info for the project you're documenting, acts as replacement for From 2c4f171b001f0c45ff84199adf419c7a70ed81c5 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 9 Mar 2024 10:52:58 -0500 Subject: [PATCH 297/425] fix(mongodb): waiting for container to start (it was not waiting at all before?) (#461) we were using this code to test if it was online or not:`MongoClient(self.get_connection_url())`, but that doesn't actually perform any connection, instead you have to do something like: ```python @wait_container_is_ready() def _connect(self): client = self.get_connection_client() # will raise pymongo.errors.ServerSelectionTimeoutError if no connection is established client.admin.command('ismaster') ``` thanks to @smparekh for pointing this out, in his PR: https://github.com/testcontainers/testcontainers-python/pull/80/files#diff-cf09f76f44db0af04c58ddb456ccae39f7e29ce1d9208acd5f514c0a7dccb646R78 this PR implements the workaround described in the PR: ```python @pytest.fixture(scope="session") def test_client(): # init mongo mongo_container = MongoDbContainer("mongo:4").start() wait_for_logs(mongo_container, 'waiting for connections on port 27017') ... Co-authored-by: Shaishav Parekh --- modules/mongodb/testcontainers/mongodb/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/mongodb/testcontainers/mongodb/__init__.py b/modules/mongodb/testcontainers/mongodb/__init__.py index 1ff029258..eee623c6d 100644 --- a/modules/mongodb/testcontainers/mongodb/__init__.py +++ b/modules/mongodb/testcontainers/mongodb/__init__.py @@ -17,7 +17,7 @@ from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.waiting_utils import wait_for_logs class MongoDbContainer(DbContainer): @@ -81,9 +81,8 @@ def get_connection_url(self) -> str: port=self.port, ) - @wait_container_is_ready() - def _connect(self) -> MongoClient: - return MongoClient(self.get_connection_url()) + def _connect(self) -> None: + wait_for_logs(self, "Waiting for connections") def get_connection_client(self) -> MongoClient: - return self._connect() + return MongoClient(self.get_connection_url()) From f30eb1d4c98d3cc20582573b5def76d533a38b80 Mon Sep 17 00:00:00 2001 From: Jan Katins Date: Sun, 10 Mar 2024 23:30:14 +0100 Subject: [PATCH 298/425] feat(postgres): Remove SqlAlchemy dependency from postgres container (#445) Updates the pg testcontainer implementation to not use (and not install) SQLAlchemy nor psycopg2. Closes: #340 Closes: #336 Closes: #320 --------- Co-authored-by: Jason Turim --- INDEX.rst | 26 +++++++++-- README.md | 4 +- .../testcontainers/postgres/__init__.py | 45 +++++++++++++++---- modules/postgres/tests/test_postgres.py | 29 +++++++++++- poetry.lock | 12 ++--- pyproject.toml | 9 ++-- 6 files changed, 100 insertions(+), 25 deletions(-) diff --git a/INDEX.rst b/INDEX.rst index be5e3d1cd..9612bb86d 100644 --- a/INDEX.rst +++ b/INDEX.rst @@ -45,15 +45,33 @@ Getting Started >>> from testcontainers.postgres import PostgresContainer >>> import sqlalchemy - >>> with PostgresContainer("postgres:9.5") as postgres: - ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) + >>> with PostgresContainer("postgres:latest") as postgres: + ... psql_url = postgres.get_connection_url() + ... engine = sqlalchemy.create_engine(psql_url) ... with engine.begin() as connection: ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() >>> version - 'PostgreSQL 9.5...' + 'PostgreSQL ...' + +The snippet above will spin up the current latest version of a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url (using the :code:`psycopg2` driver per default) to connect to the database and retrieve the database version. + +.. doctest:: + + >>> import asyncpg + >>> from testcontainers.postgres import PostgresContainer + + >>> with PostgresContainer("postgres:16", driver=None) as postgres: + ... psql_url = container.get_connection_url() + ... with asyncpg.create_pool(dsn=psql_url,server_settings={"jit": "off"}) as pool: + ... conn = await pool.acquire() + ... ret = await conn.fetchval("SELECT 1") + ... assert ret == 1 + +This snippet does the same, however using a specific version and the driver is set to None, to influence the :code:`get_connection_url()` convenience method to not include a driver in the URL (e.g. for compatibility with :code:`psycopg` v3). + +Note, that the :code:`sqlalchemy` and :code:`psycopg2` packages are no longer a dependency of :code:`testcontainers[postgres]` and not needed to launch the Postgres container. Your project therefore needs to declare a dependency on the used driver and db access methods you use in your code. -The snippet above will spin up a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url we use to connect to the database and retrieve the database version. Installation ------------ diff --git a/README.md b/README.md index 58f5eca52..84f40b61c 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ For more information, see [the docs][readthedocs]. >>> from testcontainers.postgres import PostgresContainer >>> import sqlalchemy ->>> with PostgresContainer("postgres:9.5") as postgres: +>>> with PostgresContainer("postgres:16") as postgres: ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) ... with engine.begin() as connection: ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() >>> version -'PostgreSQL 9.5...' +'PostgreSQL 16...' ``` The snippet above will spin up a postgres database in a container. The `get_connection_url()` convenience method returns a `sqlalchemy` compatible url we use to connect to the database and retrieve the database version. diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index a61ad2cf8..83354e07e 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -11,16 +11,23 @@ # License for the specific language governing permissions and limitations # under the License. import os +from time import sleep from typing import Optional +from testcontainers.core.config import MAX_TRIES, SLEEP_TIME from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + +_UNSET = object() class PostgresContainer(DbContainer): """ Postgres database container. + To get a URL without a driver, pass in :code:`driver=None`. + Example: The example spins up a Postgres database and connects to it using the :code:`psycopg` @@ -31,7 +38,7 @@ class PostgresContainer(DbContainer): >>> from testcontainers.postgres import PostgresContainer >>> import sqlalchemy - >>> postgres_container = PostgresContainer("postgres:9.5") + >>> postgres_container = PostgresContainer("postgres:16") >>> with postgres_container as postgres: ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) ... with engine.begin() as connection: @@ -48,16 +55,16 @@ def __init__( username: Optional[str] = None, password: Optional[str] = None, dbname: Optional[str] = None, - driver: str = "psycopg2", + driver: Optional[str] = "psycopg2", **kwargs, ) -> None: raise_for_deprecated_parameter(kwargs, "user", "username") super().__init__(image=image, **kwargs) - self.username = username or os.environ.get("POSTGRES_USER", "test") - self.password = password or os.environ.get("POSTGRES_PASSWORD", "test") - self.dbname = dbname or os.environ.get("POSTGRES_DB", "test") + self.username: str = username or os.environ.get("POSTGRES_USER", "test") + self.password: str = password or os.environ.get("POSTGRES_PASSWORD", "test") + self.dbname: str = dbname or os.environ.get("POSTGRES_DB", "test") self.port = port - self.driver = driver + self.driver = f"+{driver}" if driver else "" self.with_exposed_ports(self.port) @@ -66,12 +73,34 @@ def _configure(self) -> None: self.with_env("POSTGRES_PASSWORD", self.password) self.with_env("POSTGRES_DB", self.dbname) - def get_connection_url(self, host=None) -> str: + def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] = _UNSET) -> str: + """Get a DB connection URL to connect to the PG DB. + + If a driver is set in the constructor (defaults to psycopg2!), the URL will contain the + driver. The optional driver argument to :code:`get_connection_url` overwrites the constructor + set value. Pass :code:`driver=None` to get URLs without a driver. + """ + driver_str = self.driver if driver is _UNSET else f"+{driver}" return super()._create_connection_url( - dialect=f"postgresql+{self.driver}", + dialect=f"postgresql{driver_str}", username=self.username, password=self.password, dbname=self.dbname, host=host, port=self.port, ) + + @wait_container_is_ready() + def _connect(self) -> None: + wait_for_logs(self, ".*database system is ready to accept connections.*", MAX_TRIES, SLEEP_TIME) + + count = 0 + while count < MAX_TRIES: + status, _ = self.exec(f"pg_isready -hlocalhost -p{self.port} -U{self.username}") + if status == 0: + return + + sleep(SLEEP_TIME) + count += 1 + + raise RuntimeError("Postgres could not get into a ready state") diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index c1963531c..f6d4447a0 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -1,9 +1,34 @@ -import sqlalchemy +import sys + +import pytest from testcontainers.postgres import PostgresContainer +import sqlalchemy + + +# https://www.postgresql.org/support/versioning/ +@pytest.mark.parametrize("version", ["12", "13", "14", "15", "16", "latest"]) +def test_docker_run_postgres(version: str, monkeypatch): + def fail(*args, **kwargs): + raise AssertionError("SQLA was called during PG container setup") + + monkeypatch.setattr(sqlalchemy, "create_engine", fail) + postgres_container = PostgresContainer(f"postgres:{version}") + with postgres_container as postgres: + status, msg = postgres.exec(f"pg_isready -hlocalhost -p{postgres.port} -U{postgres.username}") + + assert msg.decode("utf-8").endswith("accepting connections\n") + assert status == 0 + + status, msg = postgres.exec( + f"psql -hlocalhost -p{postgres.port} -U{postgres.username} -c 'select 2*3*5*7*11*13*17 as a;' " + ) + assert "510510" in msg.decode("utf-8") + assert "(1 row)" in msg.decode("utf-8") + assert status == 0 -def test_docker_run_postgres(): +def test_docker_run_postgres_with_sqlalchemy(): postgres_container = PostgresContainer("postgres:9.5") with postgres_container as postgres: engine = sqlalchemy.create_engine(postgres.get_connection_url()) diff --git a/poetry.lock b/poetry.lock index f97690266..c55fa8f9f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -920,7 +920,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, @@ -1747,7 +1747,7 @@ files = [ name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, @@ -2759,7 +2759,7 @@ test = ["pytest"] name = "sqlalchemy" version = "2.0.25" description = "Database Abstraction Library" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, @@ -3151,7 +3151,7 @@ neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] oracle = ["cx_Oracle", "sqlalchemy"] -postgres = ["psycopg2-binary", "sqlalchemy"] +postgres = [] rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] @@ -3159,4 +3159,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "f4cb027301e265217ccb581b0ddd06fe6d91319fbcfbc3d20504a1fdbc45d7b1" +content-hash = "9d1a3bebfdad61d5be71944fd7f5a49462cbcc74ae3e0a9cf89aff0c01b0bb8f" diff --git a/pyproject.toml b/pyproject.toml index 7afb4cd96..407a4b985 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ pymysql = { version = "*", extras = ["rsa"], optional = true } neo4j = { version = "*", optional = true } opensearch-py = { version = "*", optional = true } cx_Oracle = { version = "*", optional = true } -psycopg2-binary = { version = "*", optional = true } pika = { version = "*", optional = true } redis = { version = "*", optional = true } selenium = { version = "*", optional = true } @@ -102,7 +101,7 @@ neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] oracle = ["sqlalchemy", "cx_Oracle"] -postgres = ["sqlalchemy", "psycopg2-binary"] +postgres = [] rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] @@ -110,12 +109,16 @@ selenium = ["selenium"] [tool.poetry.group.dev.dependencies] mypy = "1.7.1" pre-commit = "^3.6" -pg8000 = "*" pytest = "7.4.3" pytest-cov = "4.1.0" sphinx = "^7.2.6" twine = "^4.0.2" anyio = "^4.3.0" +# for tests only +psycopg2-binary = "*" +pg8000 = "*" +sqlalchemy = "*" + [[tool.poetry.source]] name = "PyPI" From 6854b401ea27db2e1d6041fc9c37b387c4132f08 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 11 Mar 2024 21:32:48 +1100 Subject: [PATCH 299/425] chore: update metadata (#464) Update the package metadata to match the Python range. --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 407a4b985..14c62ccec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,11 +14,10 @@ classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: Developers", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "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", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", From 5dcda8865b52faa7ef4cabab25ad25732de27feb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 07:21:31 -0400 Subject: [PATCH 300/425] chore(main): release testcontainers 4.0.1 (#456) :robot: I have created a release *beep* *boop* --- ## [4.0.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.0.1) (2024-03-11) ### Features * **postgres:** Remove SqlAlchemy dependency from postgres container ([#445](https://github.com/testcontainers/testcontainers-python/issues/445)) ([f30eb1d](https://github.com/testcontainers/testcontainers-python/commit/f30eb1d4c98d3cc20582573b5def76d533a38b80)) ### Bug Fixes * **clickhouse:** clickhouse waiting ([#428](https://github.com/testcontainers/testcontainers-python/issues/428)) ([902a5a3](https://github.com/testcontainers/testcontainers-python/commit/902a5a3d5112317782db6a9a91d9fc4bfe5701af)) * Close docker client when stopping the docker container ([#380](https://github.com/testcontainers/testcontainers-python/issues/380)) ([efb1683](https://github.com/testcontainers/testcontainers-python/commit/efb16832dc0be75014c7388f9b241ae0be36ddd4)) * failing tests for elasticsearch on machines with ARM CPU ([#454](https://github.com/testcontainers/testcontainers-python/issues/454)) ([701b23a](https://github.com/testcontainers/testcontainers-python/commit/701b23a7a0e4632db13e29c52141f9efc67467a1)) * **mongodb:** waiting for container to start (it was not waiting at all before?) ([#461](https://github.com/testcontainers/testcontainers-python/issues/461)) ([2c4f171](https://github.com/testcontainers/testcontainers-python/commit/2c4f171b001f0c45ff84199adf419c7a70ed81c5)) * unclosed socket warning in db containers ([#378](https://github.com/testcontainers/testcontainers-python/issues/378)) ([cd90aa7](https://github.com/testcontainers/testcontainers-python/commit/cd90aa7310142059cb00f66bbc3693aedf5ddcb2)) * Update the copyright header for readthedocs ([#341](https://github.com/testcontainers/testcontainers-python/issues/341)) ([5bef18a](https://github.com/testcontainers/testcontainers-python/commit/5bef18a51360a2d74ba393f86b753abdf9ec5636)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: David Ankin --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 17 +++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 4d204362b..50af31c02 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.0" + ".": "4.0.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 033a9c686..4e26c96f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.1.0) (2024-03-11) + + +### Features + +* **postgres:** Remove SqlAlchemy dependency from postgres container ([#445](https://github.com/testcontainers/testcontainers-python/issues/445)) ([f30eb1d](https://github.com/testcontainers/testcontainers-python/commit/f30eb1d4c98d3cc20582573b5def76d533a38b80)) + + +### Bug Fixes + +* **clickhouse:** clickhouse waiting ([#428](https://github.com/testcontainers/testcontainers-python/issues/428)) ([902a5a3](https://github.com/testcontainers/testcontainers-python/commit/902a5a3d5112317782db6a9a91d9fc4bfe5701af)) +* Close docker client when stopping the docker container ([#380](https://github.com/testcontainers/testcontainers-python/issues/380)) ([efb1683](https://github.com/testcontainers/testcontainers-python/commit/efb16832dc0be75014c7388f9b241ae0be36ddd4)) +* failing tests for elasticsearch on machines with ARM CPU ([#454](https://github.com/testcontainers/testcontainers-python/issues/454)) ([701b23a](https://github.com/testcontainers/testcontainers-python/commit/701b23a7a0e4632db13e29c52141f9efc67467a1)) +* **mongodb:** waiting for container to start (it was not waiting at all before?) ([#461](https://github.com/testcontainers/testcontainers-python/issues/461)) ([2c4f171](https://github.com/testcontainers/testcontainers-python/commit/2c4f171b001f0c45ff84199adf419c7a70ed81c5)) +* unclosed socket warning in db containers ([#378](https://github.com/testcontainers/testcontainers-python/issues/378)) ([cd90aa7](https://github.com/testcontainers/testcontainers-python/commit/cd90aa7310142059cb00f66bbc3693aedf5ddcb2)) +* Update the copyright header for readthedocs ([#341](https://github.com/testcontainers/testcontainers-python/issues/341)) ([5bef18a](https://github.com/testcontainers/testcontainers-python/commit/5bef18a51360a2d74ba393f86b753abdf9ec5636)) + ## [4.0.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v3.7.1...testcontainers-v4.0.0) (2024-03-06) diff --git a/pyproject.toml b/pyproject.toml index 14c62ccec..8f18aad96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.0.0" # auto-incremented by release-please +version = "4.1.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 1ac8c24d58e93ead951342dcc36e6f8cee2b5fa7 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 11 Mar 2024 07:27:04 -0400 Subject: [PATCH 301/425] fix: go back to 4.0.1 (#465) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f18aad96..e1008e8da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.1.0" # auto-incremented by release-please +version = "4.0.1" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 825a1a55bfc720fd7b96e3367666cbc0b70b580c Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 11 Mar 2024 07:30:23 -0400 Subject: [PATCH 302/425] Fix 4.0.1 (#467) thank you release please :upside_down_face: --- .github/.release-please-manifest.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 50af31c02..4d204362b 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.1" + ".": "4.0.0" } diff --git a/pyproject.toml b/pyproject.toml index e1008e8da..14c62ccec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.0.1" # auto-incremented by release-please +version = "4.0.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From e72a0ebbc41a020a0a288af4edf176f92d733652 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 07:32:52 -0400 Subject: [PATCH 303/425] chore(main): release testcontainers 4.0.1 (#466) :robot: I have created a release *beep* *boop* --- ## [4.0.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.0.1) (2024-03-11) ### Features * **postgres:** Remove SqlAlchemy dependency from postgres container ([#445](https://github.com/testcontainers/testcontainers-python/issues/445)) ([f30eb1d](https://github.com/testcontainers/testcontainers-python/commit/f30eb1d4c98d3cc20582573b5def76d533a38b80)) ### Bug Fixes * **clickhouse:** clickhouse waiting ([#428](https://github.com/testcontainers/testcontainers-python/issues/428)) ([902a5a3](https://github.com/testcontainers/testcontainers-python/commit/902a5a3d5112317782db6a9a91d9fc4bfe5701af)) * Close docker client when stopping the docker container ([#380](https://github.com/testcontainers/testcontainers-python/issues/380)) ([efb1683](https://github.com/testcontainers/testcontainers-python/commit/efb16832dc0be75014c7388f9b241ae0be36ddd4)) * failing tests for elasticsearch on machines with ARM CPU ([#454](https://github.com/testcontainers/testcontainers-python/issues/454)) ([701b23a](https://github.com/testcontainers/testcontainers-python/commit/701b23a7a0e4632db13e29c52141f9efc67467a1)) * go back to 4.0.1 ([#465](https://github.com/testcontainers/testcontainers-python/issues/465)) ([1ac8c24](https://github.com/testcontainers/testcontainers-python/commit/1ac8c24d58e93ead951342dcc36e6f8cee2b5fa7)) * **mongodb:** waiting for container to start (it was not waiting at all before?) ([#461](https://github.com/testcontainers/testcontainers-python/issues/461)) ([2c4f171](https://github.com/testcontainers/testcontainers-python/commit/2c4f171b001f0c45ff84199adf419c7a70ed81c5)) * unclosed socket warning in db containers ([#378](https://github.com/testcontainers/testcontainers-python/issues/378)) ([cd90aa7](https://github.com/testcontainers/testcontainers-python/commit/cd90aa7310142059cb00f66bbc3693aedf5ddcb2)) * Update the copyright header for readthedocs ([#341](https://github.com/testcontainers/testcontainers-python/issues/341)) ([5bef18a](https://github.com/testcontainers/testcontainers-python/commit/5bef18a51360a2d74ba393f86b753abdf9ec5636)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: David Ankin --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 18 ++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 4d204362b..50af31c02 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.0" + ".": "4.0.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e26c96f6..de170b9d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,24 @@ * **postgres:** Remove SqlAlchemy dependency from postgres container ([#445](https://github.com/testcontainers/testcontainers-python/issues/445)) ([f30eb1d](https://github.com/testcontainers/testcontainers-python/commit/f30eb1d4c98d3cc20582573b5def76d533a38b80)) +### Bug Fixes + +* **clickhouse:** clickhouse waiting ([#428](https://github.com/testcontainers/testcontainers-python/issues/428)) ([902a5a3](https://github.com/testcontainers/testcontainers-python/commit/902a5a3d5112317782db6a9a91d9fc4bfe5701af)) +* Close docker client when stopping the docker container ([#380](https://github.com/testcontainers/testcontainers-python/issues/380)) ([efb1683](https://github.com/testcontainers/testcontainers-python/commit/efb16832dc0be75014c7388f9b241ae0be36ddd4)) +* failing tests for elasticsearch on machines with ARM CPU ([#454](https://github.com/testcontainers/testcontainers-python/issues/454)) ([701b23a](https://github.com/testcontainers/testcontainers-python/commit/701b23a7a0e4632db13e29c52141f9efc67467a1)) +* go back to 4.0.1 ([#465](https://github.com/testcontainers/testcontainers-python/issues/465)) ([1ac8c24](https://github.com/testcontainers/testcontainers-python/commit/1ac8c24d58e93ead951342dcc36e6f8cee2b5fa7)) +* **mongodb:** waiting for container to start (it was not waiting at all before?) ([#461](https://github.com/testcontainers/testcontainers-python/issues/461)) ([2c4f171](https://github.com/testcontainers/testcontainers-python/commit/2c4f171b001f0c45ff84199adf419c7a70ed81c5)) +* unclosed socket warning in db containers ([#378](https://github.com/testcontainers/testcontainers-python/issues/378)) ([cd90aa7](https://github.com/testcontainers/testcontainers-python/commit/cd90aa7310142059cb00f66bbc3693aedf5ddcb2)) +* Update the copyright header for readthedocs ([#341](https://github.com/testcontainers/testcontainers-python/issues/341)) ([5bef18a](https://github.com/testcontainers/testcontainers-python/commit/5bef18a51360a2d74ba393f86b753abdf9ec5636)) + +## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.1.0) (2024-03-11) + + +### Features + +* **postgres:** Remove SqlAlchemy dependency from postgres container ([#445](https://github.com/testcontainers/testcontainers-python/issues/445)) ([f30eb1d](https://github.com/testcontainers/testcontainers-python/commit/f30eb1d4c98d3cc20582573b5def76d533a38b80)) + + ### Bug Fixes * **clickhouse:** clickhouse waiting ([#428](https://github.com/testcontainers/testcontainers-python/issues/428)) ([902a5a3](https://github.com/testcontainers/testcontainers-python/commit/902a5a3d5112317782db6a9a91d9fc4bfe5701af)) diff --git a/pyproject.toml b/pyproject.toml index 14c62ccec..e1008e8da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.0.0" # auto-incremented by release-please +version = "4.0.1" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From dcb4f6842cbfe6e880a77b0d4aabb3f396c6dc19 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 11 Mar 2024 08:02:14 -0400 Subject: [PATCH 304/425] fix: changelog after release-please (#469) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de170b9d3..e21d0ca56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.1.0) (2024-03-11) +## [4.0.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.0.1) (2024-03-11) ### Features @@ -18,7 +18,7 @@ * unclosed socket warning in db containers ([#378](https://github.com/testcontainers/testcontainers-python/issues/378)) ([cd90aa7](https://github.com/testcontainers/testcontainers-python/commit/cd90aa7310142059cb00f66bbc3693aedf5ddcb2)) * Update the copyright header for readthedocs ([#341](https://github.com/testcontainers/testcontainers-python/issues/341)) ([5bef18a](https://github.com/testcontainers/testcontainers-python/commit/5bef18a51360a2d74ba393f86b753abdf9ec5636)) -## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.1.0) (2024-03-11) +## [4.0.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.0.1) (2024-03-11) ### Features From ca65a916b719168c57c174d2af77d45fd026ec05 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 11 Mar 2024 08:11:36 -0400 Subject: [PATCH 305/425] fix: try to fix release-please by setting a bootstrap sha (#472) --- .github/release-please-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 1bd96f93c..14c2f8a26 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -1,6 +1,6 @@ { "release-type": "python", - "bootstrap-sha": "28e3a471c32c1036dd5e37df13cdde3b1ba91000", + "bootstrap-sha": "dcb4f6842cbfe6e880a77b0d4aabb3f396c6dc19", "packages": { ".": { "package-name": "testcontainers" From ade144ee2888d4044ac0c1dc627f47da92789e06 Mon Sep 17 00:00:00 2001 From: Kevin Wittek Date: Thu, 14 Mar 2024 16:19:33 +0100 Subject: [PATCH 306/425] fix(configuration): strip whitespaces when reading .testcontainers.properties (#474) ## What does this PR do? Strip whitespaces from key and value after reading `~/.testcontainers.properties`. The way TCD writes this file, the `=` will be surrounded by whitespaces, e.g.: ``` kiview@kay ~ % cat .testcontainers.properties tc.host = tcp://127.0.0.1:59499 testcontainers.reuse.enable = true ``` ## Why is it important? Adds Plug&Play support for Testcontainers Desktop and Testcontainers Cloud. ## How to test this PR Run any kind of tests that involve creating and starting containers, while Testcontainers Desktop is running. Testcontainers Desktop should be used and hence started containers can be seen in the Testcontainers Cloud dashboard. --- core/testcontainers/core/docker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 05d5377a3..af00576eb 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -143,7 +143,7 @@ def read_tc_properties() -> dict[str, str]: tuples = [] with open(file) as contents: tuples = [line.split("=") for line in contents.readlines() if "=" in line] - settings = {**settings, **{item[0]: item[1] for item in tuples}} + settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} return settings From 08a629324244347c26ab5b09f335d303b5aaa270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:15:35 +0100 Subject: [PATCH 307/425] chore(release): completely remove bootstrap-sha as we're bootstrapped (#481) # change remove the bootstrap sha from `release-please` since we're now bootstrapped - this will enable the tool to check for the latest release PR instead of looking at the SHA --- .github/release-please-config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 14c2f8a26..e6fe017d5 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -1,6 +1,5 @@ { "release-type": "python", - "bootstrap-sha": "dcb4f6842cbfe6e880a77b0d4aabb3f396c6dc19", "packages": { ".": { "package-name": "testcontainers" From d0198744c3bdc97a7fe41879b54acb2f5ee7becb Mon Sep 17 00:00:00 2001 From: Vemund Santi Date: Tue, 19 Mar 2024 16:28:06 +0100 Subject: [PATCH 308/425] feat(reliability): integrate the ryuk container for better container cleanup (#314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [!NOTE] > Editor's note from @totallyzen: > After a thorough discussion between @santi @alexanderankin, @kiview and @totallyzen on the [slack](https://testcontainers.slack.com/archives/C04SRG5AXNU/p1710156743640249) we've decided this isn't a breaking change in api, but a modification in behaviour. Therefore not worth a 5.0 but we'll release it under 4.x > If this did end up breaking your workflow, come talk to us about your use-case! **What are you trying to do?** Use Ryuk as the default resource cleanup strategy. **Why should it be done this way?** The current implementation of tc-python does not handle container lifecycle management the same way as other TC implementations. In this PR I introduce Ryuk to be the default resource cleanup strategy, in order to be better aligned with other TC implementations. Ryuk is enabled by default, but can be disabled with the `TESTCONTAINERS_RYUK_DISABLED` env variable (which follows the behavior of other TC implementations). Ryuk behavior can further be altered with the `TESTCONTAINERS_RYUK_PRIVILEGED`, `RYUK_CONTAINER_IMAGE` and `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` (also following the same conventions from other implementations). Documentation of these env variables is added to the README in the root of the repo. The implementation uses a singleton container that starts an instance of Ryuk and stores it on class / module level, so that it is reused within a process. This follows the same pattern as in other languages, and Ryuk behaviour itself is implemented the exact same way as other implementations. **BREAKING CHANGE** `__del__` support is now removed, in order to better align with other TC implementations. From the comments in the `__del__` implementation, it seems like this was added as a final attempt to cleanup resources on exit/garbage collection. This leaves three ways to cleanup resources, which has better defined behaviors and follows modern patterns for resource management in Python: Method 1: Context Manager Init/cleanup through a context manager (__enter__/__exit__): ``` with DockerContainer("postgres") as container: # some logic here # end of context: container is killed by `__exit__` method ``` Method 2: Manual start() and stop() ``` container = DockerContainer("postgres").start() # some logic here container.stop() ``` Method 3: Ryuk ``` container = DockerContainer("postgres").start() # some logic here # You forget to .stop() the container, but Ryuk kills it for you 10 seconds after your process exits. ``` _Why remove `__del__`?_ According to the previous maintainer of the repo, it has been causing “[a bunch of issues](https://github.com/testcontainers/testcontainers-python/pull/314#discussion_r1185321083)”, which I have personally experienced while using TC in a Django app, due to the automatic GC behavior when no longer referencing the container with a variable. E.g. if you instantiate the container in a method, only returning the connection string, the Python garbage collector will automatically call `__del__` on the instance at the end of the function, thus killing your container. This leads to clunky workarounds like having to store a reference to the container in a module-level variable, or always having to return a reference to the container from the function creating the container. In addition, the gc behaviour is not consistent across Python implementations, making the reliance on `__del__` flaky at best. Also, having the __del__ method cleanup your container prevents us from implementing `with_reuse()` (which is implemented in other TC implementations) in the future, as a process exit would always instantly kill the container, preventing us to use it in another process before Ryuk reaps it. **Next steps** Once this PR is accepted, my plan is to implement the `with_reuse()` functionality seen in other implementations, to enable faster / instant usage of existing containers. This is very useful in simple testing scenarios or local development workflows using hot reload behaviour. The `with_reuse()` API requires the removal of `__del__` cleanup, as otherwise the container would not be available for reuse due to the GC reaping the container as soon as the process exits. **Other changes** - Adds “x-tc-sid=SESSION_ID” header to the underlying Docker API client with the value of the current session ID (created on module init), in order to enable Testcontainers Cloud to operate in “Turbo mode” https://github.com/testcontainers/testcontainers-python/pull/314#issuecomment-1455630630 - Adds labels `org.testcontainers.lang=python` and `org.testcontainers.session-id=SESSION_ID`to the containers created by TC - As mentioned above, the env variables TESTCONTAINERS_RYUK_DISABLED, TESTCONTAINERS_RYUK_PRIVILEGED, RYUK_CONTAINER_IMAGE and TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE are now used for customizing tc-python behavior. --------- Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Co-authored-by: Balint Bartha <39852431+totallyzen@users.noreply.github.com> --- INDEX.rst | 15 ++++ README.md | 9 +++ core/testcontainers/core/config.py | 5 ++ core/testcontainers/core/container.py | 91 ++++++++++++++++++----- core/testcontainers/core/docker_client.py | 19 ++--- core/testcontainers/core/labels.py | 20 +++++ core/tests/test_ryuk.py | 24 ++++++ pyproject.toml | 3 + 8 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 core/testcontainers/core/labels.py create mode 100644 core/tests/test_ryuk.py diff --git a/INDEX.rst b/INDEX.rst index 9612bb86d..0607b5e3c 100644 --- a/INDEX.rst +++ b/INDEX.rst @@ -92,6 +92,21 @@ When trying to launch a testcontainer from within a Docker container, e.g., in c 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. +Configuration +------------- + ++-------------------------------------------+-------------------------------+------------------------------------------+ +| Env Variable | Example | Description | ++===========================================+===============================+==========================================+ +| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | ++-------------------------------------------+-------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | ++-------------------------------------------+-------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | ++-------------------------------------------+-------------------------------+------------------------------------------+ +| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.5.1`` | Custom image for ryuk | ++-------------------------------------------+-------------------------------+------------------------------------------+ + Development and Contributing ---------------------------- diff --git a/README.md b/README.md index 84f40b61c..7f4699143 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,12 @@ For more information, see [the docs][readthedocs]. ``` The snippet above will spin up a postgres database in a container. The `get_connection_url()` convenience method returns a `sqlalchemy` compatible url we use to connect to the database and retrieve the database version. + +## Configuration + +| Env Variable | Example | Description | +| ----------------------------------------- | ----------------------------- | ---------------------------------------- | +| `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk | +| `TESTCONTAINERS_RYUK_PRIVILEGED` | `false` | Run ryuk as a privileged container | +| `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk | +| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.5.1` | Custom image for ryuk | diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index e7673f755..1bf9ad4dc 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -3,3 +3,8 @@ MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120)) SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) TIMEOUT = MAX_TRIES * SLEEP_TIME + +RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.5.1") +RYUK_PRIVILEGED: bool = environ.get("TESTCONTAINERS_RYUK_PRIVILEGED", "false") == "true" +RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true" +RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock") diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index b21feabc2..f0da90bb4 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,13 +1,16 @@ -import contextlib from platform import system -from typing import Optional - -from docker.models.containers import Container +from socket import socket +from typing import TYPE_CHECKING, Optional +from testcontainers.core.config import RYUK_DISABLED, RYUK_DOCKER_SOCKET, RYUK_IMAGE, RYUK_PRIVILEGED from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID from testcontainers.core.utils import inside_container, is_arm, setup_logger -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + +if TYPE_CHECKING: + from docker.models.containers import Container logger = setup_logger(__name__) @@ -25,7 +28,12 @@ class DockerContainer: ... delay = wait_for_logs(container, "Hello from Docker!") """ - def __init__(self, image: str, docker_client_kw: Optional[dict] = None, **kwargs) -> None: + def __init__( + self, + image: str, + docker_client_kw: Optional[dict] = None, + **kwargs, + ) -> None: self.env = {} self.ports = {} self.volumes = {} @@ -58,7 +66,10 @@ def maybe_emulate_amd64(self) -> "DockerContainer": return self.with_kwargs(platform="linux/amd64") return self - def start(self) -> "DockerContainer": + def start(self): + if not RYUK_DISABLED and self.image != RYUK_IMAGE: + logger.debug("Creating Ryuk container") + Reaper.get_instance() logger.info("Pulling image %s", self.image) docker_client = self.get_docker_client() self._container = docker_client.run( @@ -69,7 +80,7 @@ def start(self) -> "DockerContainer": ports=self.ports, name=self._name, volumes=self.volumes, - **self._kwargs + **self._kwargs, ) logger.info("Container started: %s", self._container.short_id) return self @@ -78,21 +89,12 @@ def stop(self, force=True, delete_volume=True) -> None: self._container.remove(force=force, v=delete_volume) self.get_docker_client().client.close() - def __enter__(self) -> "DockerContainer": + def __enter__(self): return self.start() def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.stop() - def __del__(self) -> None: - """ - __del__ runs when Python attempts to garbage collect the object. - In case of leaky test design, we still attempt to clean up the container. - """ - with contextlib.suppress(Exception): - if self._container is not None: - self.stop() - def get_container_host_ip(self) -> str: # infer from docker host host = self.get_docker_client().host() @@ -140,7 +142,7 @@ def with_volume_mapping(self, host: str, container: str, mode: str = "ro") -> "D self.volumes[host] = mapping return self - def get_wrapped_container(self) -> Container: + def get_wrapped_container(self) -> "Container": return self._container def get_docker_client(self) -> DockerClient: @@ -155,3 +157,54 @@ def exec(self, command) -> tuple[int, str]: if not self._container: raise ContainerStartException("Container should be started before executing a command") return self._container.exec_run(command) + + +class Reaper: + _instance: "Optional[Reaper]" = None + _container: Optional[DockerContainer] = None + _socket: Optional[socket] = None + + @classmethod + def get_instance(cls) -> "Reaper": + if not Reaper._instance: + Reaper._instance = Reaper._create_instance() + + return Reaper._instance + + @classmethod + def delete_instance(cls) -> None: + if Reaper._socket is not None: + Reaper._socket.close() + Reaper._socket = None + + if Reaper._container is not None: + Reaper._container.stop() + Reaper._container = None + + if Reaper._instance is not None: + Reaper._instance = None + + @classmethod + def _create_instance(cls) -> "Reaper": + logger.debug(f"Creating new Reaper for session: {SESSION_ID}") + + Reaper._container = ( + DockerContainer(RYUK_IMAGE) + .with_name(f"testcontainers-ryuk-{SESSION_ID}") + .with_exposed_ports(8080) + .with_volume_mapping(RYUK_DOCKER_SOCKET, "/var/run/docker.sock", "rw") + .with_kwargs(privileged=RYUK_PRIVILEGED) + .start() + ) + wait_for_logs(Reaper._container, r".* Started!") + + container_host = Reaper._container.get_container_host_ip() + container_port = int(Reaper._container.get_exposed_port(8080)) + + Reaper._socket = socket() + Reaper._socket.connect((container_host, container_port)) + Reaper._socket.send(f"label={LABEL_SESSION_ID}={SESSION_ID}\r\n".encode()) + + Reaper._instance = Reaper() + + return Reaper._instance diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index af00576eb..9c1ea485e 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -10,7 +10,6 @@ # 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 atexit import functools as ft import os import urllib @@ -19,10 +18,10 @@ from typing import Optional, Union import docker -from docker.errors import NotFound from docker.models.containers import Container, ContainerCollection -from .utils import default_gateway_ip, inside_container, setup_logger +from testcontainers.core.labels import SESSION_ID, create_labels +from testcontainers.core.utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) TC_FILE = ".testcontainers.properties" @@ -42,6 +41,7 @@ def __init__(self, **kwargs) -> None: self.client = docker.DockerClient(base_url=docker_host) else: self.client = docker.from_env(**kwargs) + self.client.api.headers["x-tc-sid"] = SESSION_ID @ft.wraps(ContainerCollection.run) def run( @@ -50,6 +50,7 @@ def run( command: Optional[Union[str, list[str]]] = None, environment: Optional[dict] = None, ports: Optional[dict] = None, + labels: Optional[dict[str, str]] = None, detach: bool = False, stdout: bool = True, stderr: bool = False, @@ -65,10 +66,9 @@ def run( detach=detach, environment=environment, ports=ports, + labels=create_labels(image, labels), **kwargs, ) - if detach: - atexit.register(_stop_container, container) return container def port(self, container_id: str, port: int) -> int: @@ -145,12 +145,3 @@ def read_tc_properties() -> dict[str, str]: tuples = [line.split("=") for line in contents.readlines() if "=" in line] settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} return settings - - -def _stop_container(container: Container) -> None: - try: - container.stop() - except NotFound: - pass - except Exception as ex: - LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex) diff --git a/core/testcontainers/core/labels.py b/core/testcontainers/core/labels.py new file mode 100644 index 000000000..13937a5e8 --- /dev/null +++ b/core/testcontainers/core/labels.py @@ -0,0 +1,20 @@ +from typing import Optional +from uuid import uuid4 + +from testcontainers.core.config import RYUK_IMAGE + +SESSION_ID: str = str(uuid4()) +LABEL_SESSION_ID = "org.testcontainers.session-id" +LABEL_LANG = "org.testcontainers.lang" + + +def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str]: + if labels is None: + labels = {} + labels[LABEL_LANG] = "python" + + if image == RYUK_IMAGE: + return labels + + labels[LABEL_SESSION_ID] = SESSION_ID + return labels diff --git a/core/tests/test_ryuk.py b/core/tests/test_ryuk.py new file mode 100644 index 000000000..32370ffbc --- /dev/null +++ b/core/tests/test_ryuk.py @@ -0,0 +1,24 @@ +from testcontainers.core import container +from testcontainers.core.container import Reaper +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +def test_wait_for_reaper(): + container = DockerContainer("hello-world").start() + wait_for_logs(container, "Hello from Docker!") + + assert Reaper._socket is not None + Reaper._socket.close() + + assert Reaper._container is not None + wait_for_logs(Reaper._container, r".* Removed \d .*", timeout=30) + + Reaper.delete_instance() + + +def test_container_without_ryuk(monkeypatch): + monkeypatch.setattr(container, "RYUK_DISABLED", True) + with DockerContainer("hello-world") as cont: + wait_for_logs(cont, "Hello from Docker!") + assert Reaper._instance is None diff --git a/pyproject.toml b/pyproject.toml index e1008e8da..061cf6c8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -197,6 +197,9 @@ ignore = [ "INP001" ] +[tool.ruff.lint.pyupgrade] +keep-runtime-typing = true + [tool.ruff.lint.flake8-type-checking] strict = true From c07e99c4edb40cc3faa5c879e14ec526cd13bd46 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:41:41 +0100 Subject: [PATCH 309/425] chore(main): release testcontainers 4.1.0 (#470) :robot: I have created a release *beep* *boop* --- ## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.1...testcontainers-v4.1.0) (2024-03-19) ### Features * **reliability:** integrate the ryuk container for better container cleanup ([#314](https://github.com/testcontainers/testcontainers-python/issues/314)) ([d019874](https://github.com/testcontainers/testcontainers-python/commit/d0198744c3bdc97a7fe41879b54acb2f5ee7becb)) ### Bug Fixes * changelog after release-please ([#469](https://github.com/testcontainers/testcontainers-python/issues/469)) ([dcb4f68](https://github.com/testcontainers/testcontainers-python/commit/dcb4f6842cbfe6e880a77b0d4aabb3f396c6dc19)) * **configuration:** strip whitespaces when reading .testcontainers.properties ([#474](https://github.com/testcontainers/testcontainers-python/issues/474)) ([ade144e](https://github.com/testcontainers/testcontainers-python/commit/ade144ee2888d4044ac0c1dc627f47da92789e06)) * try to fix release-please by setting a bootstrap sha ([#472](https://github.com/testcontainers/testcontainers-python/issues/472)) ([ca65a91](https://github.com/testcontainers/testcontainers-python/commit/ca65a916b719168c57c174d2af77d45fd026ec05)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 50af31c02..12ef0bfc3 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.1" + ".": "4.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e21d0ca56..b246cb1c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.1...testcontainers-v4.1.0) (2024-03-19) + + +### Features + +* **reliability:** integrate the ryuk container for better container cleanup ([#314](https://github.com/testcontainers/testcontainers-python/issues/314)) ([d019874](https://github.com/testcontainers/testcontainers-python/commit/d0198744c3bdc97a7fe41879b54acb2f5ee7becb)) + + +### Bug Fixes + +* changelog after release-please ([#469](https://github.com/testcontainers/testcontainers-python/issues/469)) ([dcb4f68](https://github.com/testcontainers/testcontainers-python/commit/dcb4f6842cbfe6e880a77b0d4aabb3f396c6dc19)) +* **configuration:** strip whitespaces when reading .testcontainers.properties ([#474](https://github.com/testcontainers/testcontainers-python/issues/474)) ([ade144e](https://github.com/testcontainers/testcontainers-python/commit/ade144ee2888d4044ac0c1dc627f47da92789e06)) +* try to fix release-please by setting a bootstrap sha ([#472](https://github.com/testcontainers/testcontainers-python/issues/472)) ([ca65a91](https://github.com/testcontainers/testcontainers-python/commit/ca65a916b719168c57c174d2af77d45fd026ec05)) + ## [4.0.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.0...testcontainers-v4.0.1) (2024-03-11) diff --git a/pyproject.toml b/pyproject.toml index 061cf6c8f..4d310465e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.0.1" # auto-incremented by release-please +version = "4.1.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 7b58a50f3a8703c5d5e974a4ff20bc8e52ae93c8 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Wed, 20 Mar 2024 03:53:18 +0100 Subject: [PATCH 310/425] fix(arangodb): tests to pass on ARM CPUs - change default image to 3.11.x where ARM image is published (#479) - updated arangodb image version which now supports ARM CPUs - skipping test for legacy version on ARM CPU fixes https://github.com/testcontainers/testcontainers-python/issues/449 ![Screenshot 2024-03-15 at 11 43 35](https://github.com/testcontainers/testcontainers-python/assets/13573675/9fe5edfc-9a83-44d2-beb5-9c917d41438c) --- .../testcontainers/arangodb/__init__.py | 2 +- modules/arangodb/tests/test_arangodb.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/arangodb/testcontainers/arangodb/__init__.py b/modules/arangodb/testcontainers/arangodb/__init__.py index 4977e79ef..a7c954652 100644 --- a/modules/arangodb/testcontainers/arangodb/__init__.py +++ b/modules/arangodb/testcontainers/arangodb/__init__.py @@ -26,7 +26,7 @@ class ArangoDbContainer(DbContainer): >>> from testcontainers.arangodb import ArangoDbContainer >>> from arango import ArangoClient - >>> with ArangoDbContainer("arangodb:3.9.1") as arango: + >>> with ArangoDbContainer("arangodb:3.11.8") as arango: ... client = ArangoClient(hosts=arango.get_connection_url()) ... ... # Connect diff --git a/modules/arangodb/tests/test_arangodb.py b/modules/arangodb/tests/test_arangodb.py index 6c06b4ca3..f526bf383 100644 --- a/modules/arangodb/tests/test_arangodb.py +++ b/modules/arangodb/tests/test_arangodb.py @@ -7,8 +7,10 @@ from arango.exceptions import DatabaseCreateError, ServerVersionError from testcontainers.arangodb import ArangoDbContainer +import platform ARANGODB_IMAGE_NAME = "arangodb" +IMAGE_VERSION = "3.11.8" def arango_test_ops(arango_client, expeced_version, username="root", password=""): @@ -50,8 +52,7 @@ def test_docker_run_arango(): """ Test ArangoDB container with default settings. """ - image_version = "3.9.1" - image = f"{ARANGODB_IMAGE_NAME}:{image_version}" + image = f"{ARANGODB_IMAGE_NAME}:{IMAGE_VERSION}" arango_root_password = "passwd" with ArangoDbContainer(image) as arango: @@ -62,22 +63,22 @@ def test_docker_run_arango(): with pytest.raises(DatabaseCreateError): sys_db.create_database("test") - arango_test_ops(arango_client=client, expeced_version=image_version, password=arango_root_password) + arango_test_ops(arango_client=client, expeced_version=IMAGE_VERSION, password=arango_root_password) def test_docker_run_arango_without_auth(): """ Test ArangoDB container with ARANGO_NO_AUTH var set. """ - image_version = "3.9.1" - image = f"{ARANGODB_IMAGE_NAME}:{image_version}" + image = f"{ARANGODB_IMAGE_NAME}:{IMAGE_VERSION}" with ArangoDbContainer(image, arango_no_auth=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) - arango_test_ops(arango_client=client, expeced_version=image_version, password="") + arango_test_ops(arango_client=client, expeced_version=IMAGE_VERSION, password="") +@pytest.mark.skipif(platform.processor() == "arm", reason="Test does not run on machines with ARM CPU") def test_docker_run_arango_older_version(): """ Test ArangoDB container with older tag/version. @@ -100,8 +101,7 @@ def test_docker_run_arango_random_root_password(): """ Test ArangoDB container with ARANGO_RANDOM_ROOT_PASSWORD var set. """ - image_version = "3.9.1" - image = f"{ARANGODB_IMAGE_NAME}:{image_version}" + image = f"{ARANGODB_IMAGE_NAME}:{IMAGE_VERSION}" arango_root_password = "passwd" with ArangoDbContainer(image, arango_random_root_password=True) as arango: @@ -110,4 +110,4 @@ def test_docker_run_arango_random_root_password(): # Test invalid auth (we don't know the password in random mode) sys_db = client.db("_system", username="root", password=arango_root_password) with pytest.raises(ServerVersionError): - assert sys_db.version() == image_version + assert sys_db.version() == IMAGE_VERSION From 13742a5dc448c80914953c21f8f2b01177c3fa6c Mon Sep 17 00:00:00 2001 From: Luc Sorel-Giffo Date: Wed, 20 Mar 2024 04:04:03 +0100 Subject: [PATCH 311/425] feat: support influxdb (#413) Thanks to the team for this great testing utility :pray: This PR brings the possibility to spawn a Docker instance of InfluxDB (timeseries-oriented database) for integration testing. This PR supports both 1.x and 2.x versions of InfluxDB, which rely on different query languages and require the use of different Python client libraries. Therefore: - there is a root `InfluxDbContainer` class in `testcontainers/influxdb.py` for the common mechanisms (health check, connection url, etc.) - there are 2 separate classes, one for each InfluxDB major version: `InfluxDb1Container` class in `testcontainers/influxdb1/__init__.py`, `InfluxDb2Container` in `testcontainers/influxdb2/__init__.py` that are meant to be used by the testcontainers users. Each one has its own `.get_client()` method involving the dedicated Python client library Unit and integration tests (meant to be examples for the testcontainers end-users) are provided in the PR. --- INDEX.rst | 1 + modules/influxdb/README.rst | 2 + modules/influxdb/testcontainers/influxdb.py | 99 ++ .../testcontainers/influxdb1/__init__.py | 65 + .../testcontainers/influxdb2/__init__.py | 106 ++ modules/influxdb/tests/__init__.py | 0 modules/influxdb/tests/test_influxdb.py | 138 ++ poetry.lock | 1303 +++++++++-------- pyproject.toml | 4 + 9 files changed, 1118 insertions(+), 600 deletions(-) create mode 100644 modules/influxdb/README.rst create mode 100644 modules/influxdb/testcontainers/influxdb.py create mode 100644 modules/influxdb/testcontainers/influxdb1/__init__.py create mode 100644 modules/influxdb/testcontainers/influxdb2/__init__.py create mode 100644 modules/influxdb/tests/__init__.py create mode 100644 modules/influxdb/tests/test_influxdb.py diff --git a/INDEX.rst b/INDEX.rst index 0607b5e3c..4e0cd54b9 100644 --- a/INDEX.rst +++ b/INDEX.rst @@ -20,6 +20,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/clickhouse/README modules/elasticsearch/README modules/google/README + modules/influxdb/README modules/kafka/README modules/keycloak/README modules/localstack/README diff --git a/modules/influxdb/README.rst b/modules/influxdb/README.rst new file mode 100644 index 000000000..8424ebea3 --- /dev/null +++ b/modules/influxdb/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.influxdb.InfluxDbContainer +.. title:: testcontainers.influxdb.InfluxDbContainer diff --git a/modules/influxdb/testcontainers/influxdb.py b/modules/influxdb/testcontainers/influxdb.py new file mode 100644 index 000000000..4b9d9b905 --- /dev/null +++ b/modules/influxdb/testcontainers/influxdb.py @@ -0,0 +1,99 @@ +# +# 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. + +""" +testcontainers/influxdb provides means to spawn an InfluxDB instance within a Docker container. + +- this influxdb.py module provides the common mechanism to spawn an InfluxDB container. + You are not likely to use this module directly. +- import the InfluxDb1Container class from the influxdb1/__init__.py module to spawn + a container for an InfluxDB 1.x instance +- import the InfluxDb2Container class from the influxdb2/__init__.py module to spawn + a container for an InfluxDB 2.x instance + +The 2 containers are separated in different modules for 2 reasons: +- because the Docker images are not designed to be used in the same way +- because the InfluxDB clients are different for 1.x and 2.x versions, + so you won't have to install dependencies that you do not need +""" +from typing import Optional + +from requests import get +from requests.exceptions import ConnectionError, ReadTimeout + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class InfluxDbContainer(DockerContainer): + """ + Abstract class for Docker containers of InfluxDB v1 and v2. + + Concrete implementations for InfluxDB 1.x and 2.x are separated iun different packages + because their respective clients rely on different Python libraries which we don't want + to import at the same time. + """ + + def __init__( + self, + # Docker image name + image: str, + # in the container, the default port for influxdb is often 8086 and not likely to change + container_port: int = 8086, + # specifies the port on the host machine where influxdb is exposed; a random available port otherwise + host_port: Optional[int] = None, + **docker_client_kw, + ): + super().__init__(image=image, **docker_client_kw) + self.container_port = container_port + self.host_port = host_port + self.with_bind_ports(self.container_port, self.host_port) + + def get_url(self) -> str: + """ + Returns the url to interact with the InfluxDB container (health check, REST API, etc.) + """ + host = self.get_container_host_ip() + port = self.get_exposed_port(self.container_port) + + return f"http://{host}:{port}" + + @wait_container_is_ready(ConnectionError, ReadTimeout) + def _health_check(self) -> dict: + """ + Performs a health check on the running InfluxDB container. + The call is retried until it works thanks to the @wait_container_is_ready decorator. + See its documentation for the max number of retries or the timeout. + """ + + url = self.get_url() + response = get(f"{url}/health", timeout=1) + response.raise_for_status() + + return response.json() + + def get_influxdb_version(self) -> str: + """ + Returns the version of the InfluxDB service, as returned by the healthcheck. + """ + + return self._health_check().get("version") + + def start(self) -> "InfluxDbContainer": + """ + Spawns a container of the InfluxDB Docker image, ready to be used. + """ + super().start() + self._health_check() + + return self diff --git a/modules/influxdb/testcontainers/influxdb1/__init__.py b/modules/influxdb/testcontainers/influxdb1/__init__.py new file mode 100644 index 000000000..81f21c163 --- /dev/null +++ b/modules/influxdb/testcontainers/influxdb1/__init__.py @@ -0,0 +1,65 @@ +# +# 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. + +from typing import Optional + +from influxdb import InfluxDBClient + +from testcontainers.influxdb import InfluxDbContainer + + +class InfluxDb1Container(InfluxDbContainer): + """ + Docker container for InfluxDB 1.x. + Official Docker images for InfluxDB are hosted at https://hub.docker.com/_/influxdb/. + + Example: + + .. doctest:: + + >>> from testcontainers.influxdb1 import InfluxDbContainer + + >>> with InfluxDbContainer() as influxdb: + ... version = influxdb.get_version() + """ + + def __init__( + self, + image: str = "influxdb:1.8", + # in the container, the default port for influxdb is often 8086 and not likely to change + container_port: int = 8086, + # specifies the port on the host machine where influxdb is exposed; a random available port otherwise + host_port: Optional[int] = None, + **docker_client_kw, + ): + super().__init__(image, container_port, host_port, **docker_client_kw) + + def get_client(self, **client_kwargs): + """ + Returns an instance of the influxdb client, for InfluxDB 1.x versions. + Note that this client is not maintained anymore, but it is the only + official client available for 1.x InfluxDB versions: + - https://github.com/influxdata/influxdb-python + - https://pypi.org/project/influxdb/ + + To some extent, you can use the v2 client with InfluxDB v1.8+: + - https://github.com/influxdata/influxdb-client-python#influxdb-18-api-compatibility + """ + + return InfluxDBClient(self.get_container_host_ip(), self.get_exposed_port(self.container_port), **client_kwargs) + + def start(self) -> "InfluxDb1Container": + """ + Overridden for better typing reason + """ + return super().start() diff --git a/modules/influxdb/testcontainers/influxdb2/__init__.py b/modules/influxdb/testcontainers/influxdb2/__init__.py new file mode 100644 index 000000000..bd33d7fc4 --- /dev/null +++ b/modules/influxdb/testcontainers/influxdb2/__init__.py @@ -0,0 +1,106 @@ +# +# 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. + +from os import getenv +from typing import Optional + +from influxdb_client import InfluxDBClient, Organization + +from testcontainers.influxdb import InfluxDbContainer + + +class InfluxDb2Container(InfluxDbContainer): + """ + Docker container for InfluxDB 2.x. + Official Docker images for InfluxDB are hosted at https://hub.docker.com/_/influxdb/. + + Example: + + .. doctest:: + + >>> from testcontainers.influxdb2 import InfluxDb2Container + + >>> with InfluxDb2Container() as influxdb2: + ... version = influxdb2.get_version() + """ + + def __init__( + self, + image: str = "influxdb:latest", + # in the container, the default port for influxdb is often 8086 and not likely to change + container_port: int = 8086, + # specifies the port on the host machine where influxdb is exposed; a random available port otherwise + host_port: Optional[int] = None, + # parameters used by the InfluxDSB 2.x Docker container when spawned in setup mode + # (which is likely what you want). In setup mode, init_mode should be "setup" and all + # the other parameters should be set (via this constructor or their respective + # environment variables); retention does not need to be explicitely set. + init_mode: Optional[str] = None, + admin_token: Optional[str] = None, + username: Optional[str] = None, + password: Optional[str] = None, + org_name: Optional[str] = None, + bucket: Optional[str] = None, + retention: Optional[str] = None, + **docker_client_kw, + ): + super().__init__(image, container_port, host_port, **docker_client_kw) + + configuration = { + "DOCKER_INFLUXDB_INIT_MODE": init_mode, + "DOCKER_INFLUXDB_INIT_ADMIN_TOKEN": admin_token, + "DOCKER_INFLUXDB_INIT_USERNAME": username, + "DOCKER_INFLUXDB_INIT_PASSWORD": password, + "DOCKER_INFLUXDB_INIT_ORG": org_name, + "DOCKER_INFLUXDB_INIT_BUCKET": bucket, + "DOCKER_INFLUXDB_INIT_RETENTION": retention, + } + for env_key, constructor_param in configuration.items(): + env_value = constructor_param or getenv(env_key) + if env_value: + self.with_env(env_key, env_value) + + def start(self) -> "InfluxDb2Container": + """ + Overridden for better typing reason + """ + return super().start() + + def get_client( + self, token: Optional[str] = None, org_name: Optional[str] = None, **influxdb_client_kwargs + ) -> tuple[InfluxDBClient, Organization]: + """ + Returns an instance of the influxdb client with the associated test organization created + when the container is spawn; for InfluxDB 2.x versions. + - https://github.com/influxdata/influxdb-client-python + - https://pypi.org/project/influxdb-client/ + + This InfluxDB client requires to specify the organization when using most of the API's endpoints, + as an Organisation instance rather than its name or id (deprecated). As a convenience, this + client getter can also retrieve and return the organization instance along with the client. + Otherwise, None is returned in place of the organization instance. + + This organization is created when spawning the container in setup mode (which is likely what you + want) by giving its name to the 'org_name' parameter constructor. + """ + + influxclient = InfluxDBClient(self.get_url(), token=token, **influxdb_client_kwargs) + + if org_name is None: + return influxclient, None + + orgs = influxclient.organizations_api().find_organizations(org=org_name) + if len(orgs) == 0: + raise ValueError(f"Could not retrieved the Organization corresponding to name '{org_name}'") + + return influxclient, orgs[0] diff --git a/modules/influxdb/tests/__init__.py b/modules/influxdb/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/influxdb/tests/test_influxdb.py b/modules/influxdb/tests/test_influxdb.py new file mode 100644 index 000000000..62144a3c3 --- /dev/null +++ b/modules/influxdb/tests/test_influxdb.py @@ -0,0 +1,138 @@ +# +# 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. + +from datetime import datetime +from typing import Type + +from influxdb.resultset import ResultSet +from influxdb_client import Bucket +from influxdb_client.client.write_api import SYNCHRONOUS +from pytest import mark + +from testcontainers.influxdb import InfluxDbContainer +from testcontainers.influxdb1 import InfluxDb1Container +from testcontainers.influxdb2 import InfluxDb2Container + + +@mark.parametrize( + ["image", "influxdb_container_class", "exposed_port"], + [ + ("influxdb:2.7", InfluxDb1Container, 8086), + ("influxdb:1.8", InfluxDb2Container, 8086), + ], +) +def test_influxdbcontainer_get_url(image: str, influxdb_container_class: Type[InfluxDbContainer], exposed_port: int): + with influxdb_container_class(image, host_port=exposed_port) as influxdb_container: + connection_url = influxdb_container.get_url() + assert str(exposed_port) in connection_url + + +@mark.parametrize( + ["image", "influxdb_container_class", "expected_version"], + [ + ("influxdb:1.8", InfluxDb1Container, "1.8.10"), + ("influxdb:1.8.10", InfluxDb1Container, "1.8.10"), + ("influxdb:2.7.4", InfluxDb2Container, "v2.7.4"), + ("influxdb:2.7", InfluxDb2Container, "v2.7"), + ], +) +def test_influxdbcontainer_get_influxdb_version( + image: str, influxdb_container_class: Type[InfluxDbContainer], expected_version: str +): + with influxdb_container_class(image) as influxdb_container: + assert influxdb_container.get_influxdb_version().startswith(expected_version) + + +def test_influxdb1container_get_client(): + """ + This is a test example showing how you could use testcontainers/influxdb for InfluxDB 1.x versions + """ + with InfluxDb1Container("influxdb:1.8") as influxdb1_container: + influxdb1_client = influxdb1_container.get_client() + databases = influxdb1_client.get_list_database() + assert len(databases) == 0, "the InfluxDB container starts with no database at all" + + # creates a database and inserts some datapoints + influxdb1_client.create_database("testcontainers") + databases = influxdb1_client.get_list_database() + assert len(databases) == 1, "the InfluxDB container now contains one database" + assert databases[0] == {"name": "testcontainers"} + + influxdb1_client.write_points( + [ + {"measurement": "influxdbcontainer", "time": "1978-11-30T09:30:00Z", "fields": {"ratio": 0.42}}, + {"measurement": "influxdbcontainer", "time": "1978-12-25T10:30:00Z", "fields": {"ratio": 0.55}}, + ], + database="testcontainers", + ) + + # retrieves the inserted datapoints + datapoints_set: ResultSet = influxdb1_client.query( + "select ratio from influxdbcontainer;", database="testcontainers" + ) + datapoints = list(datapoints_set.get_points()) + assert len(datapoints) == 2, "2 datapoints are retrieved" + + datapoint = datapoints[0] + assert datapoint["time"] == "1978-11-30T09:30:00Z" + assert datapoint["ratio"] == 0.42 + + datapoint = datapoints[1] + assert datapoint["time"] == "1978-12-25T10:30:00Z" + assert datapoint["ratio"] == 0.55 + + +def test_influxdb2container_get_client(): + """ + This is a test example showing how you could use testcontainers/influxdb for InfluxDB 2.x versions with the Flux query language + """ + with InfluxDb2Container( + "influxdb:2.7", + init_mode="setup", + username="root", + password="secret-password", + org_name="testcontainers-org", + bucket="my-init-bucket", + admin_token="secret-token", + ) as influxdb2_container: + influxdb2_client, test_org = influxdb2_container.get_client(token="secret-token", org_name="testcontainers-org") + assert influxdb2_client.ping(), "the client can connect to the InfluxDB instance" + + # ensures that the bucket does not exist yet + buckets_api = influxdb2_client.buckets_api() + bucket: Bucket = buckets_api.find_bucket_by_name("testcontainers") + assert bucket is None, "the test bucket does not exist yet" + + # creates a test bucket and insert a point + buckets_api.create_bucket(bucket_name="testcontainers", org=test_org) + bucket: Bucket = buckets_api.find_bucket_by_name("testcontainers") + assert bucket.name == "testcontainers", "the test bucket now exists" + + write_api = influxdb2_client.write_api(write_options=SYNCHRONOUS) + write_api.write( + "testcontainers", + "testcontainers-org", + [ + {"measurement": "influxdbcontainer", "time": "1978-11-30T09:30:00Z", "fields": {"ratio": 0.42}}, + {"measurement": "influxdbcontainer", "time": "1978-12-25T10:30:00Z", "fields": {"ratio": 0.55}}, + ], + ) + + # retrieves the inserted datapoints + query_api = influxdb2_client.query_api() + tables = query_api.query('from(bucket: "testcontainers") |> range(start: 1978-11-01T22:00:00Z)', org=test_org) + results = tables.to_values(["_measurement", "_field", "_time", "_value"]) + + assert len(results) == 2, "2 datapoints were retrieved" + assert results[0] == ["influxdbcontainer", "ratio", datetime.fromisoformat("1978-11-30T09:30:00+00:00"), 0.42] + assert results[1] == ["influxdbcontainer", "ratio", datetime.fromisoformat("1978-12-25T10:30:00+00:00"), 0.55] diff --git a/poetry.lock b/poetry.lock index c55fa8f9f..17c059466 100644 --- a/poetry.lock +++ b/poetry.lock @@ -133,13 +133,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "azure-core" -version = "1.29.7" +version = "1.30.1" description = "Microsoft Azure Core Library for Python" optional = true python-versions = ">=3.7" files = [ - {file = "azure-core-1.29.7.tar.gz", hash = "sha256:2944faf1a7ff1558b1f457cabf60f279869cabaeef86b353bed8eb032c7d8c5e"}, - {file = "azure_core-1.29.7-py3-none-any.whl", hash = "sha256:95a7b41b4af102e5fcdfac9500fcc82ff86e936c7145a099b7848b9ac0501250"}, + {file = "azure-core-1.30.1.tar.gz", hash = "sha256:26273a254131f84269e8ea4464f3560c731f29c0c1f69ac99010845f239c1a8f"}, + {file = "azure_core-1.30.1-py3-none-any.whl", hash = "sha256:7c5ee397e48f281ec4dd773d67a0a47a0962ed6fa833036057f9ea067f688e74"}, ] [package.dependencies] @@ -152,13 +152,13 @@ aio = ["aiohttp (>=3.0)"] [[package]] name = "azure-storage-blob" -version = "12.19.0" +version = "12.19.1" description = "Microsoft Azure Blob Storage Client Library for Python" optional = true python-versions = ">=3.7" files = [ - {file = "azure-storage-blob-12.19.0.tar.gz", hash = "sha256:26c0a4320a34a3c2a1b74528ba6812ebcb632a04cd67b1c7377232c4b01a5897"}, - {file = "azure_storage_blob-12.19.0-py3-none-any.whl", hash = "sha256:7bbc2c9c16678f7a420367fef6b172ba8730a7e66df7f4d7a55d5b3c8216615b"}, + {file = "azure-storage-blob-12.19.1.tar.gz", hash = "sha256:13e16ba42fc54ac2c7e8f976062173a5c82b9ec0594728e134aac372965a11b0"}, + {file = "azure_storage_blob-12.19.1-py3-none-any.whl", hash = "sha256:c5530dc51c21c9564e4eb706cd499befca8819b10dd89716d3fc90d747556243"}, ] [package.dependencies] @@ -186,17 +186,17 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "boto3" -version = "1.34.28" +version = "1.34.59" description = "The AWS SDK for Python" optional = true python-versions = ">= 3.8" files = [ - {file = "boto3-1.34.28-py3-none-any.whl", hash = "sha256:fb56622ce195c06ae0d15ae9472d44529362a869ad52862a5a28b891530969f9"}, - {file = "boto3-1.34.28.tar.gz", hash = "sha256:9e0dcca7bb0567f7b4b84d1d26c19b217abfe149d19106af7f120f09142688cf"}, + {file = "boto3-1.34.59-py3-none-any.whl", hash = "sha256:004e67b078be58d34469406f93cc8b95bc43becef4bbe44523a0b8e51f84c668"}, + {file = "boto3-1.34.59.tar.gz", hash = "sha256:162edf182e53c198137a28432a626dba103f787a8f5000ed4758b73ccd203fa0"}, ] [package.dependencies] -botocore = ">=1.34.28,<1.35.0" +botocore = ">=1.34.59,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -205,21 +205,21 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.28" +version = "1.34.59" description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">= 3.8" files = [ - {file = "botocore-1.34.28-py3-none-any.whl", hash = "sha256:03be8209257ab65f3c8be7377cf8d38bff6a6afbe3d36c72924e48959bb694dc"}, - {file = "botocore-1.34.28.tar.gz", hash = "sha256:45c99ccc6389ab1a87e996a7cc8797c7e41d5ecd9a5757d567ba3a57cb7655e7"}, + {file = "botocore-1.34.59-py3-none-any.whl", hash = "sha256:4bc112dafb1679ab571117593f7656604726a3da0e5ae5bad00ea772fa40e75c"}, + {file = "botocore-1.34.59.tar.gz", hash = "sha256:24edb4d21d7c97dea0c6c4a80d36b3809b1443a30b0bd5e317d6c319dfac823f"}, ] [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}, {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}, ] [package.extras] @@ -227,24 +227,24 @@ crt = ["awscrt (==0.19.19)"] [[package]] name = "cachetools" -version = "5.3.2" +version = "5.3.3" description = "Extensible memoizing collections and decorators" optional = true python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -423,110 +423,115 @@ files = [ [[package]] name = "clickhouse-driver" -version = "0.2.6" +version = "0.2.7" description = "Python driver with native interface for ClickHouse" optional = true python-versions = ">=3.7, <4" files = [ - {file = "clickhouse-driver-0.2.6.tar.gz", hash = "sha256:028baf4d65a0b3f9e0ac5df248cab20657b51adbfce6c5427aa6c16a7318dda1"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e61d975081b74cae9efe7a64b1de1a8aec5643affb81b57487dcae7d195f250f"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee0395b49bd8c0cd3dca6b3a4b9db347c1d300de83ee7b4f482a9d48b6c7af54"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d8aff4d3f0d78fd4b11e28ef344a5ee71d6850fef4a79e3265e0728b4d1d89"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b086bd658889af10205cb8307b714c8202bdfd05a4833fc7f4f82df2d88a963"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a1b6815d56a03398110c7f602a87ad767ecfd7a0869e61f2d8bfa0779dce2b"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36146fc3782a9e45a57c1094f3f8051db4117089502a3310312768dd7e14ef6d"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4032fb9b15ddbc484073ca165e5271eed494f3f3c4e8cb3a495bbc7a151fa556"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2e0332b4e4b68be0d5e97ee40cd3ce7f4f85523e3ea3656b4dda658ba723067"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7b659583fa2a8058dfeba952c0f17d5077b2af17db9c0f45ab8a5f9cf4dc1523"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d4ce06dc2d2593bedade4bf369c28d7b0494532774e849f7213f800b06a274a2"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3bf9071ca89f661ae7dd46f2561e7e97fe71fc96dbbaf0607afe636f173e5f40"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:429d9c16355aea62a462f75f373a2d9c4b76da7996a0eb6f038b2aa079020597"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1960244de84d7888598180e69689d1ba7ec6c9c99cd2c080a76315a7a29a5cab"}, - {file = "clickhouse_driver-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:5a6bdfde4e2fb81414200303950ba75c3f7ee9249e4a997854ce18e1cb4beea9"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef0a9453a972ec32399cc93a510aec33fa4b9b1f0c5050a3a40e5d298a89a7aa"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:95c13374741a8749980436603922ad7c476ae3b5e17850c50faba3879db66bdb"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d715d392eaadb43ded1c104354aebbc233f69bbf3919aa61beb7cc6ecdaa950a"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d75d50265616a2779d0b2acaebf7253783e2b8ad0df3efa6d23f0db1c9bf50"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32f0e02c28e9a6f1c1f116d1aa14772e73beb7efd4f30490d9f171d39b40551a"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08037080bd7d1d2816767d1bc693380073ce8bdf4ad0f62871c12b77b90323a5"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfe60ec8b695c298e6156c71a35ae6586676992cdfde6d2bf0c0b74414bfa0c"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:36ae6e627ffba6ed46fa9ac4dd745bfbfd5d9f39b198f46051ebfd0dde5e01c2"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2508638aa6f44cc0653840b99e308d3c0a8684c71e3132d7d067b160bdff5a81"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:88b77719e62aaa03a9d2d05e395788c4c112d41ff35a6756e7e7a1ac5dc1b4fb"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:95d1206252c6c9d0abad61310eda455ebcdcd0b1f41c3584daffbfd52b68654f"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3bdff826074af1b339fe9bff17844f6b8117080f895b8601f536b13a9d04f82a"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-win32.whl", hash = "sha256:c8c02606eabe4288045bbba497088b7fe976c34330c1066db9744fa09fef4a2a"}, - {file = "clickhouse_driver-0.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:44df94940739a72a02716bb14ac8b683aef84b54b05783d96201ff334bcd88fb"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:079708ac620343736c2c8dace6663178156f4ded47bf25245b56147498d0d7de"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e13369cf516df6c33c156fe66cfff502f66fc25f2a515c761ed1480fc83b3aa9"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbc0bf957fc6d0163ee06ac02275bdb2f40d109fc225366e387358e78d968a43"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f58b0ffb434fefe99b7419e09d6071a49773e9eb49c5ebeedf7c3180b40c2330"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0746dac9aa5cf2c275187aef16b67ae922ef257c82671948a6be86e19ee9cb2"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f1ce40c9a2715ea44be9a5c33cb5b08048c1ef5595a6739443473e4ba23fedf"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9499a2b2d5e856c7e8efd28da479df8a962e2497c70bf5e2d9a25875d520465"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8b2e849bb7102365a480d9d1083ed203a244f0c02a0fc973eab6078b3d14638d"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5846c50e2dfe0ce2f300275955a20f82422b1128b09ab5a9ea4d8a00d4ba8438"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a12990b54b92b2a2598f144388e766d6261492408f2434738fe649423371894b"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:af14a5699fea890a1f8f022c624ca9f61994e15913cfaf4e0e58b1e4ac99540a"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:965fb8370eb7ee8a20cdf54d7c2fe024f587da692bd15e94dd2eee93a3c88f4b"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-win32.whl", hash = "sha256:9c552205d2b6125a99121080417c5c7bbc47af81ed15bb5ff9be464fed96bb68"}, - {file = "clickhouse_driver-0.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:a58fb8b12a32d58ce0c72839293ec5bacc7904f3db36a82bb963f394dbb5f230"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f2a9abb8b1464985f7a480f956744736e611970ffc8ffd3eb0b46343a3a691e6"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e01696c450a2de41d586689dbaed0893d4de7469811abd3bf831a0483e723a"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0bb85760dabbef493aec985ad94612132ddeb5b81569cf0a7222f6cb7278eda"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26fa7c46a65beb6725e9d77701ed2871c8b3b7fd0c187c3c8550ae95e9886038"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c489cd1cf8b98f78e95559122c5b0d52f25b619cfd2ca31d0784a0bea38940b4"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af20ef3ddc7f834ebb316c349770f5c381354b573419523b9703ea48ed4bd692"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82538f76640cbd22540f9de301d996b1e48dbf5de71a79fc06826ea094c8e5f7"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ccd34a5592f4212483138bea45dc6526c4cf7b5aa4b806f422b66d27232f7271"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:ab10bc9db2fbc0d5ab785c7771bfaac526ac6724b8727c2f0708caee878a6a48"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c9882ca5fd98b1801a283889e085e88c929fae1b68adc4e6b00ef1cf60adb843"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:051a1bd0f70a8d0bc11ef90b6e6291981cd8e3031cc126a7c78206849c1b8cf7"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:48f47694d5e54af192a4aa2a24f947795c362ab40a253d088593880fede97568"}, - {file = "clickhouse_driver-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b783e5d3d12947c73d991bceb6b8765231512ab0ac6363823cdcd2c283c67a99"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d24e0acf8fef1d787851ae048e0168b2fb10297c3235cbb87974f78db37d3d3"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27dc025f10a930aed453eec5ed9a0404e7b2db671da4a253109facf5c1ad1b4c"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d534c744b1b211241f8c58d2ad5fcfc465a0503011d9b9073c00e25507abcbf3"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:858b8039a1241591b63f368de9dbdef6c4e6466b6bf0e01d53d36f7091af7569"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21eb62e1de7d2d5483d121d1447e857030bf866d4f23572b0dedc515f9359cd0"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eddbc4e90da8d3f08b5aa6c58a7155ebb398cc34255083e7103071b4c4a76952"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f67ac3bf4cec39c051b33152bc1f370a3f0311774c73965727e14877e314fd"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:231ef65b7c99f2e937990b7072cf68bda6b51f45c903e5c8c068f7d754bdd489"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f4aadf85dc199f3d1ef06b961c87b168d009c88bfe431b4821460678d4ad51a9"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f08bc819a0c17b787c2984406611f5f2d9a8e33118090376c4bc8d932f38ea10"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ace862809bb89f896c2150e20ea6bbeb969c25ca40bfe389179469b0e0ec5dd7"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:512d2d5714e811dfc63eefdf5e0171f24a5e698134e684d8efe27c001fc3a06b"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-win32.whl", hash = "sha256:b9b775f70371a7333ac828fe2bbd9473c94e18728ac6b70b2865cdee1f0d551f"}, - {file = "clickhouse_driver-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:d13fe44620750abcd4c93c067d6e44c8a1ea050856c4c27a5633ad8ff197a689"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e17995752eef4f742976abab03ff3f5b81edb9b9218b151abaf3534055fcf2b8"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c23baf4b4185b3ee13332c05c201e242600e35deb8b0b0d95211e71d5eb3f59"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbd7e3e33d2bc5f32da2557e97299340a722f948790494a2e9efaed4635ff499"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c872e9fee17d278816fc30b4df4b10bedd8eec9efaa614c71725f147b00b30d"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c8af2761676cd306962a86cc87a4187efcfdaf253a0d908c8f8ef791277a7fe"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7ee63cf9443a94a0bc856ac947c9bfd8c214c12e340846f341391bb161cc4e"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3117c1bbcbec64a39c283ab2ff1ca284e57d5943b8e68e6f1df718ab04cb66e2"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9079621484b1f10017a65f7f84d81b13e44e9f23c5da1e04731405531bb63d58"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:aa73edc701635c5042c0355c8a0222eb03af117bebaf898017958d0e2cefb3b8"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:b5d9afcdec1fcc4e675fd25d31cd506b369efddc78d5e775804cfc911c773551"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6ad658e12e7c928283eb47f82fe4d36c8974918aabad1b3981212813fe21d03b"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82fdc618428b31c418e6cdedfd66bd50b7662e2c092f685438bbe77e7f295f57"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-win32.whl", hash = "sha256:1736793aa273ebb71895eaa77ae4ab8ce361a28fc1cd5d92247f7af22a030c07"}, - {file = "clickhouse_driver-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:0efc58bf8b21a84b68bbba083702dc17cab5255d2552e73dacadc830b612bd38"}, - {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5fdf175283918748e4821797e21cc0c91c44803e92698bd66f206769fb18da73"}, - {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82c4145bf1531e4a508e187e5175e9c4d3749de5d98643141a348464360b8076"}, - {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69c30943915b2ea794b8a85b8c2f6aa17dbf19a03cae1bc541c49b024f861200"}, - {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45cfa76e7dadf097e77190dc27c23361cf2806ad646df979c657336e667af03e"}, - {file = "clickhouse_driver-0.2.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d59c21e2d11e8e226ab1420a928f34be958301781dabc0176a8ae6e4d6dfa5b5"}, - {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07ab99b84510a88af3358a35deafa09942bdc26ad8213576af3f723e0cc11bb0"}, - {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712f9c313513898de98ac31e63aa2c0186f632d0490d6f2d010f259f35b9bf05"}, - {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f2cb2fc81ee5c44068dd1d15a052a092cd6008de16d7e7850b1da7e29f316e"}, - {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4bd0bf5fc48bf6317698714721fe5127c6ebc0d8ebd0ea14217bfd7d617303f5"}, - {file = "clickhouse_driver-0.2.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b52e08b5ccf3a8ceca3727d0b4594c88e5b7876d5a17451d61ed78b158ada843"}, - {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0081a4ac2fdb940c12dd74dd835323dfcda1e3df7cf178d9174f928fd28c1cfd"}, - {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27eeb4f3cf5403dd2e8c6871c25dc129e5fc3dc6fb4ea125cd755be6476c6ff1"}, - {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1ab6ce26455e4db46431fa75c6d6913e0ef91ac54ec8554ef9455e32d0090bb"}, - {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36143db3bee3f16cc98ecda6df0110a5c6c479c69ec99fb2bb904a8f8139b64d"}, - {file = "clickhouse_driver-0.2.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9fc3eacdcbc2ce5ca967c0e8dc74f2238fe9fd1bced50ea355580eebcc800dfd"}, + {file = "clickhouse-driver-0.2.7.tar.gz", hash = "sha256:299cfbe6d561955d88eeab6e09f3de31e2f6daccc6fdd904a59e46357d2d28d9"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c44fefc2fd44f432d5b162bfe34ad76840137c34167d46a18c554a7c7c6e3566"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e018452a7bf8d8c0adf958afbc5b0d29e402fc09a1fb34e9186293eae57f3b4e"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff8b09f8b13df28d2f91ee3d0d2edd9589cbda76b74acf60669112219cea8c9d"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54aa91c9512fd5a73f038cae4f67ca2ff0b2f8a84de846179a31530936ef4e20"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8342a7ba31ccb393ee31dfd61173aa84c995b4ac0b44d404adc8463534233d5"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:199000f8adf38fade0b5a52c273a396168105539de741a18ba3e68d7fc06e0e6"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60a2a40602b207506e505cfb184a81cd4b752bde17153bc0b32c3931ddb792f"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5db3a26b18146b2b0b06d3f32ce588af5afaa38c719daf6f9606981514228a8b"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5579a31da1f3cf49630e43fbbb11cab891b78161abdcb33908b79820b7cd3a23"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cc39f0fb761aed96917b0f55679174a50f9591afc0e696e745cd698ef822661f"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9aa0f7c740e4e61886c6d388792c5d1a2084d4b5462e6dcfc24e30ca7e7f8e68"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2caee88b6eec7b33ddbccd24501ad99ff8ff2b0a6a4471945cbfb28947a9a791"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-win32.whl", hash = "sha256:a4aef432cc7120a971eebb7ca2fddac4472e810b57e403d3a371b0c69cbb2bb0"}, + {file = "clickhouse_driver-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f307de7df6bc23ad5ec8a1ba1db157f4d14de673ddd4798f37790f23255605b0"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbf3ca8919bf856ca6588669a863065fb732a32a6387095f64d19038fd99db9f"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab68b3d9b9d1386adfd3a57edd47b62858a145bf7ccc7f11b31d308195d966e5"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985a9d60044c5ad39c6e018b852c7105ec4ebfdf4c3abe23183b4867454e570a"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c94330054c8d92d2286898906f843f26e2f96fc2aa11a9a96a7b5593d299bf0"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92938f55c8f797e50e624a4b96e685178d043cdf0ede306a7fd4e7dda19b8dfd"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bd53e9bf49c3013d06f9e6d2812872d44b150f7a2d1cf18e1498257d42330e"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f1f8ed5404e283a9ded499c33eade2423fdc15e31f8a711d75e91f890d0f70b"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a398085e4a1766d907ac32c282d4172db38a44243bde303372396208d1cbf4bb"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa1808593123b6056f93808f0afbc7938f06a8149cb4e381aa7b1a234c1d3c18"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:0512d54ae23bd4a69278e04f42b651d7c71b63ba6043e2c6bd97b11329692f99"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5bc2b67e7e68f74ccebf95a8b3a13f13a7c34b89b32c9813103221de14c06c8b"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:04a37cdafc671cb796af3e566cef0aeb39111d82aebeecd9106a049434953b26"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-win32.whl", hash = "sha256:019538c7c23e976538e5081dd2f77a8a40bf663c638a62d857ff05f42b0c9052"}, + {file = "clickhouse_driver-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5166643683584bc53fcadda73c65f6a9077feb472f3d167ecef1a1a7024973aa"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:59affab7b5a3c4aab5b6a730f606575efdefea213458de2eb14927ee4e0640f4"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcb93dd07fe65ac4f1a2bc0b8967911d4ad2152dbee000f025ea5cb575da5ecb"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55a48019b79181ae1ca90e980e74c5d413c3f8829f6744e2b056646c2d435a1a"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507463c9157240fd7c3246781e8c30df8db3c80bf68925b36ff3ad4a80c4b924"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e2d8d2295ee9e0cfab8ad77cb635a05da2160334b4f16ed8c3d00fbf39a2343"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e38c44546dcdb956b5ab0944cb3d51e8c98f816e75bab1a2254c478865bc6e7b"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6690a2bdd9e7531fe50b53193279f8b35cbcd5c5ee36c0fcc112518a7d24f16"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc6b4ba0a6467fd09021aa1d87a44fb4589600d61b010fca41e0dfffd0dee322"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:254bbd400eb87ff547a08755bc714f712e11f7a6d3ebbbb7aaa1dd454fb16d44"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7bbbe3f8b87fc1489bc15fa9c88cc9fac9d4d7d683d076f058c2c83e6ee422fd"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:745e5b18f0957d932151527f1523d0e516c199de8c589638e5f55ab2559886f3"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fa0357fb5f26149e3df86a117d3678329b85d8827b78a5a09bbf224d8dd4541"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-win32.whl", hash = "sha256:ace652af7ca94ba3cb3a04a5c363e135dc5009f31d8201903e21db9d5daf2358"}, + {file = "clickhouse_driver-0.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:c0ba68489544df89e4138a14b0ec3e1e5eb102d5d3283a91d9b837c420c0ab97"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:66267e4ba21fa66c97ce784a5de2202d3b7d4db3e50bfcdde92830a68f6fae30"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf55c285b75c178487407721baef4980b3c6515c9c0c1a6c1ea8b001afe658e"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:deeb66bb56490db2157f199c6d9aa2c53f046677be430cc834fc1e74eec6e654"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfe5b4020939abeeb407b4eead598c954b1573d2d2b4f174f793b196d378b9d9"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84d39506b5f8d86a1195ebde1c66aba168f34ebce6ebd828888f0625cac54774"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f93a27db2dcbbd3ecad36e8df4395d047cb7410e2dc69f6d037674e15442f4ee"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ebc29e501e47ecbfd44c89c0e5c87b2a722049d38b9e93fdd4bea510a82e16ac"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f9cc8c186fea09a94d89e5c9c4e8d05ec3a80e2f6d25673c48efec8117a13cfc"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0757dfde5410c42230b24825ea3ab904a78160520e5ceb953482e133e368733b"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c9f88818cf411f928c29ba295c677cd95773bd256b8490f5655fb489e0c6658c"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e19952f158ebe274c65ffeb294ba378d75048a48f31b77573948d606bed019d5"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:008b1f32c7c68564de8051482b72a5289b6933bca9d9b1ad1474dd448d6768ba"}, + {file = "clickhouse_driver-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:622933cc9834c39f03de5d43a12f13fc7133d31d6d2597e67866d4a549ca9e60"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:92540581e5b5f36d915f14d05c30244870fb123c74b38c645fa47663053c5471"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02dfadc6111b64e01c20b8c11266cab97d4f06685a392a183af437f2f1afb990"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ca17fece86fe85d97705024bec881978271931b3d00db273c9d63244f7d606"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76474f1315ca3ab484ae28ad085b8f756c8b9a755882f93912b2149290482033"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5c0ff12368b34aaf58dd948b0819e5b54d261911de334d3f048328dc9354013"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd441b17294e90e313b08fabf84fcc782c191d2b9b2a924f163928202db6fcc"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62aa158f61d7d84c58e8cd75b3b8340b28607e5a70132395078f578d518aaae3"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcb2a39a1fef8bf1b581f06125c2a84a5b92c939b079d1a95126e3207b05dc77"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f29cc641a65e89a51a15f6d195f565ad2761d1bd653408c6b4046c987c5fb99"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ac1a43690696bda46c9a23fc6fd79b6fe22d428a18e880bdbdf5e6aeb31008c5"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1dd5ea4584c42f85d96ddfa7d07da2abb35a797c45e4d3a66ace149ee4977cad"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a736c0af858a3c83af03848b18754ab18dc594cc7f3bf6be0b1fac682def182c"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-win32.whl", hash = "sha256:6cb8ca47f5818c1bc5814b9ff775e383f3c50059b1fd28a02cb9be1b666929f8"}, + {file = "clickhouse_driver-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:a90e7dc92985669a5e6569356bb3028d9d475f95006d4487cb0789aa53f9489c"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04b77cd6c583da9135db4a62c5a7999ae248c2dbfc0cb8e8a3d8a853b1fbfa11"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c7671f8c0e8960d766b2e0eaefcae3088fccdd3920e9cd3dee8e344cfd0a6929"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:502d7cd28522b95a399e993ffd48487e8c12c50ce2d4e89b77b938f945304405"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:969739279f4010e7b5b6b2c9d2ab56a463aed11fdaed5e02424c1b3915f144f8"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed34b60f741eeb02407ea72180d77cbfc368c1be6fc2f2ff8319d1856ce67e10"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a667b48927f4420eb8c03fa33369edfbdf359a788897a01ac945263a2a611461"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f93aa3a90f3847872d7464ec9076482b2e812c4e7d61682daedffdf3471be00"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:190890667215691fdf2155c3b233b39146054ab1cd854c7d91221e6ed633d71e"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ff280aeac5e96c764cd31ba1077c95601337b9a97fb0b9ed4d24c64431f2c322"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01e63e35d2ab55b8eb48facf6e951968c80d27ee6703aa6c91c73d9d0a4d0efe"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a29fb24b910dafc8c11ba882797d13ec0323a97dce80a57673116fa893d1b669"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5f229a7853fc767e63143ea69889d49f6fd5623adc2f7b0f7eb360117d7e91a5"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-win32.whl", hash = "sha256:b7f34ad2ed509f48f8ed1f9b96e89765173a7b35d286c7350aa85934a11c0f49"}, + {file = "clickhouse_driver-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:78b166597afbe490cc0cdac44fed8c8b81668f87125601dda17b154f237eef5d"}, + {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:16ab64beb8d079cb9b3200539539a35168f524eedf890c9acefb719e25bdc96e"}, + {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03e28fd50fc7c54874bf8e638a2ea87f73ae35bfbbf90123fdb395f38d62f159"}, + {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0677b8350acd8d186b6acd0026b62dd262d6fee428a5fa3ad9561908d4b02c39"}, + {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a2f3c9e2182809131701bb28a606dec90525c7ab20490714714a4b3eb015454b"}, + {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e03a1a1b30cc58c9bd2cbe25bf5e40b1f1d16d52d44ddefb3af50435d1ed613c"}, + {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1be8081306a4beb12444ed8e3208e1eb6c01ed207c471b33009c13504c88139"}, + {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:933b40722cbca9b1123a5bb2fb4bafafd234deae0f3481125cb6b6fa1d39aa84"}, + {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3054b5022f9bf15a5f4663a7cd190f466e70a2d7b8d45429d8742c515b556c10"}, + {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61744760ee046c9a268cb801ca21bfe44c4873db9901a7cd0f3ca8830205feff"}, + {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5e28427e05a72e7a4c3672e36703a2d80107ee0b3ab537e3380d726c96b07821"}, + {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c483f5ec836ae87803478f2a7b9daf15343078edd6a8be7364dd9db64905bbd0"}, + {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28220b794874e68bc2f06dbfff5748f1c5a3236922f59e127abd58d44ae20a3f"}, + {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c09877b59b34d5b3043ad70ec31543173cac8b64b4a8afaa89416b22fb28da5"}, + {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3580f78db27119f7380627873214ae1342066f1ecb35700c1d7bf418dd70ae73"}, + {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0842ac1b2f7a9ca46dac2027849b241bccd8eb8ff1c59cb0a5874042b267b733"}, + {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a3fb585e2d3514196258a4a3b0267510c03477f3c2380239ade4c056ba689a7"}, + {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ea25287566d45efbaee0857ad25e8b33ffd7fd73e89424d79fe7f532962915"}, + {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee4a4935667b59b4816a5ca77300f5dbe5a7416860551d17376426b8fefc1175"}, + {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:358058cfceea9b43c4af9de81842563746f16984b34525a15b41eacf8fc2bed2"}, + {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ae760fb843dec0b5c398536ca8dfaf243f494ba8fc68132ae1bd62004b0c396a"}, ] [package.dependencies] @@ -551,63 +556,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.0" +version = "7.4.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, - {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, - {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, - {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, - {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, - {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, - {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, - {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, - {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, - {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, - {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, - {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, - {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, - {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, + {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, + {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, + {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, + {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, + {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, + {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, + {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, + {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, + {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, + {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, + {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, + {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, ] [package.dependencies] @@ -618,43 +623,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.1" +version = "42.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, - {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, - {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, - {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, - {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, - {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, - {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, - {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, + {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, + {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, + {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, + {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, + {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, + {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, ] [package.dependencies] @@ -722,22 +727,22 @@ files = [ [[package]] name = "dnspython" -version = "2.5.0" +version = "2.6.1" description = "DNS toolkit" optional = true python-versions = ">=3.8" files = [ - {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, - {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1)"] -trio = ["trio (>=0.14)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] [[package]] @@ -772,24 +777,6 @@ files = [ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] -[[package]] -name = "ecdsa" -version = "0.18.0" -description = "ECDSA cryptographic signature library (pure python)" -optional = true -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, - {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, -] - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - [[package]] name = "exceptiongroup" version = "1.2.0" @@ -822,25 +809,25 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "google-api-core" -version = "2.15.0" +version = "2.17.1" description = "Google API client core library" optional = true python-versions = ">=3.7" files = [ - {file = "google-api-core-2.15.0.tar.gz", hash = "sha256:abc978a72658f14a2df1e5e12532effe40f94f868f6e23d95133bd6abcca35ca"}, - {file = "google_api_core-2.15.0-py3-none-any.whl", hash = "sha256:2aa56d2be495551e66bbff7f729b790546f87d5c90e74781aa77233bcb395a8a"}, + {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, + {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, ] [package.dependencies] google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" @@ -852,13 +839,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-auth" -version = "2.27.0" +version = "2.28.2" description = "Google Authentication Library" optional = true python-versions = ">=3.7" files = [ - {file = "google-auth-2.27.0.tar.gz", hash = "sha256:e863a56ccc2d8efa83df7a80272601e43487fa9a728a376205c86c26aaefa821"}, - {file = "google_auth-2.27.0-py2.py3-none-any.whl", hash = "sha256:8e4bad367015430ff253fe49d500fdc3396c1a434db5740828c728e45bcce245"}, + {file = "google-auth-2.28.2.tar.gz", hash = "sha256:80b8b4969aa9ed5938c7828308f20f035bc79f9d8fb8120bf9dc8db20b41ba30"}, + {file = "google_auth-2.28.2-py2.py3-none-any.whl", hash = "sha256:9fd67bbcd40f16d9d42f950228e9cf02a2ded4ae49198b27432d0cded5a74c38"}, ] [package.dependencies] @@ -875,23 +862,24 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-cloud-pubsub" -version = "2.19.0" +version = "2.20.1" description = "Google Cloud Pub/Sub API client library" optional = true python-versions = ">=3.7" files = [ - {file = "google-cloud-pubsub-2.19.0.tar.gz", hash = "sha256:6a98c33f7eb5f7de2ae52efa059b2b5f75b2ccd9f0f11f2edcefdda8d14e425c"}, - {file = "google_cloud_pubsub-2.19.0-py2.py3-none-any.whl", hash = "sha256:0cc444e5b2220a703106668829315a724cfb4304d6772725035993bb2fc81388"}, + {file = "google-cloud-pubsub-2.20.1.tar.gz", hash = "sha256:b6d06f1827968273c42b57a09f642462649c9504dc0f8756f99770f4e3e755ad"}, + {file = "google_cloud_pubsub-2.20.1-py2.py3-none-any.whl", hash = "sha256:06dd62181e2f248f32b9077f4dc07b413191a84fc06d7323b208602d887207bc"}, ] [package.dependencies] google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-auth = ">=2.14.1,<3.0.0dev" grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" grpcio = ">=1.51.3,<2.0dev" grpcio-status = ">=1.33.2" proto-plus = [ - {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, + {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -1005,84 +993,84 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 [[package]] name = "grpcio" -version = "1.60.0" +version = "1.62.1" description = "HTTP/2-based RPC framework" optional = true python-versions = ">=3.7" files = [ - {file = "grpcio-1.60.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:d020cfa595d1f8f5c6b343530cd3ca16ae5aefdd1e832b777f9f0eb105f5b139"}, - {file = "grpcio-1.60.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b98f43fcdb16172dec5f4b49f2fece4b16a99fd284d81c6bbac1b3b69fcbe0ff"}, - {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:20e7a4f7ded59097c84059d28230907cd97130fa74f4a8bfd1d8e5ba18c81491"}, - {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452ca5b4afed30e7274445dd9b441a35ece656ec1600b77fff8c216fdf07df43"}, - {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43e636dc2ce9ece583b3e2ca41df5c983f4302eabc6d5f9cd04f0562ee8ec1ae"}, - {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e306b97966369b889985a562ede9d99180def39ad42c8014628dd3cc343f508"}, - {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f897c3b127532e6befdcf961c415c97f320d45614daf84deba0a54e64ea2457b"}, - {file = "grpcio-1.60.0-cp310-cp310-win32.whl", hash = "sha256:b87efe4a380887425bb15f220079aa8336276398dc33fce38c64d278164f963d"}, - {file = "grpcio-1.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:a9c7b71211f066908e518a2ef7a5e211670761651039f0d6a80d8d40054047df"}, - {file = "grpcio-1.60.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:fb464479934778d7cc5baf463d959d361954d6533ad34c3a4f1d267e86ee25fd"}, - {file = "grpcio-1.60.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:4b44d7e39964e808b071714666a812049765b26b3ea48c4434a3b317bac82f14"}, - {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:90bdd76b3f04bdb21de5398b8a7c629676c81dfac290f5f19883857e9371d28c"}, - {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91229d7203f1ef0ab420c9b53fe2ca5c1fbeb34f69b3bc1b5089466237a4a134"}, - {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b36a2c6d4920ba88fa98075fdd58ff94ebeb8acc1215ae07d01a418af4c0253"}, - {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:297eef542156d6b15174a1231c2493ea9ea54af8d016b8ca7d5d9cc65cfcc444"}, - {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:87c9224acba0ad8bacddf427a1c2772e17ce50b3042a789547af27099c5f751d"}, - {file = "grpcio-1.60.0-cp311-cp311-win32.whl", hash = "sha256:95ae3e8e2c1b9bf671817f86f155c5da7d49a2289c5cf27a319458c3e025c320"}, - {file = "grpcio-1.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:467a7d31554892eed2aa6c2d47ded1079fc40ea0b9601d9f79204afa8902274b"}, - {file = "grpcio-1.60.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:a7152fa6e597c20cb97923407cf0934e14224af42c2b8d915f48bc3ad2d9ac18"}, - {file = "grpcio-1.60.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:7db16dd4ea1b05ada504f08d0dca1cd9b926bed3770f50e715d087c6f00ad748"}, - {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:b0571a5aef36ba9177e262dc88a9240c866d903a62799e44fd4aae3f9a2ec17e"}, - {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fd9584bf1bccdfff1512719316efa77be235469e1e3295dce64538c4773840b"}, - {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6a478581b1a1a8fdf3318ecb5f4d0cda41cacdffe2b527c23707c9c1b8fdb55"}, - {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:77c8a317f0fd5a0a2be8ed5cbe5341537d5c00bb79b3bb27ba7c5378ba77dbca"}, - {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1c30bb23a41df95109db130a6cc1b974844300ae2e5d68dd4947aacba5985aa5"}, - {file = "grpcio-1.60.0-cp312-cp312-win32.whl", hash = "sha256:2aef56e85901c2397bd557c5ba514f84de1f0ae5dd132f5d5fed042858115951"}, - {file = "grpcio-1.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:e381fe0c2aa6c03b056ad8f52f8efca7be29fb4d9ae2f8873520843b6039612a"}, - {file = "grpcio-1.60.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:92f88ca1b956eb8427a11bb8b4a0c0b2b03377235fc5102cb05e533b8693a415"}, - {file = "grpcio-1.60.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:e278eafb406f7e1b1b637c2cf51d3ad45883bb5bd1ca56bc05e4fc135dfdaa65"}, - {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:a48edde788b99214613e440fce495bbe2b1e142a7f214cce9e0832146c41e324"}, - {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de2ad69c9a094bf37c1102b5744c9aec6cf74d2b635558b779085d0263166454"}, - {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:073f959c6f570797272f4ee9464a9997eaf1e98c27cb680225b82b53390d61e6"}, - {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c826f93050c73e7769806f92e601e0efdb83ec8d7c76ddf45d514fee54e8e619"}, - {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e30be89a75ee66aec7f9e60086fadb37ff8c0ba49a022887c28c134341f7179"}, - {file = "grpcio-1.60.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b0fb2d4801546598ac5cd18e3ec79c1a9af8b8f2a86283c55a5337c5aeca4b1b"}, - {file = "grpcio-1.60.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:9073513ec380434eb8d21970e1ab3161041de121f4018bbed3146839451a6d8e"}, - {file = "grpcio-1.60.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:74d7d9fa97809c5b892449b28a65ec2bfa458a4735ddad46074f9f7d9550ad13"}, - {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:1434ca77d6fed4ea312901122dc8da6c4389738bf5788f43efb19a838ac03ead"}, - {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e61e76020e0c332a98290323ecfec721c9544f5b739fab925b6e8cbe1944cf19"}, - {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675997222f2e2f22928fbba640824aebd43791116034f62006e19730715166c0"}, - {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5208a57eae445ae84a219dfd8b56e04313445d146873117b5fa75f3245bc1390"}, - {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:428d699c8553c27e98f4d29fdc0f0edc50e9a8a7590bfd294d2edb0da7be3629"}, - {file = "grpcio-1.60.0-cp38-cp38-win32.whl", hash = "sha256:83f2292ae292ed5a47cdcb9821039ca8e88902923198f2193f13959360c01860"}, - {file = "grpcio-1.60.0-cp38-cp38-win_amd64.whl", hash = "sha256:705a68a973c4c76db5d369ed573fec3367d7d196673fa86614b33d8c8e9ebb08"}, - {file = "grpcio-1.60.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c193109ca4070cdcaa6eff00fdb5a56233dc7610216d58fb81638f89f02e4968"}, - {file = "grpcio-1.60.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:676e4a44e740deaba0f4d95ba1d8c5c89a2fcc43d02c39f69450b1fa19d39590"}, - {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5ff21e000ff2f658430bde5288cb1ac440ff15c0d7d18b5fb222f941b46cb0d2"}, - {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c86343cf9ff7b2514dd229bdd88ebba760bd8973dac192ae687ff75e39ebfab"}, - {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fd3b3968ffe7643144580f260f04d39d869fcc2cddb745deef078b09fd2b328"}, - {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:30943b9530fe3620e3b195c03130396cd0ee3a0d10a66c1bee715d1819001eaf"}, - {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b10241250cb77657ab315270b064a6c7f1add58af94befa20687e7c8d8603ae6"}, - {file = "grpcio-1.60.0-cp39-cp39-win32.whl", hash = "sha256:79a050889eb8d57a93ed21d9585bb63fca881666fc709f5d9f7f9372f5e7fd03"}, - {file = "grpcio-1.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a97a681e82bc11a42d4372fe57898d270a2707f36c45c6676e49ce0d5c41353"}, - {file = "grpcio-1.60.0.tar.gz", hash = "sha256:2199165a1affb666aa24adf0c97436686d0a61bc5fc113c037701fb7c7fceb96"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.60.0)"] + {file = "grpcio-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:179bee6f5ed7b5f618844f760b6acf7e910988de77a4f75b95bbfaa8106f3c1e"}, + {file = "grpcio-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:48611e4fa010e823ba2de8fd3f77c1322dd60cb0d180dc6630a7e157b205f7ea"}, + {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b2a0e71b0a2158aa4bce48be9f8f9eb45cbd17c78c7443616d00abbe2a509f6d"}, + {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbe80577c7880911d3ad65e5ecc997416c98f354efeba2f8d0f9112a67ed65a5"}, + {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f6c693d446964e3292425e1d16e21a97a48ba9172f2d0df9d7b640acb99243"}, + {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:77c339403db5a20ef4fed02e4d1a9a3d9866bf9c0afc77a42234677313ea22f3"}, + {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b5a4ea906db7dec694098435d84bf2854fe158eb3cd51e1107e571246d4d1d70"}, + {file = "grpcio-1.62.1-cp310-cp310-win32.whl", hash = "sha256:4187201a53f8561c015bc745b81a1b2d278967b8de35f3399b84b0695e281d5f"}, + {file = "grpcio-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:844d1f3fb11bd1ed362d3fdc495d0770cfab75761836193af166fee113421d66"}, + {file = "grpcio-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:833379943d1728a005e44103f17ecd73d058d37d95783eb8f0b28ddc1f54d7b2"}, + {file = "grpcio-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:c7fcc6a32e7b7b58f5a7d27530669337a5d587d4066060bcb9dee7a8c833dfb7"}, + {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:fa7d28eb4d50b7cbe75bb8b45ed0da9a1dc5b219a0af59449676a29c2eed9698"}, + {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48f7135c3de2f298b833be8b4ae20cafe37091634e91f61f5a7eb3d61ec6f660"}, + {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f11fd63365ade276c9d4a7b7df5c136f9030e3457107e1791b3737a9b9ed6a"}, + {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b49fd8fe9f9ac23b78437da94c54aa7e9996fbb220bac024a67469ce5d0825f"}, + {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:482ae2ae78679ba9ed5752099b32e5fe580443b4f798e1b71df412abf43375db"}, + {file = "grpcio-1.62.1-cp311-cp311-win32.whl", hash = "sha256:1faa02530b6c7426404372515fe5ddf66e199c2ee613f88f025c6f3bd816450c"}, + {file = "grpcio-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bd90b8c395f39bc82a5fb32a0173e220e3f401ff697840f4003e15b96d1befc"}, + {file = "grpcio-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:b134d5d71b4e0837fff574c00e49176051a1c532d26c052a1e43231f252d813b"}, + {file = "grpcio-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d1f6c96573dc09d50dbcbd91dbf71d5cf97640c9427c32584010fbbd4c0e0037"}, + {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:359f821d4578f80f41909b9ee9b76fb249a21035a061a327f91c953493782c31"}, + {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a485f0c2010c696be269184bdb5ae72781344cb4e60db976c59d84dd6354fac9"}, + {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50b09b4dc01767163d67e1532f948264167cd27f49e9377e3556c3cba1268e1"}, + {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3227c667dccbe38f2c4d943238b887bac588d97c104815aecc62d2fd976e014b"}, + {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3952b581eb121324853ce2b191dae08badb75cd493cb4e0243368aa9e61cfd41"}, + {file = "grpcio-1.62.1-cp312-cp312-win32.whl", hash = "sha256:83a17b303425104d6329c10eb34bba186ffa67161e63fa6cdae7776ff76df73f"}, + {file = "grpcio-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:6696ffe440333a19d8d128e88d440f91fb92c75a80ce4b44d55800e656a3ef1d"}, + {file = "grpcio-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:e3393b0823f938253370ebef033c9fd23d27f3eae8eb9a8f6264900c7ea3fb5a"}, + {file = "grpcio-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83e7ccb85a74beaeae2634f10eb858a0ed1a63081172649ff4261f929bacfd22"}, + {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:882020c87999d54667a284c7ddf065b359bd00251fcd70279ac486776dbf84ec"}, + {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a10383035e864f386fe096fed5c47d27a2bf7173c56a6e26cffaaa5a361addb1"}, + {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:960edebedc6b9ada1ef58e1c71156f28689978188cd8cff3b646b57288a927d9"}, + {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:23e2e04b83f347d0aadde0c9b616f4726c3d76db04b438fd3904b289a725267f"}, + {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978121758711916d34fe57c1f75b79cdfc73952f1481bb9583399331682d36f7"}, + {file = "grpcio-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9084086190cc6d628f282e5615f987288b95457292e969b9205e45b442276407"}, + {file = "grpcio-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:22bccdd7b23c420a27fd28540fb5dcbc97dc6be105f7698cb0e7d7a420d0e362"}, + {file = "grpcio-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:8999bf1b57172dbc7c3e4bb3c732658e918f5c333b2942243f10d0d653953ba9"}, + {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d9e52558b8b8c2f4ac05ac86344a7417ccdd2b460a59616de49eb6933b07a0bd"}, + {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1714e7bc935780bc3de1b3fcbc7674209adf5208ff825799d579ffd6cd0bd505"}, + {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8842ccbd8c0e253c1f189088228f9b433f7a93b7196b9e5b6f87dba393f5d5d"}, + {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f1e7b36bdff50103af95a80923bf1853f6823dd62f2d2a2524b66ed74103e49"}, + {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bba97b8e8883a8038606480d6b6772289f4c907f6ba780fa1f7b7da7dfd76f06"}, + {file = "grpcio-1.62.1-cp38-cp38-win32.whl", hash = "sha256:a7f615270fe534548112a74e790cd9d4f5509d744dd718cd442bf016626c22e4"}, + {file = "grpcio-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:e6c8c8693df718c5ecbc7babb12c69a4e3677fd11de8886f05ab22d4e6b1c43b"}, + {file = "grpcio-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:73db2dc1b201d20ab7083e7041946910bb991e7e9761a0394bbc3c2632326483"}, + {file = "grpcio-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:407b26b7f7bbd4f4751dbc9767a1f0716f9fe72d3d7e96bb3ccfc4aace07c8de"}, + {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f8de7c8cef9261a2d0a62edf2ccea3d741a523c6b8a6477a340a1f2e417658de"}, + {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd5c8a1af40ec305d001c60236308a67e25419003e9bb3ebfab5695a8d0b369"}, + {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0477cb31da67846a33b1a75c611f88bfbcd427fe17701b6317aefceee1b96f"}, + {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:60dcd824df166ba266ee0cfaf35a31406cd16ef602b49f5d4dfb21f014b0dedd"}, + {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:973c49086cabab773525f6077f95e5a993bfc03ba8fc32e32f2c279497780585"}, + {file = "grpcio-1.62.1-cp39-cp39-win32.whl", hash = "sha256:12859468e8918d3bd243d213cd6fd6ab07208195dc140763c00dfe901ce1e1b4"}, + {file = "grpcio-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7209117bbeebdfa5d898205cc55153a51285757902dd73c47de498ad4d11332"}, + {file = "grpcio-1.62.1.tar.gz", hash = "sha256:6c455e008fa86d9e9a9d85bb76da4277c0d7d9668a3bfa70dbe86e9f3c759947"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.62.1)"] [[package]] name = "grpcio-status" -version = "1.60.0" +version = "1.62.1" description = "Status proto mapping for gRPC" optional = true python-versions = ">=3.6" files = [ - {file = "grpcio-status-1.60.0.tar.gz", hash = "sha256:f10e0b6db3adc0fdc244b71962814ee982996ef06186446b5695b9fa635aa1ab"}, - {file = "grpcio_status-1.60.0-py3-none-any.whl", hash = "sha256:7d383fa36e59c1e61d380d91350badd4d12ac56e4de2c2b831b050362c3c572e"}, + {file = "grpcio-status-1.62.1.tar.gz", hash = "sha256:3431c8abbab0054912c41df5c72f03ddf3b7a67be8a287bb3c18a3456f96ff77"}, + {file = "grpcio_status-1.62.1-py3-none-any.whl", hash = "sha256:af0c3ab85da31669f21749e8d53d669c061ebc6ce5637be49a46edcb7aa8ab17"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.60.0" +grpcio = ">=1.62.1" protobuf = ">=4.21.6" [[package]] @@ -1134,22 +1122,67 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "7.0.2" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, + {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "influxdb" +version = "5.3.1" +description = "InfluxDB client" +optional = true +python-versions = "*" +files = [ + {file = "influxdb-5.3.1-py2.py3-none-any.whl", hash = "sha256:65040a1f53d1a2a4f88a677e89e3a98189a7d30cf2ab61c318aaa89733280747"}, + {file = "influxdb-5.3.1.tar.gz", hash = "sha256:46f85e7b04ee4b3dee894672be6a295c94709003a7ddea8820deec2ac4d8b27a"}, +] + +[package.dependencies] +msgpack = "*" +python-dateutil = ">=2.6.0" +pytz = "*" +requests = ">=2.17.0" +six = ">=1.10.0" + +[package.extras] +test = ["mock", "nose", "nose-cov", "requests-mock"] + +[[package]] +name = "influxdb-client" +version = "1.41.0" +description = "InfluxDB 2.0 Python client library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "influxdb_client-1.41.0-py3-none-any.whl", hash = "sha256:420d7c5b7ed0f778de0eac923aded3ea3a4eb6b247e3fbb7a187e0a577a5a3be"}, + {file = "influxdb_client-1.41.0.tar.gz", hash = "sha256:4b85bad3991f3de24818366c87c8868a64917fea2d21bbcc2b579fbe5d904990"}, +] + +[package.dependencies] +certifi = ">=14.05.14" +python-dateutil = ">=2.5.3" +reactivex = ">=4.0.4" +setuptools = ">=21.0.0" +urllib3 = ">=1.26.0" + +[package.extras] +async = ["aiocsv (>=1.2.2)", "aiohttp (>=3.8.1)"] +ciso = ["ciso8601 (>=2.1.1)"] +extra = ["numpy", "pandas (>=1.0.0)"] +test = ["aioresponses (>=0.7.3)", "coverage (>=4.0.3)", "flake8 (>=5.0.3)", "httpretty (==1.0.5)", "jinja2 (==3.1.3)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "psutil (>=5.6.3)", "py (>=1.4.31)", "pytest (>=5.0.0)", "pytest-cov (>=3.0.0)", "pytest-timeout (>=2.1.0)", "randomize (>=0.13)", "sphinx (==1.8.5)", "sphinx-rtd-theme"] [[package]] name = "iniconfig" @@ -1237,6 +1270,21 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "jwcrypto" +version = "1.5.6" +description = "Implementation of JOSE Web standards" +optional = true +python-versions = ">= 3.8" +files = [ + {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, + {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, +] + +[package.dependencies] +cryptography = ">=3.4" +typing-extensions = ">=4.5.0" + [[package]] name = "kafka-python" version = "2.0.2" @@ -1253,13 +1301,13 @@ crc32c = ["crc32c"] [[package]] name = "keyring" -version = "24.3.0" +version = "24.3.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, - {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, + {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, + {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, ] [package.dependencies] @@ -1272,7 +1320,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "kubernetes" @@ -1326,71 +1374,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.4" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, - {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -1406,13 +1454,13 @@ files = [ [[package]] name = "minio" -version = "7.2.3" +version = "7.2.5" description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" optional = true python-versions = "*" files = [ - {file = "minio-7.2.3-py3-none-any.whl", hash = "sha256:e6b5ce0a9b4368da50118c3f0c4df5dbf33885d44d77fce6c0aa1c485e6af7a1"}, - {file = "minio-7.2.3.tar.gz", hash = "sha256:4971dfb1a71eeefd38e1ce2dc7edc4e6eb0f07f1c1d6d70c15457e3280cfc4b9"}, + {file = "minio-7.2.5-py3-none-any.whl", hash = "sha256:ed9176c96d4271cb1022b9ecb8a538b1e55b32ae06add6de16425cab99ef2304"}, + {file = "minio-7.2.5.tar.gz", hash = "sha256:59d8906e2da248a9caac34d4958a859cc3a44abbe6447910c82b5abfa9d6a2e1"}, ] [package.dependencies] @@ -1433,6 +1481,72 @@ files = [ {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, ] +[[package]] +name = "msgpack" +version = "1.0.8" +description = "MessagePack serializer" +optional = true +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, +] + [[package]] name = "mypy" version = "1.7.1" @@ -1493,12 +1607,12 @@ files = [ [[package]] name = "neo4j" -version = "5.16.0" +version = "5.18.0" description = "Neo4j Bolt driver for Python" optional = true python-versions = ">=3.7" files = [ - {file = "neo4j-5.16.0.tar.gz", hash = "sha256:3d04334f5f99dc06c8150e75f2d608a560789ef35670494ecdcec31c0af276a9"}, + {file = "neo4j-5.18.0.tar.gz", hash = "sha256:4014406ae5b8b485a8ba46c9f00b6f5b4aaf88e7c3a50603445030c2aab701c9"}, ] [package.dependencies] @@ -1604,24 +1718,24 @@ attrs = ">=19.2.0" [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "pg8000" -version = "1.30.4" +version = "1.30.5" description = "PostgreSQL interface library" optional = false python-versions = ">=3.8" files = [ - {file = "pg8000-1.30.4-py3-none-any.whl", hash = "sha256:64bbe27b11588a53cee08e840988416227263dc5191b649fab963949f3ddd84d"}, - {file = "pg8000-1.30.4.tar.gz", hash = "sha256:2fa6964fff591a5e076fa6dd21a317c74de2caaa52991bb1f8b3d8ef2e56d172"}, + {file = "pg8000-1.30.5-py3-none-any.whl", hash = "sha256:1abf18da652b0ad8e9cbfe57ed841c350b5330c33d8151303555db1fe5ce57f8"}, + {file = "pg8000-1.30.5.tar.gz", hash = "sha256:072f7ad00cd723695cb2e9fc02c1dfb84c781455e97b8de6f4c4281eea08078c"}, ] [package.dependencies] @@ -1646,17 +1760,17 @@ twisted = ["twisted"] [[package]] name = "pkginfo" -version = "1.9.6" +version = "1.10.0" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.6" files = [ - {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, - {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, + {file = "pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097"}, + {file = "pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297"}, ] [package.extras] -testing = ["pytest", "pytest-cov"] +testing = ["pytest", "pytest-cov", "wheel"] [[package]] name = "platformdirs" @@ -1725,22 +1839,22 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] [[package]] name = "protobuf" -version = "4.25.2" +version = "4.25.3" description = "" optional = true python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, - {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, - {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, - {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, - {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, - {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, - {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, - {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, - {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, ] [[package]] @@ -1935,93 +2049,93 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pymongo" -version = "4.6.1" +version = "4.6.2" description = "Python driver for MongoDB " optional = true python-versions = ">=3.7" files = [ - {file = "pymongo-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4344c30025210b9fa80ec257b0e0aab5aa1d5cca91daa70d82ab97b482cc038e"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:1c5654bb8bb2bdb10e7a0bc3c193dd8b49a960b9eebc4381ff5a2043f4c3c441"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:eaf2f65190c506def2581219572b9c70b8250615dc918b3b7c218361a51ec42e"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:262356ea5fcb13d35fb2ab6009d3927bafb9504ef02339338634fffd8a9f1ae4"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:2dd2f6960ee3c9360bed7fb3c678be0ca2d00f877068556785ec2eb6b73d2414"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:ff925f1cca42e933376d09ddc254598f8c5fcd36efc5cac0118bb36c36217c41"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:3cadf7f4c8e94d8a77874b54a63c80af01f4d48c4b669c8b6867f86a07ba994f"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55dac73316e7e8c2616ba2e6f62b750918e9e0ae0b2053699d66ca27a7790105"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:154b361dcb358ad377d5d40df41ee35f1cc14c8691b50511547c12404f89b5cb"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2940aa20e9cc328e8ddeacea8b9a6f5ddafe0b087fedad928912e787c65b4909"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:010bc9aa90fd06e5cc52c8fac2c2fd4ef1b5f990d9638548dde178005770a5e8"}, - {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e470fa4bace5f50076c32f4b3cc182b31303b4fefb9b87f990144515d572820b"}, - {file = "pymongo-4.6.1-cp310-cp310-win32.whl", hash = "sha256:da08ea09eefa6b960c2dd9a68ec47949235485c623621eb1d6c02b46765322ac"}, - {file = "pymongo-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:13d613c866f9f07d51180f9a7da54ef491d130f169e999c27e7633abe8619ec9"}, - {file = "pymongo-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a0ae7a48a6ef82ceb98a366948874834b86c84e288dbd55600c1abfc3ac1d88"}, - {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd94c503271e79917b27c6e77f7c5474da6930b3fb9e70a12e68c2dff386b9a"}, - {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d4ccac3053b84a09251da8f5350bb684cbbf8c8c01eda6b5418417d0a8ab198"}, - {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:349093675a2d3759e4fb42b596afffa2b2518c890492563d7905fac503b20daa"}, - {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88beb444fb438385e53dc9110852910ec2a22f0eab7dd489e827038fdc19ed8d"}, - {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8e62d06e90f60ea2a3d463ae51401475568b995bafaffd81767d208d84d7bb1"}, - {file = "pymongo-4.6.1-cp311-cp311-win32.whl", hash = "sha256:5556e306713e2522e460287615d26c0af0fe5ed9d4f431dad35c6624c5d277e9"}, - {file = "pymongo-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:b10d8cda9fc2fcdcfa4a000aa10413a2bf8b575852cd07cb8a595ed09689ca98"}, - {file = "pymongo-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b435b13bb8e36be11b75f7384a34eefe487fe87a6267172964628e2b14ecf0a7"}, - {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e438417ce1dc5b758742e12661d800482200b042d03512a8f31f6aaa9137ad40"}, - {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b47ebd89e69fbf33d1c2df79759d7162fc80c7652dacfec136dae1c9b3afac7"}, - {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbed8cccebe1169d45cedf00461b2842652d476d2897fd1c42cf41b635d88746"}, - {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30a9e06041fbd7a7590693ec5e407aa8737ad91912a1e70176aff92e5c99d20"}, - {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8729dbf25eb32ad0dc0b9bd5e6a0d0b7e5c2dc8ec06ad171088e1896b522a74"}, - {file = "pymongo-4.6.1-cp312-cp312-win32.whl", hash = "sha256:3177f783ae7e08aaf7b2802e0df4e4b13903520e8380915e6337cdc7a6ff01d8"}, - {file = "pymongo-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:00c199e1c593e2c8b033136d7a08f0c376452bac8a896c923fcd6f419e07bdd2"}, - {file = "pymongo-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6dcc95f4bb9ed793714b43f4f23a7b0c57e4ef47414162297d6f650213512c19"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:13552ca505366df74e3e2f0a4f27c363928f3dff0eef9f281eb81af7f29bc3c5"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:77e0df59b1a4994ad30c6d746992ae887f9756a43fc25dec2db515d94cf0222d"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3a7f02a58a0c2912734105e05dedbee4f7507e6f1bd132ebad520be0b11d46fd"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:026a24a36394dc8930cbcb1d19d5eb35205ef3c838a7e619e04bd170713972e7"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:3b287e814a01deddb59b88549c1e0c87cefacd798d4afc0c8bd6042d1c3d48aa"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:9a710c184ba845afb05a6f876edac8f27783ba70e52d5eaf939f121fc13b2f59"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:30b2c9caf3e55c2e323565d1f3b7e7881ab87db16997dc0cbca7c52885ed2347"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff62ba8ff70f01ab4fe0ae36b2cb0b5d1f42e73dfc81ddf0758cd9f77331ad25"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:547dc5d7f834b1deefda51aedb11a7af9c51c45e689e44e14aa85d44147c7657"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1de3c6faf948f3edd4e738abdb4b76572b4f4fdfc1fed4dad02427e70c5a6219"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2831e05ce0a4df10c4ac5399ef50b9a621f90894c2a4d2945dc5658765514ed"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:144a31391a39a390efce0c5ebcaf4bf112114af4384c90163f402cec5ede476b"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33bb16a07d3cc4e0aea37b242097cd5f7a156312012455c2fa8ca396953b11c4"}, - {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b7b1a83ce514700276a46af3d9e481ec381f05b64939effc9065afe18456a6b9"}, - {file = "pymongo-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:3071ec998cc3d7b4944377e5f1217c2c44b811fae16f9a495c7a1ce9b42fb038"}, - {file = "pymongo-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2346450a075625c4d6166b40a013b605a38b6b6168ce2232b192a37fb200d588"}, - {file = "pymongo-4.6.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:061598cbc6abe2f382ab64c9caa83faa2f4c51256f732cdd890bcc6e63bfb67e"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d483793a384c550c2d12cb794ede294d303b42beff75f3b3081f57196660edaf"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f9756f1d25454ba6a3c2f1ef8b7ddec23e5cdeae3dc3c3377243ae37a383db00"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ed23b0e2dac6f84f44c8494fbceefe6eb5c35db5c1099f56ab78fc0d94ab3af"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3d18a9b9b858ee140c15c5bfcb3e66e47e2a70a03272c2e72adda2482f76a6ad"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:c258dbacfff1224f13576147df16ce3c02024a0d792fd0323ac01bed5d3c545d"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:f7acc03a4f1154ba2643edeb13658d08598fe6e490c3dd96a241b94f09801626"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:76013fef1c9cd1cd00d55efde516c154aa169f2bf059b197c263a255ba8a9ddf"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0e6a6c807fa887a0c51cc24fe7ea51bb9e496fe88f00d7930063372c3664c3"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd1fa413f8b9ba30140de198e4f408ffbba6396864c7554e0867aa7363eb58b2"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d219b4508f71d762368caec1fc180960569766049bbc4d38174f05e8ef2fe5b"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b81ecf18031998ad7db53b960d1347f8f29e8b7cb5ea7b4394726468e4295e"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56816e43c92c2fa8c11dc2a686f0ca248bea7902f4a067fa6cbc77853b0f041e"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef801027629c5b511cf2ba13b9be29bfee36ae834b2d95d9877818479cdc99ea"}, - {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d4c2be9760b112b1caf649b4977b81b69893d75aa86caf4f0f398447be871f3c"}, - {file = "pymongo-4.6.1-cp38-cp38-win32.whl", hash = "sha256:39d77d8bbb392fa443831e6d4ae534237b1f4eee6aa186f0cdb4e334ba89536e"}, - {file = "pymongo-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:4497d49d785482cc1a44a0ddf8830b036a468c088e72a05217f5b60a9e025012"}, - {file = "pymongo-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:69247f7a2835fc0984bbf0892e6022e9a36aec70e187fcfe6cae6a373eb8c4de"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7bb0e9049e81def6829d09558ad12d16d0454c26cabe6efc3658e544460688d9"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a1810c2cbde714decf40f811d1edc0dae45506eb37298fd9d4247b8801509fe"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e2aced6fb2f5261b47d267cb40060b73b6527e64afe54f6497844c9affed5fd0"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d0355cff58a4ed6d5e5f6b9c3693f52de0784aa0c17119394e2a8e376ce489d4"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:3c74f4725485f0a7a3862cfd374cc1b740cebe4c133e0c1425984bcdcce0f4bb"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:9c79d597fb3a7c93d7c26924db7497eba06d58f88f58e586aa69b2ad89fee0f8"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8ec75f35f62571a43e31e7bd11749d974c1b5cd5ea4a8388725d579263c0fdf6"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e641f931c5cd95b376fd3c59db52770e17bec2bf86ef16cc83b3906c054845"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aafd036f6f2e5ad109aec92f8dbfcbe76cff16bad683eb6dd18013739c0b3ae"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f2b856518bfcfa316c8dae3d7b412aecacf2e8ba30b149f5eb3b63128d703b9"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec31adc2e988fd7db3ab509954791bbc5a452a03c85e45b804b4bfc31fa221d"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9167e735379ec43d8eafa3fd675bfbb12e2c0464f98960586e9447d2cf2c7a83"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1461199b07903fc1424709efafe379205bf5f738144b1a50a08b0396357b5abf"}, - {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3094c7d2f820eecabadae76bfec02669567bbdd1730eabce10a5764778564f7b"}, - {file = "pymongo-4.6.1-cp39-cp39-win32.whl", hash = "sha256:c91ea3915425bd4111cb1b74511cdc56d1d16a683a48bf2a5a96b6a6c0f297f7"}, - {file = "pymongo-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ef102a67ede70e1721fe27f75073b5314911dbb9bc27cde0a1c402a11531e7bd"}, - {file = "pymongo-4.6.1.tar.gz", hash = "sha256:31dab1f3e1d0cdd57e8df01b645f52d43cc1b653ed3afd535d2891f4fc4f9712"}, + {file = "pymongo-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7640d176ee5b0afec76a1bda3684995cb731b2af7fcfd7c7ef8dc271c5d689af"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux1_i686.whl", hash = "sha256:4e2129ec8f72806751b621470ac5d26aaa18fae4194796621508fa0e6068278a"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:c43205e85cbcbdf03cff62ad8f50426dd9d20134a915cfb626d805bab89a1844"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:91ddf95cedca12f115fbc5f442b841e81197d85aa3cc30b82aee3635a5208af2"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:0fbdbf2fba1b4f5f1522e9f11e21c306e095b59a83340a69e908f8ed9b450070"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:097791d5a8d44e2444e0c8c4d6e14570ac11e22bcb833808885a5db081c3dc2a"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:e0b208ebec3b47ee78a5c836e2e885e8c1e10f8ffd101aaec3d63997a4bdcd04"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1849fd6f1917b4dc5dbf744b2f18e41e0538d08dd8e9ba9efa811c5149d665a3"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa0bbbfbd1f8ebbd5facaa10f9f333b20027b240af012748555148943616fdf3"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4522ad69a4ab0e1b46a8367d62ad3865b8cd54cf77518c157631dac1fdc97584"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397949a9cc85e4a1452f80b7f7f2175d557237177120954eff00bf79553e89d3"}, + {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d511db310f43222bc58d811037b176b4b88dc2b4617478c5ef01fea404f8601"}, + {file = "pymongo-4.6.2-cp310-cp310-win32.whl", hash = "sha256:991e406db5da4d89fb220a94d8caaf974ffe14ce6b095957bae9273c609784a0"}, + {file = "pymongo-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:94637941fe343000f728e28d3fe04f1f52aec6376b67b85583026ff8dab2a0e0"}, + {file = "pymongo-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84593447a5c5fe7a59ba86b72c2c89d813fbac71c07757acdf162fbfd5d005b9"}, + {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aebddb2ec2128d5fc2fe3aee6319afef8697e0374f8a1fcca3449d6f625e7b4"}, + {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f706c1a644ed33eaea91df0a8fb687ce572b53eeb4ff9b89270cb0247e5d0e1"}, + {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18c422e6b08fa370ed9d8670c67e78d01f50d6517cec4522aa8627014dfa38b6"}, + {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d002ae456a15b1d790a78bb84f87af21af1cb716a63efb2c446ab6bcbbc48ca"}, + {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f86ba0c781b497a3c9c886765d7b6402a0e3ae079dd517365044c89cd7abb06"}, + {file = "pymongo-4.6.2-cp311-cp311-win32.whl", hash = "sha256:ac20dd0c7b42555837c86f5ea46505f35af20a08b9cf5770cd1834288d8bd1b4"}, + {file = "pymongo-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:e78af59fd0eb262c2a5f7c7d7e3b95e8596a75480d31087ca5f02f2d4c6acd19"}, + {file = "pymongo-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6125f73503407792c8b3f80165f8ab88a4e448d7d9234c762681a4d0b446fcb4"}, + {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba052446a14bd714ec83ca4e77d0d97904f33cd046d7bb60712a6be25eb31dbb"}, + {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b65433c90e07dc252b4a55dfd885ca0df94b1cf77c5b8709953ec1983aadc03"}, + {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2160d9c8cd20ce1f76a893f0daf7c0d38af093f36f1b5c9f3dcf3e08f7142814"}, + {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f251f287e6d42daa3654b686ce1fcb6d74bf13b3907c3ae25954978c70f2cd4"}, + {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d227a60b00925dd3aeae4675575af89c661a8e89a1f7d1677e57eba4a3693c"}, + {file = "pymongo-4.6.2-cp312-cp312-win32.whl", hash = "sha256:311794ef3ccae374aaef95792c36b0e5c06e8d5cf04a1bdb1b2bf14619ac881f"}, + {file = "pymongo-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:f673b64a0884edcc56073bda0b363428dc1bf4eb1b5e7d0b689f7ec6173edad6"}, + {file = "pymongo-4.6.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:fe010154dfa9e428bd2fb3e9325eff2216ab20a69ccbd6b5cac6785ca2989161"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f5f4cd2969197e25b67e24d5b8aa2452d381861d2791d06c493eaa0b9c9fcfe"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c9519c9d341983f3a1bd19628fecb1d72a48d8666cf344549879f2e63f54463b"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c68bf4a399e37798f1b5aa4f6c02886188ef465f4ac0b305a607b7579413e366"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:a509db602462eb736666989739215b4b7d8f4bb8ac31d0bffd4be9eae96c63ef"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:362a5adf6f3f938a8ff220a4c4aaa93e84ef932a409abecd837c617d17a5990f"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:ee30a9d4c27a88042d0636aca0275788af09cc237ae365cd6ebb34524bddb9cc"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:477914e13501bb1d4608339ee5bb618be056d2d0e7267727623516cfa902e652"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd343ca44982d480f1e39372c48e8e263fc6f32e9af2be456298f146a3db715"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3797e0a628534e07a36544d2bfa69e251a578c6d013e975e9e3ed2ac41f2d95"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97d81d357e1a2a248b3494d52ebc8bf15d223ee89d59ee63becc434e07438a24"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed694c0d1977cb54281cb808bc2b247c17fb64b678a6352d3b77eb678ebe1bd9"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ceaaff4b812ae368cf9774989dea81b9bbb71e5bed666feca6a9f3087c03e49"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7dd63f7c2b3727541f7f37d0fb78d9942eb12a866180fbeb898714420aad74e2"}, + {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e571434633f99a81e081738721bb38e697345281ed2f79c2f290f809ba3fbb2f"}, + {file = "pymongo-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:3e9f6e2f3da0a6af854a3e959a6962b5f8b43bbb8113cd0bff0421c5059b3106"}, + {file = "pymongo-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3a5280f496297537301e78bde250c96fadf4945e7b2c397d8bb8921861dd236d"}, + {file = "pymongo-4.6.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:5f6bcd2d012d82d25191a911a239fd05a8a72e8c5a7d81d056c0f3520cad14d1"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4fa30494601a6271a8b416554bd7cde7b2a848230f0ec03e3f08d84565b4bf8c"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bea62f03a50f363265a7a651b4e2a4429b4f138c1864b2d83d4bf6f9851994be"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b2d445f1cf147331947cc35ec10342f898329f29dd1947a3f8aeaf7e0e6878d1"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:5db133d6ec7a4f7fc7e2bd098e4df23d7ad949f7be47b27b515c9fb9301c61e4"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:9eec7140cf7513aa770ea51505d312000c7416626a828de24318fdcc9ac3214c"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:5379ca6fd325387a34cda440aec2bd031b5ef0b0aa2e23b4981945cff1dab84c"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:579508536113dbd4c56e4738955a18847e8a6c41bf3c0b4ab18b51d81a6b7be8"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bae553ca39ed52db099d76acd5e8566096064dc7614c34c9359bb239ec4081"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0257e0eebb50f242ca28a92ef195889a6ad03dcdde5bf1c7ab9f38b7e810801"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbafe3a1df21eeadb003c38fc02c1abf567648b6477ec50c4a3c042dca205371"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaecfafb407feb6f562c7f2f5b91f22bfacba6dd739116b1912788cff7124c4a"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e942945e9112075a84d2e2d6e0d0c98833cdcdfe48eb8952b917f996025c7ffa"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f7b98f8d2cf3eeebde738d080ae9b4276d7250912d9751046a9ac1efc9b1ce2"}, + {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8110b78fc4b37dced85081d56795ecbee6a7937966e918e05e33a3900e8ea07d"}, + {file = "pymongo-4.6.2-cp38-cp38-win32.whl", hash = "sha256:df813f0c2c02281720ccce225edf39dc37855bf72cdfde6f789a1d1cf32ffb4b"}, + {file = "pymongo-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:64ec3e2dcab9af61bdbfcb1dd863c70d1b0c220b8e8ac11df8b57f80ee0402b3"}, + {file = "pymongo-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bff601fbfcecd2166d9a2b70777c2985cb9689e2befb3278d91f7f93a0456cae"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f1febca6f79e91feafc572906871805bd9c271b6a2d98a8bb5499b6ace0befed"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d788cb5cc947d78934be26eef1623c78cec3729dc93a30c23f049b361aa6d835"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c2f258489de12a65b81e1b803a531ee8cf633fa416ae84de65cd5f82d2ceb37"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:fb24abcd50501b25d33a074c1790a1389b6460d2509e4b240d03fd2e5c79f463"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:4d982c6db1da7cf3018183891883660ad085de97f21490d314385373f775915b"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:b2dd8c874927a27995f64a3b44c890e8a944c98dec1ba79eab50e07f1e3f801b"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:4993593de44c741d1e9f230f221fe623179f500765f9855936e4ff6f33571bad"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:658f6c028edaeb02761ebcaca8d44d519c22594b2a51dcbc9bd2432aa93319e3"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68109c13176749fbbbbbdb94dd4a58dcc604db6ea43ee300b2602154aebdd55f"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:707d28a822b918acf941cff590affaddb42a5d640614d71367c8956623a80cbc"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f251db26c239aec2a4d57fbe869e0a27b7f6b5384ec6bf54aeb4a6a5e7408234"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57c05f2e310701fc17ae358caafd99b1830014e316f0242d13ab6c01db0ab1c2"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b575fbe6396bbf21e4d0e5fd2e3cdb656dc90c930b6c5532192e9a89814f72d"}, + {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ca5877754f3fa6e4fe5aacf5c404575f04c2d9efc8d22ed39576ed9098d555c8"}, + {file = "pymongo-4.6.2-cp39-cp39-win32.whl", hash = "sha256:8caa73fb19070008e851a589b744aaa38edd1366e2487284c61158c77fdf72af"}, + {file = "pymongo-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:3e03c732cb64b96849310e1d8688fb70d75e2571385485bf2f1e7ad1d309fa53"}, + {file = "pymongo-4.6.2.tar.gz", hash = "sha256:ab7d01ac832a1663dad592ccbd92bb0f0775bc8f98a1923c5e1a7d7fead495af"}, ] [package.dependencies] @@ -2208,53 +2322,32 @@ dev = ["black (>=22.3.0)", "flake8 (>=4.0.1)", "isort (>=5.10.1)", "mock", "mypy [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] six = ">=1.5" -[[package]] -name = "python-jose" -version = "3.3.0" -description = "JOSE implementation in Python" -optional = true -python-versions = "*" -files = [ - {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, - {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, -] - -[package.dependencies] -ecdsa = "!=0.15" -pyasn1 = "*" -rsa = "*" - -[package.extras] -cryptography = ["cryptography (>=3.4.0)"] -pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] -pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] - [[package]] name = "python-keycloak" -version = "3.7.0" +version = "3.9.1" description = "python-keycloak is a Python package providing access to the Keycloak API." optional = true python-versions = ">=3.8,<4.0" files = [ - {file = "python_keycloak-3.7.0-py3-none-any.whl", hash = "sha256:92aa0a7e965cc5422d335c36efa0519f3188d9b8048cc8083f8f6e23c13178a5"}, - {file = "python_keycloak-3.7.0.tar.gz", hash = "sha256:29eee9490ba354af81fcdf86ec81d840515d8b53002de831715e05d07298886a"}, + {file = "python_keycloak-3.9.1-py3-none-any.whl", hash = "sha256:898d1fc73560171d3870251f981e069f854cc67bc0a51b96703355512d8d3cf3"}, + {file = "python_keycloak-3.9.1.tar.gz", hash = "sha256:50c8073172ca0630f3569c6b631134216b60f4e347cc5bb669a57e6ffba50f7e"}, ] [package.dependencies] deprecation = ">=2.1.0" -python-jose = ">=3.3.0" +jwcrypto = ">=1.5.4,<2.0.0" requests = ">=2.20.0" requests-toolbelt = ">=0.6.0" @@ -2263,13 +2356,13 @@ docs = ["Sphinx (>=6.1.0,<7.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (> [[package]] name = "pytz" -version = "2023.3.post1" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = true python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -2331,7 +2424,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2366,15 +2458,29 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "reactivex" +version = "4.0.4" +description = "ReactiveX (Rx) for Python" +optional = true +python-versions = ">=3.7,<4.0" +files = [ + {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"}, + {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"}, +] + +[package.dependencies] +typing-extensions = ">=4.1.1,<5.0.0" + [[package]] name = "readme-renderer" -version = "42.0" +version = "43.0" description = "readme_renderer is a library for rendering readme descriptions for Warehouse" optional = false python-versions = ">=3.8" files = [ - {file = "readme_renderer-42.0-py3-none-any.whl", hash = "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d"}, - {file = "readme_renderer-42.0.tar.gz", hash = "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1"}, + {file = "readme_renderer-43.0-py3-none-any.whl", hash = "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9"}, + {file = "readme_renderer-43.0.tar.gz", hash = "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311"}, ] [package.dependencies] @@ -2387,17 +2493,17 @@ md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "redis" -version = "5.0.1" +version = "5.0.3" description = "Python client for Redis database and key-value store" optional = true python-versions = ">=3.7" files = [ - {file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"}, - {file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"}, + {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, + {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, ] [package.dependencies] -async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} [package.extras] hiredis = ["hiredis (>=1.0.0)"] @@ -2426,13 +2532,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" -version = "1.3.1" +version = "1.4.0" description = "OAuthlib authentication support for Requests." optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, - {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, + {file = "requests-oauthlib-1.4.0.tar.gz", hash = "sha256:acee623221e4a39abcbb919312c8ff04bd44e7e417087fb4bd5e2a2f53d5e79a"}, + {file = "requests_oauthlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:7a3130d94a17520169e38db6c8d75f2c974643788465ecc2e4b36d288bf13033"}, ] [package.dependencies] @@ -2472,13 +2578,13 @@ idna2008 = ["idna"] [[package]] name = "rich" -version = "13.7.0" +version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] [package.dependencies] @@ -2550,13 +2656,13 @@ jeepney = ">=0.6" [[package]] name = "selenium" -version = "4.17.2" +version = "4.18.1" description = "" optional = true python-versions = ">=3.8" files = [ - {file = "selenium-4.17.2-py3-none-any.whl", hash = "sha256:5aee79026c07985dc1b0c909f34084aa996dfe5b307602de9016d7a621a473f2"}, - {file = "selenium-4.17.2.tar.gz", hash = "sha256:d43d6972e516855fb242ef9ce4ce759057b115070e702e7b1c1032fe7b38d87b"}, + {file = "selenium-4.18.1-py3-none-any.whl", hash = "sha256:b24a3cdd2d47c29832e81345bfcde0c12bb608738013e53c781b211b418df241"}, + {file = "selenium-4.18.1.tar.gz", hash = "sha256:a11f67afa8bfac6b77e148c987b33f6b14eb1cae4d352722a75de1f26e3f0ae2"}, ] [package.dependencies] @@ -2568,19 +2674,19 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} [[package]] name = "setuptools" -version = "69.0.3" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -2595,13 +2701,13 @@ files = [ [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -2757,60 +2863,56 @@ test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.25" +version = "2.0.28" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, - {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, - {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, + {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] @@ -2913,24 +3015,24 @@ urllib3 = ">=1.26.0" [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] name = "tzdata" -version = "2023.4" +version = "2024.1" description = "Provider of IANA time zone data" optional = true python-versions = ">=2" files = [ - {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, - {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] @@ -3139,6 +3241,7 @@ azurite = ["azure-storage-blob"] clickhouse = ["clickhouse-driver"] elasticsearch = [] google = ["google-cloud-pubsub"] +influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] kafka = ["kafka-python"] keycloak = ["python-keycloak"] @@ -3159,4 +3262,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "9d1a3bebfdad61d5be71944fd7f5a49462cbcc74ae3e0a9cf89aff0c01b0bb8f" +content-hash = "55534a498e0ae69beb3eba43e055910e656f67de80ec72400c4a535f91b2be56" diff --git a/pyproject.toml b/pyproject.toml index 4d310465e..b9ac9d7c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ packages = [ { include = "testcontainers", from = "modules/clickhouse" }, { include = "testcontainers", from = "modules/elasticsearch" }, { include = "testcontainers", from = "modules/google" }, + { include = "testcontainers", from = "modules/influxdb" }, { include = "testcontainers", from = "modules/k3s" }, { include = "testcontainers", from = "modules/kafka" }, { include = "testcontainers", from = "modules/keycloak" }, @@ -65,6 +66,8 @@ python-arango = { version = "^7.8", optional = true } azure-storage-blob = { version = "^12.19", optional = true } clickhouse-driver = { version = "*", optional = true } google-cloud-pubsub = { version = ">=2", optional = true } +influxdb = { version = "*", optional = true } +influxdb-client = { version = "*", optional = true } kubernetes = { version = "*", optional = true } pyyaml = { version = "*", optional = true } kafka-python = { version = "*", optional = true } @@ -88,6 +91,7 @@ azurite = ["azure-storage-blob"] clickhouse = ["clickhouse-driver"] elasticsearch = [] google = ["google-cloud-pubsub"] +influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] kafka = ["kafka-python"] keycloak = ["python-keycloak"] From d61af383def6eadcd7f2b5ba667eb587c6cc84f1 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 20 Mar 2024 00:12:57 -0400 Subject: [PATCH 312/425] fix(core): raise an exception when docker compose fails to start #258 (#485) see #258 for background Co-authored-by: Gareth Davidson --- core/testcontainers/compose/compose.py | 35 ++++++++++++-------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index e72824bd1..d59e683bf 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -1,9 +1,10 @@ -import subprocess from dataclasses import dataclass, field, fields from functools import cached_property from json import loads from os import PathLike from re import split +from subprocess import CompletedProcess +from subprocess import run as subprocess_run from typing import Callable, Literal, Optional, TypeVar, Union from urllib.error import HTTPError, URLError from urllib.request import urlopen @@ -197,7 +198,7 @@ def start(self) -> None: # pull means running a separate command before starting if self.pull: pull_cmd = [*base_cmd, "pull"] - self._call_command(cmd=pull_cmd) + self._run_command(cmd=pull_cmd) up_cmd = [*base_cmd, "up"] @@ -214,7 +215,7 @@ def start(self) -> None: if self.services: up_cmd.extend(self.services) - self._call_command(cmd=up_cmd) + self._run_command(cmd=up_cmd) def stop(self, down=True) -> None: """ @@ -225,7 +226,7 @@ def stop(self, down=True) -> None: down_cmd += ["down", "--volumes"] else: down_cmd += ["stop"] - self._call_command(cmd=down_cmd) + self._run_command(cmd=down_cmd) def get_logs(self, *services: str) -> tuple[str, str]: """ @@ -239,11 +240,7 @@ def get_logs(self, *services: str) -> tuple[str, str]: """ logs_cmd = [*self.compose_command_property, "logs", *services] - result = subprocess.run( - logs_cmd, - cwd=self.context, - capture_output=True, - ) + result = self._run_command(cmd=logs_cmd) return result.stdout.decode("utf-8"), result.stderr.decode("utf-8") def get_containers(self, include_all=False) -> list[ComposeContainer]: @@ -259,7 +256,7 @@ def get_containers(self, include_all=False) -> list[ComposeContainer]: cmd = [*self.compose_command_property, "ps", "--format", "json"] if include_all: cmd = [*cmd, "-a"] - result = subprocess.run(cmd, cwd=self.context, check=True, stdout=subprocess.PIPE) + result = self._run_command(cmd=cmd) stdout = split(r"\r?\n", result.stdout.decode("utf-8")) containers = [] @@ -322,22 +319,22 @@ def exec_in_container( if not service_name: service_name = self.get_container().Service exec_cmd = [*self.compose_command_property, "exec", "-T", service_name, *command] - result = subprocess.run( - exec_cmd, - cwd=self.context, - capture_output=True, - check=True, - ) + result = self._run_command(cmd=exec_cmd) return (result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode) - def _call_command( + def _run_command( self, cmd: Union[str, list[str]], context: Optional[str] = None, - ) -> None: + ) -> CompletedProcess[bytes]: context = context or self.context - subprocess.call(cmd, cwd=context) + return subprocess_run( + cmd, + capture_output=True, + check=True, + cwd=context, + ) def get_service_port( self, From b10d916848cccc016fc457333f7b382b18a7b3ef Mon Sep 17 00:00:00 2001 From: Dee Moore <117185602+deeninetyone@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:50:03 +0200 Subject: [PATCH 313/425] fix(core): DinD issues #141, #329 (#368) Fix #141 - find IP from custom network if the container is not using the default network Close #329 - This seems fixed in the underlying docker libraries. Improve support for Docker in Docker running on a custom network, by attempting to find the right custom network and use it for new containers. This adds support for using testcontainers-python running the GitHub Actions Runner Controller to run self-hosted actions runners on prem, when you run your workflows in containers. --------- Co-authored-by: Dee Moore Co-authored-by: David Ankin Co-authored-by: Balint Bartha <39852431+totallyzen@users.noreply.github.com> --- core/testcontainers/core/docker_client.py | 54 +++++++++++++++++-- core/tests/test_docker_in_docker.py | 63 ++++++++++++++++++++--- 2 files changed, 107 insertions(+), 10 deletions(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 9c1ea485e..04fdca59a 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -11,8 +11,10 @@ # License for the specific language governing permissions and limitations # under the License. import functools as ft +import ipaddress import os import urllib +import urllib.parse from os.path import exists from pathlib import Path from typing import Optional, Union @@ -34,7 +36,7 @@ class DockerClient: """ def __init__(self, **kwargs) -> None: - docker_host = read_tc_properties().get("tc.host") + docker_host = get_docker_host() if docker_host: LOGGER.info(f"using host {docker_host}") @@ -57,6 +59,12 @@ def run( remove: bool = False, **kwargs, ) -> Container: + # If the user has specified a network, we'll assume the user knows best + if "network" not in kwargs and not get_docker_host(): + # Otherwise we'll try to find the docker host for dind usage. + host_network = self.find_host_network() + if host_network: + kwargs["network"] = host_network container = self.client.containers.run( image, command=command, @@ -71,6 +79,30 @@ def run( ) return container + def find_host_network(self) -> Optional[str]: + """ + Try to find the docker host network. + + :return: The network name if found, None if not set. + """ + # If we're docker in docker running on a custom network, we need to inherit the + # network settings, so we can access the resulting container. + try: + docker_host = ipaddress.IPv4Address(self.host()) + # See if we can find the host on our networks + for network in self.client.networks.list(filters={"type": "custom"}): + if "IPAM" in network.attrs: + for config in network.attrs["IPAM"]["Config"]: + try: + subnet = ipaddress.IPv4Network(config["Subnet"]) + except ipaddress.AddressValueError: + continue + if docker_host in subnet: + return network.name + except ipaddress.AddressValueError: + pass + return None + def port(self, container_id: str, port: int) -> int: """ Lookup the public-facing port that is NAT-ed to :code:`port`. @@ -94,14 +126,26 @@ def bridge_ip(self, container_id: str) -> str: Get the bridge ip address for a container. """ container = self.get_container(container_id) - return container["NetworkSettings"]["Networks"]["bridge"]["IPAddress"] + network_name = self.network_name(container_id) + return container["NetworkSettings"]["Networks"][network_name]["IPAddress"] + + def network_name(self, container_id: str) -> str: + """ + Get the name of the network this container runs on + """ + container = self.get_container(container_id) + name = container["HostConfig"]["NetworkMode"] + if name == "default": + return "bridge" + return name def gateway_ip(self, container_id: str) -> str: """ Get the gateway ip address for a container. """ container = self.get_container(container_id) - return container["NetworkSettings"]["Networks"]["bridge"]["Gateway"] + network_name = self.network_name(container_id) + return container["NetworkSettings"]["Networks"][network_name]["Gateway"] def host(self) -> str: """ @@ -145,3 +189,7 @@ def read_tc_properties() -> dict[str, str]: tuples = [line.split("=") for line in contents.readlines() if "=" in line] settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} return settings + + +def get_docker_host() -> Optional[str]: + return read_tc_properties().get("tc.host") or os.getenv("DOCKER_HOST") diff --git a/core/tests/test_docker_in_docker.py b/core/tests/test_docker_in_docker.py index 95392408d..6a424884b 100644 --- a/core/tests/test_docker_in_docker.py +++ b/core/tests/test_docker_in_docker.py @@ -1,11 +1,28 @@ -import pytest - +import time +import socket from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient from testcontainers.core.waiting_utils import wait_for_logs -@pytest.mark.xfail(reason="https://github.com/docker/docker-py/issues/2717") +def _wait_for_dind_return_ip(client, dind): + # get ip address for DOCKER_HOST + # avoiding DockerContainer class here to prevent code changes affecting the test + docker_host_ip = client.bridge_ip(dind.id) + # Wait for startup + timeout = 10 + start_wait = time.perf_counter() + while True: + try: + with socket.create_connection((docker_host_ip, 2375), timeout=timeout): + break + except ConnectionRefusedError: + if time.perf_counter() - start_wait > timeout: + raise RuntimeError("Docker in docker took longer than 10 seconds to start") + time.sleep(0.01) + return docker_host_ip + + def test_wait_for_logs_docker_in_docker(): # real dind isn't possible (AFAIK) in CI # forwarding the socket to a container port is at least somewhat the same @@ -18,11 +35,38 @@ def test_wait_for_logs_docker_in_docker(): ) not_really_dind.start() + docker_host_ip = _wait_for_dind_return_ip(client, not_really_dind) + docker_host = f"tcp://{docker_host_ip}:2375" - # get ip address for DOCKER_HOST - # avoiding DockerContainer class here to prevent code changes affecting the test - specs = client.get_container(not_really_dind.id) - docker_host_ip = specs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"] + with DockerContainer( + image="hello-world", + docker_client_kw={"environment": {"DOCKER_HOST": docker_host, "DOCKER_CERT_PATH": "", "DOCKER_TLS_VERIFY": ""}}, + ) as container: + assert container.get_container_host_ip() == docker_host_ip + wait_for_logs(container, "Hello from Docker!") + stdout, stderr = container.get_logs() + assert stdout, "There should be something on stdout" + + not_really_dind.stop() + not_really_dind.remove() + + +def test_dind_inherits_network(): + client = DockerClient() + try: + custom_network = client.client.networks.create("custom_network", driver="bridge", check_duplicate=True) + except Exception: + custom_network = client.client.networks.list(names=["custom_network"])[0] + not_really_dind = client.run( + image="alpine/socat", + command="tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock", + volumes={"/var/run/docker.sock": {"bind": "/var/run/docker.sock"}}, + detach=True, + ) + + not_really_dind.start() + + docker_host_ip = _wait_for_dind_return_ip(client, not_really_dind) docker_host = f"tcp://{docker_host_ip}:2375" with DockerContainer( @@ -30,9 +74,14 @@ def test_wait_for_logs_docker_in_docker(): docker_client_kw={"environment": {"DOCKER_HOST": docker_host, "DOCKER_CERT_PATH": "", "DOCKER_TLS_VERIFY": ""}}, ) as container: assert container.get_container_host_ip() == docker_host_ip + # Check the gateways are the same, so they can talk to each other + assert container.get_docker_client().gateway_ip(container.get_wrapped_container().id) == client.gateway_ip( + not_really_dind.id + ) wait_for_logs(container, "Hello from Docker!") stdout, stderr = container.get_logs() assert stdout, "There should be something on stdout" not_really_dind.stop() not_really_dind.remove() + custom_network.remove() From 5758310532b8a8e1303a24bc534fa8aeb0f75eb2 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Thu, 21 Mar 2024 09:45:58 +0100 Subject: [PATCH 314/425] fix(keycloak): tests on aarch64, use image from [jboss -> quay], change supported version [16+ -> 18+] (#480) - jboss/keycloak is discontinued, adapting the official Docker image: https://quay.io/repository/keycloak/keycloak - switched to the latest image version with ARM support fixes https://github.com/testcontainers/testcontainers-python/issues/451 fixes https://github.com/testcontainers/testcontainers-python/issues/483 ![Screenshot 2024-03-15 at 12 42 29](https://github.com/testcontainers/testcontainers-python/assets/13573675/f82b9372-a94e-45c8-a47b-7ddb4c1c6a57) --- .../keycloak/testcontainers/keycloak/__init__.py | 16 ++++++++-------- modules/keycloak/tests/test_keycloak.py | 9 +++------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index 2e8f77383..ff7a64c2a 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -36,20 +36,21 @@ class KeycloakContainer(DockerContainer): def __init__( self, - image="jboss/keycloak:latest", + image="quay.io/keycloak/keycloak:latest", username: Optional[str] = None, password: Optional[str] = None, port: int = 8080, ) -> None: super().__init__(image=image) - self.username = username or os.environ.get("KEYCLOAK_USER", "test") - self.password = password or os.environ.get("KEYCLOAK_PASSWORD", "test") + self.username = username or os.environ.get("KEYCLOAK_ADMIN", "test") + self.password = password or os.environ.get("KEYCLOAK_ADMIN_PASSWORD", "test") self.port = port self.with_exposed_ports(self.port) def _configure(self) -> None: - self.with_env("KEYCLOAK_USER", self.username) - self.with_env("KEYCLOAK_PASSWORD", self.password) + self.with_env("KEYCLOAK_ADMIN", self.username) + self.with_env("KEYCLOAK_ADMIN_PASSWORD", self.password) + self.with_command("start-dev") def get_url(self) -> str: host = self.get_container_host_ip() @@ -58,8 +59,7 @@ def get_url(self) -> str: @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) def _connect(self) -> None: - url = self.get_url() - response = requests.get(f"{url}/auth", timeout=1) + response = requests.get(self.get_url(), timeout=1) response.raise_for_status() def start(self) -> "KeycloakContainer": @@ -70,7 +70,7 @@ def start(self) -> "KeycloakContainer": def get_client(self, **kwargs) -> KeycloakAdmin: default_kwargs = { - "server_url": f"{self.get_url()}/auth/", + "server_url": self.get_url(), "username": self.username, "password": self.password, "realm_name": "master", diff --git a/modules/keycloak/tests/test_keycloak.py b/modules/keycloak/tests/test_keycloak.py index 70eff57cb..f6d29a4e7 100644 --- a/modules/keycloak/tests/test_keycloak.py +++ b/modules/keycloak/tests/test_keycloak.py @@ -1,9 +1,6 @@ -import pytest - from testcontainers.keycloak import KeycloakContainer -@pytest.mark.parametrize("version", ["16.1.1"]) -def test_docker_run_keycloak(version: str): - with KeycloakContainer(f"jboss/keycloak:{version}") as kc: - kc.get_client().users_count() +def test_docker_run_keycloak(): + with KeycloakContainer("quay.io/keycloak/keycloak:24.0.1") as keycloak_admin: + keycloak_admin.get_client().users_count() From c9c6f92348299a2cc04988af8d69a53a23a7c7d5 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Fri, 22 Mar 2024 03:43:30 -0400 Subject: [PATCH 315/425] fix(postgres): doctest (#473) Co-authored-by: Vemund Santi --- modules/postgres/testcontainers/postgres/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index 83354e07e..fd537f92b 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -45,7 +45,7 @@ class PostgresContainer(DbContainer): ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() >>> version - 'PostgreSQL 9.5...' + 'PostgreSQL 16...' """ def __init__( From b3b990159154857239e2fb86da3cb85a6a13ab8e Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 23 Mar 2024 23:22:37 -0400 Subject: [PATCH 316/425] fix: readthedocs build - take 1 (#495) --- .readthedocs.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 5a80deec6..8675d8c42 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,6 +11,9 @@ build: tools: python: "3.10" -python: - install: - - requirements: requirements/3.10.txt + # https://github.com/readthedocs/readthedocs.org/issues/4912#issuecomment-1143587902s + jobs: + post_install: + - pip install poetry==1.7.1 # match version from poetry.lock + - poetry config virtualenvs.create false + - poetry install --all-extras From dfd17814a7fc9ede510ae17569004bd92f2a6fa6 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 23 Mar 2024 23:42:27 -0400 Subject: [PATCH 317/425] fix: read the docs build works again (#496) proof of working build - https://readthedocs.org/projects/testcontainers/builds/23851186/ this is the one i created - "testcontainers" - the one we actually link to is "testcontainers-python" not sure how to update to re-run that one, which is what we want to do (and i will either rename or delete the one i just made). --- conf.py | 2 +- INDEX.rst => index.rst | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename INDEX.rst => index.rst (100%) diff --git a/conf.py b/conf.py index 5887d3a70..5db9477ed 100644 --- a/conf.py +++ b/conf.py @@ -48,7 +48,7 @@ source_suffix = ".rst" # The master toctree document. -master_doc = "INDEX" +master_doc = "index" # General information about the project. project = "testcontainers" diff --git a/INDEX.rst b/index.rst similarity index 100% rename from INDEX.rst rename to index.rst From 1d10c1ca8c8163b8d68338e1d50d0e26d7b0515e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B4=E4=B8=8A=20=E7=9A=93=E7=99=BB?= Date: Sun, 24 Mar 2024 12:54:28 +0900 Subject: [PATCH 318/425] fix(docs): update the non-existent main.yml badge (#493) The .github/workflows/main.yml has already been removed. This caused the badge on the tutorial page not to be displayed, so it has been updated in index.rst (to ci-core.yml) ![image](https://github.com/testcontainers/testcontainers-python/assets/30658134/f6df32ce-aa93-43f5-becc-2dd8ec0322d7) # Target Page https://testcontainers-python.readthedocs.io/en/latest/README.html --------- Co-authored-by: Dave Ankin --- index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.rst b/index.rst index 4e0cd54b9..d6e7ac7d7 100644 --- a/index.rst +++ b/index.rst @@ -1,8 +1,8 @@ testcontainers-python ===================== -.. image:: https://github.com/testcontainers/testcontainers-python/workflows/testcontainers-python/badge.svg - :target: https://github.com/testcontainers/testcontainers-python/actions/workflows/main.yml +.. image:: https://github.com/testcontainers/testcontainers-python/actions/workflows/ci-core.yml/badge.svg + :target: https://github.com/testcontainers/testcontainers-python/actions/workflows/ci-core.yml .. image:: https://img.shields.io/pypi/v/testcontainers.svg :target: https://pypi.python.org/pypi/testcontainers .. image:: https://readthedocs.org/projects/testcontainers-python/badge/?version=latest From cd72f6896db3eb1fd5ea60f9c051cb719568a12f Mon Sep 17 00:00:00 2001 From: kshramt Date: Sun, 24 Mar 2024 20:35:37 +0900 Subject: [PATCH 319/425] fix: Fix the return type of `DockerContainer.get_logs` (#487) --- core/testcontainers/core/container.py | 2 +- core/tests/test_core.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index f0da90bb4..e0f2e9728 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -148,7 +148,7 @@ def get_wrapped_container(self) -> "Container": def get_docker_client(self) -> DockerClient: return self._docker - def get_logs(self) -> tuple[str, str]: + def get_logs(self) -> tuple[bytes, bytes]: if not self._container: raise ContainerStartException("Container should be started before getting logs") return self._container.logs(stderr=False), self._container.logs(stdout=False) diff --git a/core/tests/test_core.py b/core/tests/test_core.py index a00be1f02..4ebe90409 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -28,4 +28,6 @@ def test_can_get_logs(): with DockerContainer("hello-world") as container: wait_for_logs(container, "Hello from Docker!") stdout, stderr = container.get_logs() + assert isinstance(stdout, bytes) + assert isinstance(stderr, bytes) assert stdout, "There should be something on stdout" From 274a4002600ae70662a5785c7a903cf8846b2ffc Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sun, 24 Mar 2024 12:53:21 -0400 Subject: [PATCH 320/425] fix(core): use auto_remove=True with reaper instance (#499) fix #489 , supercede #491, #498 Co-authored-by: Stefan Hoffmeister --- core/testcontainers/core/container.py | 2 +- core/tests/test_ryuk.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index e0f2e9728..42f4de526 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -193,7 +193,7 @@ def _create_instance(cls) -> "Reaper": .with_name(f"testcontainers-ryuk-{SESSION_ID}") .with_exposed_ports(8080) .with_volume_mapping(RYUK_DOCKER_SOCKET, "/var/run/docker.sock", "rw") - .with_kwargs(privileged=RYUK_PRIVILEGED) + .with_kwargs(privileged=RYUK_PRIVILEGED, auto_remove=True) .start() ) wait_for_logs(Reaper._container, r".* Started!") diff --git a/core/tests/test_ryuk.py b/core/tests/test_ryuk.py index 32370ffbc..4f3b431e3 100644 --- a/core/tests/test_ryuk.py +++ b/core/tests/test_ryuk.py @@ -1,9 +1,14 @@ +from contextlib import contextmanager + +import pytest + from testcontainers.core import container from testcontainers.core.container import Reaper from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs +@pytest.mark.skip("invalid test - ryuk logs 'Removed' right before exiting") def test_wait_for_reaper(): container = DockerContainer("hello-world").start() wait_for_logs(container, "Hello from Docker!") @@ -17,8 +22,16 @@ def test_wait_for_reaper(): Reaper.delete_instance() +@contextmanager +def reset_reaper_instance(): + old_value = Reaper._instance + Reaper._instance = None + yield + Reaper._instance = old_value + + def test_container_without_ryuk(monkeypatch): monkeypatch.setattr(container, "RYUK_DISABLED", True) - with DockerContainer("hello-world") as cont: + with reset_reaper_instance(), DockerContainer("hello-world") as cont: wait_for_logs(cont, "Hello from Docker!") assert Reaper._instance is None From e96218907c39c97b57c11c0da22f9550522d42ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 12:54:07 -0400 Subject: [PATCH 321/425] chore(main): release testcontainers 4.2.0 (#484) :robot: I have created a release *beep* *boop* --- ## [4.2.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.1.0...testcontainers-v4.2.0) (2024-03-24) ### Features * support influxdb ([#413](https://github.com/testcontainers/testcontainers-python/issues/413)) ([13742a5](https://github.com/testcontainers/testcontainers-python/commit/13742a5dc448c80914953c21f8f2b01177c3fa6c)) ### Bug Fixes * **arangodb:** tests to pass on ARM CPUs - change default image to 3.11.x where ARM image is published ([#479](https://github.com/testcontainers/testcontainers-python/issues/479)) ([7b58a50](https://github.com/testcontainers/testcontainers-python/commit/7b58a50f3a8703c5d5e974a4ff20bc8e52ae93c8)) * **core:** DinD issues [#141](https://github.com/testcontainers/testcontainers-python/issues/141), [#329](https://github.com/testcontainers/testcontainers-python/issues/329) ([#368](https://github.com/testcontainers/testcontainers-python/issues/368)) ([b10d916](https://github.com/testcontainers/testcontainers-python/commit/b10d916848cccc016fc457333f7b382b18a7b3ef)) * **core:** raise an exception when docker compose fails to start [#258](https://github.com/testcontainers/testcontainers-python/issues/258) ([#485](https://github.com/testcontainers/testcontainers-python/issues/485)) ([d61af38](https://github.com/testcontainers/testcontainers-python/commit/d61af383def6eadcd7f2b5ba667eb587c6cc84f1)) * **core:** use auto_remove=True with reaper instance ([#499](https://github.com/testcontainers/testcontainers-python/issues/499)) ([274a400](https://github.com/testcontainers/testcontainers-python/commit/274a4002600ae70662a5785c7a903cf8846b2ffc)) * **docs:** update the non-existent main.yml badge ([#493](https://github.com/testcontainers/testcontainers-python/issues/493)) ([1d10c1c](https://github.com/testcontainers/testcontainers-python/commit/1d10c1ca8c8163b8d68338e1d50d0e26d7b0515e)) * Fix the return type of `DockerContainer.get_logs` ([#487](https://github.com/testcontainers/testcontainers-python/issues/487)) ([cd72f68](https://github.com/testcontainers/testcontainers-python/commit/cd72f6896db3eb1fd5ea60f9c051cb719568a12f)) * **keycloak:** tests on aarch64, use image from [jboss -> quay], change supported version [16+ -> 18+] ([#480](https://github.com/testcontainers/testcontainers-python/issues/480)) ([5758310](https://github.com/testcontainers/testcontainers-python/commit/5758310532b8a8e1303a24bc534fa8aeb0f75eb2)) * **postgres:** doctest ([#473](https://github.com/testcontainers/testcontainers-python/issues/473)) ([c9c6f92](https://github.com/testcontainers/testcontainers-python/commit/c9c6f92348299a2cc04988af8d69a53a23a7c7d5)) * read the docs build works again ([#496](https://github.com/testcontainers/testcontainers-python/issues/496)) ([dfd1781](https://github.com/testcontainers/testcontainers-python/commit/dfd17814a7fc9ede510ae17569004bd92f2a6fa6)) * readthedocs build - take 1 ([#495](https://github.com/testcontainers/testcontainers-python/issues/495)) ([b3b9901](https://github.com/testcontainers/testcontainers-python/commit/b3b990159154857239e2fb86da3cb85a6a13ab8e)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 21 +++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 12ef0bfc3..e8e4b4dfa 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.1.0" + ".": "4.2.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b246cb1c6..dea823097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [4.2.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.1.0...testcontainers-v4.2.0) (2024-03-24) + + +### Features + +* support influxdb ([#413](https://github.com/testcontainers/testcontainers-python/issues/413)) ([13742a5](https://github.com/testcontainers/testcontainers-python/commit/13742a5dc448c80914953c21f8f2b01177c3fa6c)) + + +### Bug Fixes + +* **arangodb:** tests to pass on ARM CPUs - change default image to 3.11.x where ARM image is published ([#479](https://github.com/testcontainers/testcontainers-python/issues/479)) ([7b58a50](https://github.com/testcontainers/testcontainers-python/commit/7b58a50f3a8703c5d5e974a4ff20bc8e52ae93c8)) +* **core:** DinD issues [#141](https://github.com/testcontainers/testcontainers-python/issues/141), [#329](https://github.com/testcontainers/testcontainers-python/issues/329) ([#368](https://github.com/testcontainers/testcontainers-python/issues/368)) ([b10d916](https://github.com/testcontainers/testcontainers-python/commit/b10d916848cccc016fc457333f7b382b18a7b3ef)) +* **core:** raise an exception when docker compose fails to start [#258](https://github.com/testcontainers/testcontainers-python/issues/258) ([#485](https://github.com/testcontainers/testcontainers-python/issues/485)) ([d61af38](https://github.com/testcontainers/testcontainers-python/commit/d61af383def6eadcd7f2b5ba667eb587c6cc84f1)) +* **core:** use auto_remove=True with reaper instance ([#499](https://github.com/testcontainers/testcontainers-python/issues/499)) ([274a400](https://github.com/testcontainers/testcontainers-python/commit/274a4002600ae70662a5785c7a903cf8846b2ffc)) +* **docs:** update the non-existent main.yml badge ([#493](https://github.com/testcontainers/testcontainers-python/issues/493)) ([1d10c1c](https://github.com/testcontainers/testcontainers-python/commit/1d10c1ca8c8163b8d68338e1d50d0e26d7b0515e)) +* Fix the return type of `DockerContainer.get_logs` ([#487](https://github.com/testcontainers/testcontainers-python/issues/487)) ([cd72f68](https://github.com/testcontainers/testcontainers-python/commit/cd72f6896db3eb1fd5ea60f9c051cb719568a12f)) +* **keycloak:** tests on aarch64, use image from [jboss -> quay], change supported version [16+ -> 18+] ([#480](https://github.com/testcontainers/testcontainers-python/issues/480)) ([5758310](https://github.com/testcontainers/testcontainers-python/commit/5758310532b8a8e1303a24bc534fa8aeb0f75eb2)) +* **postgres:** doctest ([#473](https://github.com/testcontainers/testcontainers-python/issues/473)) ([c9c6f92](https://github.com/testcontainers/testcontainers-python/commit/c9c6f92348299a2cc04988af8d69a53a23a7c7d5)) +* read the docs build works again ([#496](https://github.com/testcontainers/testcontainers-python/issues/496)) ([dfd1781](https://github.com/testcontainers/testcontainers-python/commit/dfd17814a7fc9ede510ae17569004bd92f2a6fa6)) +* readthedocs build - take 1 ([#495](https://github.com/testcontainers/testcontainers-python/issues/495)) ([b3b9901](https://github.com/testcontainers/testcontainers-python/commit/b3b990159154857239e2fb86da3cb85a6a13ab8e)) + ## [4.1.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.0.1...testcontainers-v4.1.0) (2024-03-19) diff --git a/pyproject.toml b/pyproject.toml index b9ac9d7c9..ecf693111 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.1.0" # auto-incremented by release-please +version = "4.2.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 909107b221417a39516f961364beb518d2756f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Ragnar=20Brynjarsson?= Date: Sun, 24 Mar 2024 17:06:08 +0000 Subject: [PATCH 322/425] fix(kafka): wait_for_logs in kafka container to reduce lib requirement (#377) Use `wait_for_logs` to wait for startup instead of waiting for successful connection via kafka-python. Also removes the dependency on kafka-python. Closes #351 --------- Co-authored-by: Gudjon Ragnar Brynjarsson --- .../kafka/testcontainers/kafka/__init__.py | 15 +++----------- poetry.lock | 20 +++++++++++-------- pyproject.toml | 5 ++--- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index 399839433..577416504 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -3,11 +3,9 @@ from io import BytesIO from textwrap import dedent -from kafka import KafkaConsumer -from kafka.errors import KafkaError, NoBrokersAvailable, UnrecognizedBrokerVersion from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.waiting_utils import wait_for_logs class KafkaContainer(DockerContainer): @@ -47,13 +45,6 @@ def get_bootstrap_server(self) -> str: port = self.get_exposed_port(self.port) return f"{host}:{port}" - @wait_container_is_ready(UnrecognizedBrokerVersion, NoBrokersAvailable, KafkaError, ValueError) - def _connect(self) -> None: - bootstrap_server = self.get_bootstrap_server() - consumer = KafkaConsumer(group_id="test", bootstrap_servers=[bootstrap_server]) - if not consumer.bootstrap_connected(): - raise KafkaError("Unable to connect with kafka container!") - def tc_start(self) -> None: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) @@ -78,13 +69,13 @@ def tc_start(self) -> None: ) self.create_file(data, KafkaContainer.TC_START_SCRIPT) - def start(self) -> "KafkaContainer": + def start(self, timeout=30) -> "KafkaContainer": script = KafkaContainer.TC_START_SCRIPT command = f'sh -c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' self.with_command(command) super().start() self.tc_start() - self._connect() + wait_for_logs(self, r".*\[KafkaServer id=\d+\] started.*", timeout=timeout) return self def create_file(self, content: bytes, path: str) -> None: diff --git a/poetry.lock b/poetry.lock index 17c059466..d27c491a8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -218,8 +218,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, ] [package.extras] @@ -822,12 +822,12 @@ files = [ google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" @@ -878,8 +878,8 @@ grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" grpcio = ">=1.51.3,<2.0dev" grpcio-status = ">=1.33.2" proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -1289,7 +1289,7 @@ typing-extensions = ">=4.5.0" name = "kafka-python" version = "2.0.2" description = "Pure Python client for Apache Kafka" -optional = true +optional = false python-versions = "*" files = [ {file = "kafka-python-2.0.2.tar.gz", hash = "sha256:04dfe7fea2b63726cd6f3e79a2d86e709d608d74406638c5da33a01d45a9d7e3"}, @@ -1543,7 +1543,6 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -2424,6 +2423,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2870,7 +2870,9 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, @@ -2907,7 +2909,9 @@ files = [ {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, @@ -3243,7 +3247,7 @@ elasticsearch = [] google = ["google-cloud-pubsub"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] -kafka = ["kafka-python"] +kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] minio = ["minio"] @@ -3262,4 +3266,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "55534a498e0ae69beb3eba43e055910e656f67de80ec72400c4a535f91b2be56" +content-hash = "c092494be845c5f76ff36892af1fd89c23e9445fd4a1014da8f3335c9862f240" diff --git a/pyproject.toml b/pyproject.toml index ecf693111..09dde883d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,6 @@ influxdb = { version = "*", optional = true } influxdb-client = { version = "*", optional = true } kubernetes = { version = "*", optional = true } pyyaml = { version = "*", optional = true } -kafka-python = { version = "*", optional = true } python-keycloak = { version = "*", optional = true } boto3 = { version = "*", optional = true } minio = { version = "*", optional = true } @@ -93,7 +92,7 @@ elasticsearch = [] google = ["google-cloud-pubsub"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] -kafka = ["kafka-python"] +kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] minio = ["minio"] @@ -121,7 +120,7 @@ anyio = "^4.3.0" psycopg2-binary = "*" pg8000 = "*" sqlalchemy = "*" - +kafka-python = "^2.0.2" [[tool.poetry.source]] name = "PyPI" From 2e272253148797759748bd40c42f797697d3163f Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Sun, 24 Mar 2024 19:19:31 +0100 Subject: [PATCH 323/425] fix(keycloak): container should use dedicated API endpoints to determine container readiness (#490) As decided to support v18.0 image or newer, we should use these dedicated API endpoints to determine container readiness. These endpoints became introduced with v18.0. --- modules/keycloak/testcontainers/keycloak/__init__.py | 12 +++++++++--- modules/keycloak/tests/test_keycloak.py | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index ff7a64c2a..843283d67 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -50,6 +50,11 @@ def __init__( def _configure(self) -> None: self.with_env("KEYCLOAK_ADMIN", self.username) self.with_env("KEYCLOAK_ADMIN_PASSWORD", self.password) + # Enable health checks + # see: https://www.keycloak.org/server/health#_relevant_options + self.with_env("KC_HEALTH_ENABLED", "true") + # Starting Keycloak in development mode + # see: https://www.keycloak.org/server/configuration#_starting_keycloak_in_development_mode self.with_command("start-dev") def get_url(self) -> str: @@ -58,14 +63,15 @@ def get_url(self) -> str: return f"http://{host}:{port}" @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) - def _connect(self) -> None: - response = requests.get(self.get_url(), timeout=1) + def _readiness_probe(self) -> None: + # Keycloak provides an REST API endpoints for health checks: https://www.keycloak.org/server/health + response = requests.get(f"{self.get_url()}/health/ready", timeout=1) response.raise_for_status() def start(self) -> "KeycloakContainer": self._configure() super().start() - self._connect() + self._readiness_probe() return self def get_client(self, **kwargs) -> KeycloakAdmin: diff --git a/modules/keycloak/tests/test_keycloak.py b/modules/keycloak/tests/test_keycloak.py index f6d29a4e7..6eac42152 100644 --- a/modules/keycloak/tests/test_keycloak.py +++ b/modules/keycloak/tests/test_keycloak.py @@ -1,6 +1,8 @@ +import pytest from testcontainers.keycloak import KeycloakContainer -def test_docker_run_keycloak(): - with KeycloakContainer("quay.io/keycloak/keycloak:24.0.1") as keycloak_admin: +@pytest.mark.parametrize("image_version", ["24.0.1", "18.0"]) +def test_docker_run_keycloak(image_version: str): + with KeycloakContainer(f"quay.io/keycloak/keycloak:{image_version}") as keycloak_admin: keycloak_admin.get_client().users_count() From dd55082991b3405038a90678a39e8c815f0d1fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Wed, 27 Mar 2024 07:09:08 -0500 Subject: [PATCH 324/425] feat(client): Add custom User-Agent in Docker client as `tc-python/` (#507) Set User-Agent in format `tc-python/`, `version` value is coming from `pyproject.toml`. The `User-Agent` header will allow to identify Testcontainers language implementation and the specific version. Also, track the usage of the library. --- core/testcontainers/core/docker_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 04fdca59a..b4e968d02 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. import functools as ft +import importlib.metadata import ipaddress import os import urllib @@ -44,6 +45,7 @@ def __init__(self, **kwargs) -> None: else: self.client = docker.from_env(**kwargs) self.client.api.headers["x-tc-sid"] = SESSION_ID + self.client.api.headers["User-Agent"] = "tc-python/" + importlib.metadata.version("testcontainers") @ft.wraps(ContainerCollection.run) def run( From 545240dfdcb2a565ad7cef0e9813f03b9b6f910e Mon Sep 17 00:00:00 2001 From: Vemund Santi Date: Wed, 27 Mar 2024 15:19:18 +0100 Subject: [PATCH 325/425] fix: pass doctests, s/doctest/doctests/, run them in gha, s/asyncpg/psycopg/ in doctest, fix keycloak flakiness: wait for first user (#505) Doctests are run as interpreted code from docstrings. In order to run these tests, libraries need to be available for the example code, and usage of async code either needs to be wrapped in an `asyncio` call or avoided completely. This PR fixes up all failing doctests and makes `make doctests` target run successfully again. Summary: - Renames Make target `doctest` to `doctests` to follow naming convention from `tests` target - Adds `doctests` step to Github Action workflow runs - Replaces `asyncpg` example from `index.rst` with `psycopg` to be able to run as a doctest. Also added `psycopg` as dev dependency (`asyncpg` was already missing from here) - Fixes Keycloak doctest by providing expected output, also did the same for regular test - Also: Fixed `wait_for_container` method in `Keycloak` module to actually wait for the first user to be created (in order to be able to authenticate at all) before returning the started container, if the command is `dev-start`. This is needed in order to prevent race conditions in flaky tests and for the sample usage code. --- .github/workflows/ci-community.yml | 2 ++ .github/workflows/ci-core.yml | 2 ++ Makefile | 6 ++--- conf.py | 2 +- index.rst | 24 ++++++++--------- .../testcontainers/keycloak/__init__.py | 13 ++++++--- modules/keycloak/tests/test_keycloak.py | 2 +- .../testcontainers/postgres/__init__.py | 3 +-- modules/postgres/tests/test_postgres.py | 2 -- poetry.lock | 27 +++++++++++++++++-- pyproject.toml | 4 ++- 11 files changed, 59 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 5188b9d43..9284463cc 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -57,3 +57,5 @@ jobs: run: poetry install -E ${{ matrix.module }} - name: Run tests run: make modules/${{ matrix.module }}/tests + - name: Run doctests + run: make modules/${{ matrix.module }}/doctests diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 65bb23884..c39eb1ea0 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -28,3 +28,5 @@ jobs: run: poetry build && poetry run twine check dist/*.tar.gz - name: Run tests run: make core/tests + - name: Run doctests + run: make core/doctests diff --git a/Makefile b/Makefile index d8537efe7..1816f64b9 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ UPLOAD = $(addsuffix /upload,${PACKAGES}) # All */tests folders for each of the test suites. TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES})) TESTS_DIND = $(addsuffix -dind,${TESTS}) -DOCTESTS = $(addsuffix /doctest,$(filter-out meta,${PACKAGES})) +DOCTESTS = $(addsuffix /doctests,$(filter-out modules/README.md,${PACKAGES})) # All linting targets. LINT = $(addsuffix /lint,${PACKAGES}) @@ -56,10 +56,10 @@ ${TESTS_DIND} : %/tests-dind : image docs : poetry run sphinx-build -nW . docs/_build -doctest : ${DOCTESTS} +doctests : ${DOCTESTS} poetry run sphinx-build -b doctest . docs/_build -${DOCTESTS} : %/doctest : +${DOCTESTS} : %/doctests : poetry run sphinx-build -b doctest -c doctests $* docs/_build # Remove any generated files. diff --git a/conf.py b/conf.py index 5db9477ed..4c5ff938a 100644 --- a/conf.py +++ b/conf.py @@ -74,7 +74,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "meta/README.rst", ".venv"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv", ".git"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" diff --git a/index.rst b/index.rst index d6e7ac7d7..71828de28 100644 --- a/index.rst +++ b/index.rst @@ -21,6 +21,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/elasticsearch/README modules/google/README modules/influxdb/README + modules/k3s/README modules/kafka/README modules/keycloak/README modules/localstack/README @@ -36,7 +37,6 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/rabbitmq/README modules/redis/README modules/selenium/README - modules/k3s/README Getting Started --------------- @@ -46,32 +46,32 @@ Getting Started >>> from testcontainers.postgres import PostgresContainer >>> import sqlalchemy - >>> with PostgresContainer("postgres:latest") as postgres: + >>> with PostgresContainer("postgres:16") as postgres: ... psql_url = postgres.get_connection_url() ... engine = sqlalchemy.create_engine(psql_url) ... with engine.begin() as connection: - ... result = connection.execute(sqlalchemy.text("select version()")) - ... version, = result.fetchone() + ... version, = connection.execute(sqlalchemy.text("SELECT version()")).fetchone() >>> version - 'PostgreSQL ...' + 'PostgreSQL 16...' The snippet above will spin up the current latest version of a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url (using the :code:`psycopg2` driver per default) to connect to the database and retrieve the database version. .. doctest:: - >>> import asyncpg >>> from testcontainers.postgres import PostgresContainer + >>> import psycopg >>> with PostgresContainer("postgres:16", driver=None) as postgres: - ... psql_url = container.get_connection_url() - ... with asyncpg.create_pool(dsn=psql_url,server_settings={"jit": "off"}) as pool: - ... conn = await pool.acquire() - ... ret = await conn.fetchval("SELECT 1") - ... assert ret == 1 + ... psql_url = postgres.get_connection_url() + ... with psycopg.connect(psql_url) as connection: + ... with connection.cursor() as cursor: + ... version, = cursor.execute("SELECT version()").fetchone() + >>> version + 'PostgreSQL 16...' This snippet does the same, however using a specific version and the driver is set to None, to influence the :code:`get_connection_url()` convenience method to not include a driver in the URL (e.g. for compatibility with :code:`psycopg` v3). -Note, that the :code:`sqlalchemy` and :code:`psycopg2` packages are no longer a dependency of :code:`testcontainers[postgres]` and not needed to launch the Postgres container. Your project therefore needs to declare a dependency on the used driver and db access methods you use in your code. +Note, that the :code:`sqlalchemy` and :code:`psycopg` packages are no longer a dependency of :code:`testcontainers[postgres]` and not needed to launch the Postgres container. Your project therefore needs to declare a dependency on the used driver and db access methods you use in your code. Installation diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index 843283d67..ca5702298 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -17,7 +17,9 @@ from keycloak import KeycloakAdmin from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + +_DEFAULT_DEV_COMMAND = "start-dev" class KeycloakContainer(DockerContainer): @@ -30,8 +32,9 @@ class KeycloakContainer(DockerContainer): >>> from testcontainers.keycloak import KeycloakContainer - >>> with KeycloakContainer() as kc: - ... keycloak = kc.get_client() + >>> with KeycloakContainer(f"quay.io/keycloak/keycloak:24.0.1") as keycloak: + ... keycloak.get_client().users_count() + 1 """ def __init__( @@ -55,7 +58,7 @@ def _configure(self) -> None: self.with_env("KC_HEALTH_ENABLED", "true") # Starting Keycloak in development mode # see: https://www.keycloak.org/server/configuration#_starting_keycloak_in_development_mode - self.with_command("start-dev") + self.with_command(_DEFAULT_DEV_COMMAND) def get_url(self) -> str: host = self.get_container_host_ip() @@ -67,6 +70,8 @@ def _readiness_probe(self) -> None: # Keycloak provides an REST API endpoints for health checks: https://www.keycloak.org/server/health response = requests.get(f"{self.get_url()}/health/ready", timeout=1) response.raise_for_status() + if self._command == _DEFAULT_DEV_COMMAND: + wait_for_logs(self, "Added user .* to realm .*") def start(self) -> "KeycloakContainer": self._configure() diff --git a/modules/keycloak/tests/test_keycloak.py b/modules/keycloak/tests/test_keycloak.py index 6eac42152..ce54e4674 100644 --- a/modules/keycloak/tests/test_keycloak.py +++ b/modules/keycloak/tests/test_keycloak.py @@ -5,4 +5,4 @@ @pytest.mark.parametrize("image_version", ["24.0.1", "18.0"]) def test_docker_run_keycloak(image_version: str): with KeycloakContainer(f"quay.io/keycloak/keycloak:{image_version}") as keycloak_admin: - keycloak_admin.get_client().users_count() + assert keycloak_admin.get_client().users_count() == 1 diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index fd537f92b..3810ea0f2 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -38,8 +38,7 @@ class PostgresContainer(DbContainer): >>> from testcontainers.postgres import PostgresContainer >>> import sqlalchemy - >>> postgres_container = PostgresContainer("postgres:16") - >>> with postgres_container as postgres: + >>> with PostgresContainer("postgres:16") as postgres: ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) ... with engine.begin() as connection: ... result = connection.execute(sqlalchemy.text("select version()")) diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index f6d4447a0..d0f61e64a 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -1,5 +1,3 @@ -import sys - import pytest from testcontainers.postgres import PostgresContainer diff --git a/poetry.lock b/poetry.lock index d27c491a8..357dd6604 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1856,6 +1856,29 @@ files = [ {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, ] +[[package]] +name = "psycopg" +version = "3.1.18" +description = "PostgreSQL database adapter for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"}, + {file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"}, +] + +[package.dependencies] +typing-extensions = ">=4.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +binary = ["psycopg-binary (==3.1.18)"] +c = ["psycopg-c (==3.1.18)"] +dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] +docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] +pool = ["psycopg-pool"] +test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] + [[package]] name = "psycopg2-binary" version = "2.9.9" @@ -3032,7 +3055,7 @@ files = [ name = "tzdata" version = "2024.1" description = "Provider of IANA time zone data" -optional = true +optional = false python-versions = ">=2" files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, @@ -3266,4 +3289,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "c092494be845c5f76ff36892af1fd89c23e9445fd4a1014da8f3335c9862f240" +content-hash = "d58539d14fbcf79c97d6dde14d76a86b52cc11d059bd5e211655be73b74c4993" diff --git a/pyproject.toml b/pyproject.toml index 09dde883d..c48dce7e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,8 @@ description = "Python library for throwaway instances of anything that can run i authors = ["Sergey Pirogov "] maintainers = [ "Balint Bartha ", - "David Ankin " + "David Ankin ", + "Vemund Santi " ] readme = "README.md" keywords = ["testing", "logging", "docker", "test automation"] @@ -120,6 +121,7 @@ anyio = "^4.3.0" psycopg2-binary = "*" pg8000 = "*" sqlalchemy = "*" +psycopg = "*" kafka-python = "^2.0.2" [[tool.poetry.source]] From 63fcd52ec2d6ded5f6413166a3690c1138e4dae0 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Thu, 28 Mar 2024 20:05:14 -0400 Subject: [PATCH 326/425] fix(core): allow setting docker command path for docker compose (#512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #306 the original request was to remedy tc-python's dependency on docker-compose. this is something totally different but its what was asked for, so oh well. ¯\_(ツ)_/¯. --- core/testcontainers/compose/compose.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index d59e683bf..5931b35a6 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -158,6 +158,7 @@ class DockerCompose: wait: bool = True env_file: Optional[str] = None services: Optional[list[str]] = None + docker_command_path: Optional[str] = None def __post_init__(self): if isinstance(self.compose_file_name, str): @@ -181,7 +182,7 @@ def docker_compose_command(self) -> list[str]: @cached_property def compose_command_property(self) -> list[str]: - docker_compose_cmd = ["docker", "compose"] + docker_compose_cmd = [self.docker_command_path or "docker", "compose"] if self.compose_file_name: for file in self.compose_file_name: docker_compose_cmd += ["-f", file] From 8fb4bcc097c3f213327d13d0bfcb34ae35a0faba Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Sat, 30 Mar 2024 15:37:33 -0400 Subject: [PATCH 327/425] chore(build): improve devcontainers setup with poetry (#506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Replaces uses an image directly. 2. Creates a custom image, still based off the original Image. 3. Installs poetry inside the container. This saves the user about 20 seconds every container load. 4. Installs `pre-commit` in the container. Saves more time. 5. Enables `pre-commit`. From inside the container the user cannot commit without `pre-commit` running. 6. Adds in git autocomplete to the container (see screenshot) 7. Adds in poetry autocomplete to the container (see screenshot) ![devcontainer-completion](https://github.com/testcontainers/testcontainers-python/assets/1908139/11446f78-4fbf-4d08-a997-043ae1632919) --------- Co-authored-by: bstrausser Co-authored-by: David Ankin Co-authored-by: Bálint Bartha <39852431+totallyzen@users.noreply.github.com> --- .devcontainer/Dockerfile | 20 +++++++++++++++++++ .devcontainer/commands/post-create-command.sh | 3 +-- .devcontainer/devcontainer.json | 8 +++++++- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..82cabe3b4 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/devcontainers/python:1-3.11-bookworm + + +RUN \ + apt update && apt install bash-completion -y && \ + pip install pre-commit && \ + curl -sSL https://install.python-poetry.org | POETRY_HOME=/home/vscode/.local python3 - + + +RUN \ + echo >> /home/vscode/.bashrc && \ + # add completions to bashrc + # see how ubuntu does it for reference: + # https://git.launchpad.net/ubuntu/+source/base-files/tree/share/dot.bashrc + # https://stackoverflow.com/a/68566555 + echo 'if [ -f /etc/bash_completion ] && ! shopt -oq posix; then' >> /home/vscode/.bashrc && \ + echo ' . /etc/bash_completion' >> /home/vscode/.bashrc && \ + echo 'fi' >> /home/vscode/.bashrc && \ + echo >> /home/vscode/.bashrc && \ + echo '. <(poetry completions)' >> /home/vscode/.bashrc diff --git a/.devcontainer/commands/post-create-command.sh b/.devcontainer/commands/post-create-command.sh index c3229490f..58dc0f754 100755 --- a/.devcontainer/commands/post-create-command.sh +++ b/.devcontainer/commands/post-create-command.sh @@ -1,5 +1,4 @@ echo "Running post-create-command.sh" -curl -sSL https://install.python-poetry.org | python3 - - +pre-commit install poetry install --all-extras diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1c300bcc7..da44489a1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,13 @@ { "name": "Python 3", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bookworm", + "build": { + // Path is relative to the devcontainer.json file. + // We prebuild the image to get poetry into the image + // This saves the user a bit of time, when re-opening containers + "dockerfile": "Dockerfile" + }, + "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { "version": "latest", From 8addc111c94826c2a619a0880d48550673f4d7b9 Mon Sep 17 00:00:00 2001 From: Syed Mohsin Ul Islam <47140121+Mohsin-Ul-Islam@users.noreply.github.com> Date: Sun, 31 Mar 2024 03:43:11 +0500 Subject: [PATCH 328/425] fix: pass updated keyword args to Publisher/Subscriber client in google/pubsub #161 (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …le/pubsub --------- Co-authored-by: David Ankin --- modules/google/testcontainers/google/pubsub.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/google/testcontainers/google/pubsub.py b/modules/google/testcontainers/google/pubsub.py index 78c6929e2..706030599 100644 --- a/modules/google/testcontainers/google/pubsub.py +++ b/modules/google/testcontainers/google/pubsub.py @@ -56,7 +56,15 @@ def _get_client(self, cls: type, **kwargs) -> dict: return cls(**kwargs) def get_publisher_client(self, **kwargs) -> pubsub.PublisherClient: + from google.auth import credentials + + kwargs["client_options"] = {"api_endpoint": self.get_pubsub_emulator_host()} + kwargs["credentials"] = credentials.AnonymousCredentials() return self._get_client(pubsub.PublisherClient, **kwargs) def get_subscriber_client(self, **kwargs) -> pubsub.SubscriberClient: + from google.auth import credentials + + kwargs["client_options"] = {"api_endpoint": self.get_pubsub_emulator_host()} + kwargs["credentials"] = credentials.AnonymousCredentials() return self._get_client(pubsub.SubscriberClient, **kwargs) From 3d891a5ec62944d01d1bf3d6f70e6aec83f6e516 Mon Sep 17 00:00:00 2001 From: Matt Oates Date: Sat, 30 Mar 2024 23:04:13 +0000 Subject: [PATCH 329/425] fix(google): add support for Datastore emulator (#508) Expands the google module with a DatastoreContainer using the beta Datastore emulator using the same image as the PubSubContainer. Im already using a local copy of this in production. It would be nice to not have to support copy paste solutions and instead see it added to the google module. This is my first PR so please let me know what I need to do to get this over the line. Thanks. Looks like @tillahoffmann wrote the original PubSub emulator container --------- Co-authored-by: Matt Oates Co-authored-by: David Ankin --- modules/google/README.rst | 2 + .../google/testcontainers/google/__init__.py | 1 + .../google/testcontainers/google/datastore.py | 68 +++++++++++++++++++ modules/google/tests/test_google.py | 49 ++++++++++++- poetry.lock | 46 ++++++++++++- pyproject.toml | 3 +- 6 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 modules/google/testcontainers/google/datastore.py diff --git a/modules/google/README.rst b/modules/google/README.rst index 2f8c14d8f..903c3c4a1 100644 --- a/modules/google/README.rst +++ b/modules/google/README.rst @@ -1,2 +1,4 @@ +.. autoclass:: testcontainers.google.DatastoreContainer +.. title:: testcontainers.google.DatastoreContainer .. autoclass:: testcontainers.google.PubSubContainer .. title:: testcontainers.google.PubSubContainer diff --git a/modules/google/testcontainers/google/__init__.py b/modules/google/testcontainers/google/__init__.py index b28f2ed48..92c782efc 100644 --- a/modules/google/testcontainers/google/__init__.py +++ b/modules/google/testcontainers/google/__init__.py @@ -1 +1,2 @@ +from .datastore import DatastoreContainer # noqa: F401 from .pubsub import PubSubContainer # noqa: F401 diff --git a/modules/google/testcontainers/google/datastore.py b/modules/google/testcontainers/google/datastore.py new file mode 100644 index 000000000..24edbdcd7 --- /dev/null +++ b/modules/google/testcontainers/google/datastore.py @@ -0,0 +1,68 @@ +# +# 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 os +from unittest.mock import patch + +from google.cloud import datastore +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +class DatastoreContainer(DockerContainer): + """ + Datastore container for testing managed message queues. + + Example: + + The example will spin up a Google Cloud Datastore emulator that you can use for integration + tests. The :code:`datastore` instance provides convenience methods :code:`get_datastore_client` to + connect to the emulator without having to set the environment variable :code:`DATASTORE_EMULATOR_HOST`. + + .. doctest:: + + >>> from testcontainers.google import DatastoreContainer + + >>> config = DatastoreContainer() + >>> with config as datastore: + ... datastore_client = datastore.get_datastore_client() + """ + + def __init__( + self, + image: str = "google/cloud-sdk:emulators", + project: str = "test-project", + port: int = 8081, + **kwargs, + ) -> None: + super().__init__(image=image, **kwargs) + self.project = project + self.port = port + self.with_exposed_ports(self.port) + self.with_command( + f"gcloud beta emulators datastore start --no-store-on-disk --project={project} --host-port=0.0.0.0:{port}" + ) + + def get_datastore_emulator_host(self) -> str: + return f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" + + def get_datastore_client(self, **kwargs) -> datastore.Client: + wait_for_logs(self, "Dev App Server is now running.", timeout=30.0) + env_vars = { + "DATASTORE_DATASET": self.project, + "DATASTORE_EMULATOR_HOST": self.get_datastore_emulator_host(), + "DATASTORE_EMULATOR_HOST_PATH": f"{self.get_datastore_emulator_host()}/datastore", + "DATASTORE_HOST": f"http://{self.get_datastore_emulator_host()}", + "DATASTORE_PROJECT_ID": self.project, + } + with patch.dict(os.environ, env_vars): + return datastore.Client(**kwargs) diff --git a/modules/google/tests/test_google.py b/modules/google/tests/test_google.py index 780f5fdd6..0c412d706 100644 --- a/modules/google/tests/test_google.py +++ b/modules/google/tests/test_google.py @@ -1,7 +1,8 @@ from queue import Queue +from google.cloud.datastore import Entity from testcontainers.core.waiting_utils import wait_for_logs -from testcontainers.google import PubSubContainer +from testcontainers.google import PubSubContainer, DatastoreContainer def test_pubsub_container(): @@ -27,3 +28,49 @@ def test_pubsub_container(): message = queue.get(timeout=1) assert message.data == b"Hello world!" message.ack() + + +def test_datastore_container_creation(): + # Initialize the Datastore emulator container + with DatastoreContainer() as datastore: + # Obtain a datastore client configured to connect to the emulator + client = datastore.get_datastore_client() + + # Define a unique key for a test entity to ensure test isolation + key = client.key("TestKind", "test_id_1") + + # Create and insert a new entity + entity = Entity(key=key) + entity.update({"foo": "bar"}) + client.put(entity) + + # Fetch the just-inserted entity directly + fetched_entity = client.get(key) + + # Assert that the fetched entity matches what was inserted + assert fetched_entity is not None, "Entity was not found in the datastore." + assert fetched_entity["foo"] == "bar", "Entity attribute 'foo' did not match expected value 'bar'." + + +def test_datastore_container_isolation(): + # Initialize the Datastore emulator container + with DatastoreContainer() as datastore: + # Obtain a datastore client configured to connect to the emulator + client = datastore.get_datastore_client() + + # Define a unique key for a test entity to ensure test isolation + key = client.key("TestKind", "test_id_1") + + # Create and insert a new entity + entity = Entity(key=key) + entity.update({"foo": "bar"}) + client.put(entity) + + # Create a second container and try to fetch the entity to makesure its a different container + with DatastoreContainer() as datastore2: + assert ( + datastore.get_datastore_emulator_host() != datastore2.get_datastore_emulator_host() + ), "Datastore containers use the same port." + client2 = datastore2.get_datastore_client() + fetched_entity2 = client2.get(key) + assert fetched_entity2 is None, "Entity was found in the datastore." diff --git a/poetry.lock b/poetry.lock index 357dd6604..39b3d3d4e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -860,6 +860,47 @@ pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +[[package]] +name = "google-cloud-core" +version = "2.4.1" +description = "Google Cloud API client core library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, +] + +[package.dependencies] +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.25.0,<3.0dev" + +[package.extras] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] + +[[package]] +name = "google-cloud-datastore" +version = "2.19.0" +description = "Google Cloud Datastore API client library" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-cloud-datastore-2.19.0.tar.gz", hash = "sha256:07fc5870a0261f25466c557c134df95a96dfd2537abd088b9d537fbabe99b974"}, + {file = "google_cloud_datastore-2.19.0-py2.py3-none-any.whl", hash = "sha256:c52086670d4c3779ea7bd8f8353b093a9b5e81c6606f36ffcdf46e6ce9fc80c0"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-cloud-core = ">=1.4.0,<3.0.0dev" +proto-plus = [ + {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, +] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" + +[package.extras] +libcst = ["libcst (>=0.2.5)"] + [[package]] name = "google-cloud-pubsub" version = "2.20.1" @@ -1543,6 +1584,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -3267,7 +3309,7 @@ arangodb = ["python-arango"] azurite = ["azure-storage-blob"] clickhouse = ["clickhouse-driver"] elasticsearch = [] -google = ["google-cloud-pubsub"] +google = ["google-cloud-datastore", "google-cloud-pubsub"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] kafka = [] @@ -3289,4 +3331,4 @@ selenium = ["selenium"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "d58539d14fbcf79c97d6dde14d76a86b52cc11d059bd5e211655be73b74c4993" +content-hash = "d28fd579c8964fef5acec08c1c39a77d7200d77393320e92cf72f07f49100e5e" diff --git a/pyproject.toml b/pyproject.toml index c48dce7e5..a0ca924a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ python-arango = { version = "^7.8", optional = true } azure-storage-blob = { version = "^12.19", optional = true } clickhouse-driver = { version = "*", optional = true } google-cloud-pubsub = { version = ">=2", optional = true } +google-cloud-datastore = { version = ">=2", optional = true } influxdb = { version = "*", optional = true } influxdb-client = { version = "*", optional = true } kubernetes = { version = "*", optional = true } @@ -90,7 +91,7 @@ arangodb = ["python-arango"] azurite = ["azure-storage-blob"] clickhouse = ["clickhouse-driver"] elasticsearch = [] -google = ["google-cloud-pubsub"] +google = ["google-cloud-pubsub", "google-cloud-datastore"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] kafka = [] From 90762e817bf49de6d6366212fb48e7edb67ab0c6 Mon Sep 17 00:00:00 2001 From: Marcin Antas Date: Sun, 31 Mar 2024 00:15:38 +0100 Subject: [PATCH 330/425] fix: Add Weaviate module (#492) This PR adds Weaviate module. --------- Co-authored-by: David Ankin --- index.rst | 1 + modules/weaviate/README.rst | 2 + .../testcontainers/weaviate/__init__.py | 178 ++++++++++ modules/weaviate/tests/test_weaviate.py | 55 ++++ poetry.lock | 309 +++++++++++++++++- pyproject.toml | 6 +- 6 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 modules/weaviate/README.rst create mode 100644 modules/weaviate/testcontainers/weaviate/__init__.py create mode 100644 modules/weaviate/tests/test_weaviate.py diff --git a/index.rst b/index.rst index 71828de28..6d15329f7 100644 --- a/index.rst +++ b/index.rst @@ -37,6 +37,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/rabbitmq/README modules/redis/README modules/selenium/README + modules/weaviate/README Getting Started --------------- diff --git a/modules/weaviate/README.rst b/modules/weaviate/README.rst new file mode 100644 index 000000000..560934164 --- /dev/null +++ b/modules/weaviate/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.weaviate.WeaviateContainer +.. title:: testcontainers.weaviate.WeaviateContainer diff --git a/modules/weaviate/testcontainers/weaviate/__init__.py b/modules/weaviate/testcontainers/weaviate/__init__.py new file mode 100644 index 000000000..e59e251ec --- /dev/null +++ b/modules/weaviate/testcontainers/weaviate/__init__.py @@ -0,0 +1,178 @@ +# +# 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. +from typing import TYPE_CHECKING, Optional + +from requests import ConnectionError, get + +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + +if TYPE_CHECKING: + from requests import Response + + +class WeaviateContainer(DbContainer): + """ + Weaviate vector database container. + + Arguments: + `image` + Docker image to use with Weaviate container. + `env_vars` + Additional environment variables to include with the container, e.g. ENABLE_MODULES list, QUERY_DEFAULTS_LIMIT setting. + + Example: + This example shows how to start Weaviate container with defualt settings. + + .. doctest:: + + >>> from testcontainers.weaviate import WeaviateContainer + + >>> with WeaviateContainer() as container: + ... with container.get_client() as client: + ... client.is_live() + True + + This example shows how to start Weaviate container with additinal settings. + + .. doctest:: + + >>> from testcontainers.weaviate import WeaviateContainer + + >>> with WeaviateContainer( + ... env_vars={ + ... "ENABLE_MODULES": "backup-filesystem,text2vec-openai", + ... "BACKUP_FILESYSTEM_PATH": "/tmp/backups", + ... "QUERY_DEFAULTS_LIMIT": 100, + ... } + ... ) as container: + ... with container.get_client() as client: + ... client.is_live() + True + """ + + def __init__( + self, + image: str = "semitechnologies/weaviate:1.24.5", + env_vars: Optional[dict[str, str]] = None, + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + self._http_port = 8080 + self._grpc_port = 50051 + + self.with_command(f"--host 0.0.0.0 --scheme http --port {self._http_port}") + self.with_exposed_ports(self._http_port, self._grpc_port) + + if env_vars is not None: + for key, value in env_vars.items(): + self.with_env(key, value) + + def _configure(self) -> None: + self.with_env("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "true") + self.with_env("PERSISTENCE_DATA_PATH", "/var/lib/weaviate") + + @wait_container_is_ready(ConnectionError) + def _connect(self) -> None: + url = f"http://{self.get_http_host()}:{self.get_http_port()}/v1/.well-known/ready" + response: Response = get(url) + response.raise_for_status() + + def get_client( + self, + headers: Optional[dict[str, str]] = None, + ): + """ + Get a `weaviate.WeaviateClient` instance associated with the container. + + Arguments: + `headers` + Additional headers to include in the requests, e.g. API keys for third-party Cloud vectorization. + + Returns: + WeaviateClient: An instance of the `weaviate.WeaviateClient` class. + """ + + try: + import weaviate + except ImportError as e: + raise ImportError("To use the `get_client` method, you must install the `weaviate-client` package.") from e + return weaviate.connect_to_custom( + http_host=self.get_http_host(), + http_port=self.get_http_port(), + http_secure=self.get_http_secure(), + grpc_host=self.get_http_host(), + grpc_port=self.get_grpc_port(), + grpc_secure=self.get_grpc_secure(), + headers=headers, + ) + + def get_http_host(self) -> str: + """ + Get the HTTP host of Weaviate container. + + Returns: + `str` + The HTTP host of Weaviate container. + """ + return f"{self.get_container_host_ip()}" + + def get_http_port(self) -> int: + """ + Get the HTTP port of Weaviate container. + + Returns: + `int` + The HTTP port of Weaviate container. + """ + return self.get_exposed_port(self._http_port) + + def get_http_secure(self) -> bool: + """ + Get the HTTP secured setting of Weaviate container. + + Returns: + `bool` + True if it's https. + """ + return False + + def get_grpc_host(self) -> str: + """ + Get the gRPC host of Weaviate container. + + Returns: + `str` + The gRPC host of Weaviate container. + """ + return f"{self.get_container_host_ip()}" + + def get_grpc_port(self) -> int: + """ + Get the gRPC port of Weaviate container. + + Returns: + `int` + The gRPC port of Weaviate container. + """ + return self.get_exposed_port(self._grpc_port) + + def get_grpc_secure(self) -> bool: + """ + Get the gRPC secured setting of Weaviate container. + + Returns: + `str` + True if the conntection is secured with SSL. + """ + return False diff --git a/modules/weaviate/tests/test_weaviate.py b/modules/weaviate/tests/test_weaviate.py new file mode 100644 index 000000000..40728d4aa --- /dev/null +++ b/modules/weaviate/tests/test_weaviate.py @@ -0,0 +1,55 @@ +from testcontainers.weaviate import WeaviateContainer +import weaviate + + +def test_docker_run_weaviate(): + with WeaviateContainer() as container: + client = weaviate.connect_to_custom( + http_host=container.get_http_host(), + http_port=container.get_http_port(), + http_secure=container.get_http_secure(), + grpc_host=container.get_grpc_host(), + grpc_port=container.get_grpc_port(), + grpc_secure=container.get_grpc_secure(), + ) + + meta = client.get_meta() + assert len(meta.get("version")) > 0 + + client.close() + + +def test_docker_run_weaviate_with_client(): + with WeaviateContainer() as container: + with container.get_client() as client: + assert client.is_live() + + meta = client.get_meta() + assert len(meta.get("version")) > 0 + + +def test_docker_run_weaviate_with_modules(): + enable_modules = [ + "backup-filesystem", + "text2vec-openai", + "text2vec-cohere", + "text2vec-huggingface", + "generative-openai", + ] + with WeaviateContainer( + env_vars={ + "ENABLE_MODULES": ",".join(enable_modules), + "BACKUP_FILESYSTEM_PATH": "/tmp/backups", + } + ) as container: + with container.get_client() as client: + assert client.is_live() + + meta = client.get_meta() + assert len(meta.get("version")) > 0 + + modules = meta.get("modules") + assert len(modules) == len(enable_modules) + + for name in enable_modules: + assert len(modules[name]) > 0 diff --git a/poetry.lock b/poetry.lock index 39b3d3d4e..0622fbabb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,17 @@ files = [ {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = true +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "anyio" version = "4.3.0" @@ -131,6 +142,20 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "authlib" +version = "1.3.0" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." +optional = true +python-versions = ">=3.8" +files = [ + {file = "Authlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:9637e4de1fb498310a56900b3e2043a206b03cb11c05422014b0302cbc814be3"}, + {file = "Authlib-1.3.0.tar.gz", hash = "sha256:959ea62a5b7b5123c5059758296122b57cd2585ae2ed1c0622c21b371ffdae06"}, +] + +[package.dependencies] +cryptography = "*" + [[package]] name = "azure-core" version = "1.30.1" @@ -1098,6 +1123,21 @@ files = [ [package.extras] protobuf = ["grpcio-tools (>=1.62.1)"] +[[package]] +name = "grpcio-health-checking" +version = "1.62.1" +description = "Standard Health Checking Service for gRPC" +optional = true +python-versions = ">=3.6" +files = [ + {file = "grpcio-health-checking-1.62.1.tar.gz", hash = "sha256:9e56180a941b1d32a077d7491e0611d0483c396358afd5349bf00152612e4583"}, + {file = "grpcio_health_checking-1.62.1-py3-none-any.whl", hash = "sha256:9ce761c09fc383e7aa2f7e6c0b0b65d5a1157c1b98d1f5871f7c38aca47d49b9"}, +] + +[package.dependencies] +grpcio = ">=1.62.1" +protobuf = ">=4.21.6" + [[package]] name = "grpcio-status" version = "1.62.1" @@ -1114,6 +1154,74 @@ googleapis-common-protos = ">=1.5.5" grpcio = ">=1.62.1" protobuf = ">=4.21.6" +[[package]] +name = "grpcio-tools" +version = "1.62.1" +description = "Protobuf code generator for gRPC" +optional = true +python-versions = ">=3.7" +files = [ + {file = "grpcio-tools-1.62.1.tar.gz", hash = "sha256:a4991e5ee8a97ab791296d3bf7e8700b1445635cc1828cc98df945ca1802d7f2"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:f2b404bcae7e2ef9b0b9803b2a95119eb7507e6dc80ea4a64a78be052c30cebc"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:fdd987a580b4474769adfd40144486f54bcc73838d5ec5d3647a17883ea78e76"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:07af1a6442e2313cff22af93c2c4dd37ae32b5239b38e0d99e2cbf93de65429f"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41384c9ee18e61ef20cad2774ef71bd8854b63efce263b5177aa06fccb84df1f"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c38006f7702d2ff52122e4c77a47348709374050c76216e84b30a9f06e45afa"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08fecc3c5b4e6dd3278f2b9d12837e423c7dcff551ca1e587018b4a0fc5f8019"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a01e8dcd0f041f6fa6d815c54a2017d032950e310c41d514a8bc041e872c4d12"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-win32.whl", hash = "sha256:dd933b8e0b3c13fe3543d58f849a6a5e0d7987688cb6801834278378c724f695"}, + {file = "grpcio_tools-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b04844a9382f1bde4b4174e476e654ab3976168d2469cb4b29e352f4f35a5aa"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:024380536ba71a96cdf736f0954f6ad03f5da609c09edbcc2ca02fdd639e0eed"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:21f14b99e0cd38ad56754cc0b62b2bf3cf75f9f7fc40647da54669e0da0726fe"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:975ac5fb482c23f3608c16e06a43c8bab4d79c2e2564cdbc25cf753c6e998775"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50739aaab0c8076ad5957204e71f2e0c9876e11fd8338f7f09de12c2d75163c5"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598c54318f0326cf5020aa43fc95a15e933aba4a71943d3bff2677d2d21ddfa1"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f309bdb33a61f8e049480d41498ee2e525cfb5e959958b326abfdf552bf9b9cb"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f358effd3c11d66c150e0227f983d54a5cd30e14038566dadcf25f9f6844e6e8"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-win32.whl", hash = "sha256:b76aead9b73f1650a091870fe4e9ed15ac4d8ed136f962042367255199c23594"}, + {file = "grpcio_tools-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:d66a5d47eaa427039752fa0a83a425ff2a487b6a0ac30556fd3be2f3a27a0130"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:575535d039b97d63e6a9abee626d6c7cd47bd8cb73dd00a5c84a98254a2164a4"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:22644c90e43d1a888477899af917979e17364fdd6e9bbb92679cd6a54c4d36c3"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:156d3e1b227c16e903003a56881dbe60e40f2b4bd66f0bc3b27c53e466e6384d"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ad7c5691625a85327e5b683443baf73ae790fd5afc938252041ed5cd665e377"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e140bbc08eea8abf51c0274f45fb1e8350220e64758998d7f3c7f985a0b2496"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7444fcab861911525470d398e5638b70d5cbea3b4674a3de92b5c58c5c515d4d"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e643cd14a5d1e59865cba68a5a6f0175d987f36c5f4cb0db80dee9ed60b4c174"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-win32.whl", hash = "sha256:1344a773d2caa9bb7fbea7e879b84f33740c808c34a5bd2a2768e526117a6b44"}, + {file = "grpcio_tools-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:2eea1db3748b2f37b4dce84d8e0c15d9bc811094807cabafe7b0ea47f424dfd5"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:45d2e6cf04d27286b6f73e6e20ba3f0a1f6d8f5535e5dcb1356200419bb457f4"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:46ae58e6926773e7315e9005f0f17aacedbc0895a8752bec087d24efa2f1fb21"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c28086df31478023a36f45e50767872ab3aed2419afff09814cb61c88b77db4"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fba5b339f4797548591036c9481e6895bf920fab7d3dc664d2697f8fb7c0bf"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23eb3d47f78f509fcd201749b1f1e44b76f447913f7fbb3b8bae20f109086295"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fd5d47707bd6bc2b707ece765c362d2a1d2e8f6cd92b04c99fab49a929f3610c"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1924a6a943df7c73b9ef0048302327c75962b567451479710da729ead241228"}, + {file = "grpcio_tools-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:fe71ca30aabe42591e84ecb9694c0297dc699cc20c5b24d2cb267fb0fc01f947"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1819fd055c1ae672d1d725ec75eefd1f700c18acba0ed9332202be31d69c401d"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:5dbe1f7481dd14b6d477b4bace96d275090bc7636b9883975a08b802c94e7b78"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:771c051c5ece27ad03e4f2e33624a925f0ad636c01757ab7dbb04a37964af4ba"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98209c438b38b6f1276dbc27b1c04e346a75bfaafe72a25a548f2dc5ce71d226"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2152308e5321cb90fb45aaa84d03d6dedb19735a8779aaf36c624f97b831842d"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ed1f27dc2b2262c8b8d9036276619c1bb18791311c16ccbf1f31b660f2aad7cf"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2744947b6c5e907af21133431809ccca535a037356864e32c122efed8cb9de1f"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-win32.whl", hash = "sha256:13b20e269d14ad629ff9a2c9a2450f3dbb119d5948de63b27ffe624fa7aea85a"}, + {file = "grpcio_tools-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:999823758e9eacd0095863d06cd6d388be769f80c9abb65cdb11c4f2cfce3fea"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:941f8a5c31986053e75fa466bcfa743c2bf1b513b7978cf1f4ab4e96a8219d27"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b9c02c88c77ef6057c6cbeea8922d7c2424aabf46bfc40ddf42a32765ba91061"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6abd4eb3ccb444383a40156139acc3aaa73745d395139cb6bc8e2a3429e1e627"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:449503213d142f8470b331a1c2f346f8457f16c7fe20f531bc2500e271f7c14c"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a11bcf609d00cfc9baed77ab308223cabc1f0b22a05774a26dd4c94c0c80f1f"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5d7bdea33354b55acf40bb4dd3ba7324d6f1ef6b4a1a4da0807591f8c7e87b9a"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d03b645852d605f43003020e78fe6d573cae6ee6b944193e36b8b317e7549a20"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-win32.whl", hash = "sha256:52b185dfc3bf32e70929310367dbc66185afba60492a6a75a9b1141d407e160c"}, + {file = "grpcio_tools-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:63a273b70896d3640b7a883eb4a080c3c263d91662d870a2e9c84b7bbd978e7b"}, +] + +[package.dependencies] +grpcio = ">=1.62.1" +protobuf = ">=4.21.6,<5.0dev" +setuptools = "*" + [[package]] name = "h11" version = "0.14.0" @@ -1125,6 +1233,51 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "identify" version = "2.5.35" @@ -2079,6 +2232,116 @@ files = [ {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] +[[package]] +name = "pydantic" +version = "2.6.4" +description = "Data validation using Python type hints" +optional = true +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = true +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pygments" version = "2.17.2" @@ -3160,6 +3423,28 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "validators" +version = "0.22.0" +description = "Python Data Validation for Humans™" +optional = true +python-versions = ">=3.8" +files = [ + {file = "validators-0.22.0-py3-none-any.whl", hash = "sha256:61cf7d4a62bbae559f2e54aed3b000cea9ff3e2fdbe463f51179b92c58c9585a"}, + {file = "validators-0.22.0.tar.gz", hash = "sha256:77b2689b172eeeb600d9605ab86194641670cdb73b60afd577142a9397873370"}, +] + +[package.extras] +docs-offline = ["myst-parser (>=2.0.0)", "pypandoc-binary (>=1.11)", "sphinx (>=7.1.1)"] +docs-online = ["mkdocs (>=1.5.2)", "mkdocs-git-revision-date-localized-plugin (>=1.2.0)", "mkdocs-material (>=9.2.6)", "mkdocstrings[python] (>=0.22.0)", "pyaml (>=23.7.0)"] +hooks = ["pre-commit (>=3.3.3)"] +package = ["build (>=1.0.0)", "twine (>=4.0.2)"] +runner = ["tox (>=4.11.1)"] +sast = ["bandit[toml] (>=1.7.5)"] +testing = ["pytest (>=7.4.0)"] +tooling = ["black (>=23.7.0)", "pyright (>=1.1.325)", "ruff (>=0.0.287)"] +tooling-extras = ["pyaml (>=23.7.0)", "pypandoc-binary (>=1.11)", "pytest (>=7.4.0)"] + [[package]] name = "virtualenv" version = "20.25.1" @@ -3180,6 +3465,27 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "weaviate-client" +version = "4.5.4" +description = "A python native Weaviate client" +optional = true +python-versions = ">=3.8" +files = [ + {file = "weaviate-client-4.5.4.tar.gz", hash = "sha256:fc53dc73cd53df453c5e6dc758e49a6a1549212d6670ddd013392107120692f8"}, + {file = "weaviate_client-4.5.4-py3-none-any.whl", hash = "sha256:f6d3a6b759e5aa0d3350067490526ea38b9274ae4043b4a3ae0064c28d56883f"}, +] + +[package.dependencies] +authlib = ">=1.2.1,<2.0.0" +grpcio = ">=1.57.0,<2.0.0" +grpcio-health-checking = ">=1.57.0,<2.0.0" +grpcio-tools = ">=1.57.0,<2.0.0" +httpx = "0.27.0" +pydantic = ">=2.5.0,<3.0.0" +requests = ">=2.30.0,<3.0.0" +validators = "0.22.0" + [[package]] name = "websocket-client" version = "1.7.0" @@ -3327,8 +3633,9 @@ postgres = [] rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] +weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "d28fd579c8964fef5acec08c1c39a77d7200d77393320e92cf72f07f49100e5e" +content-hash = "15e0e0ba774e0e8babe3ed56b7c16f364790cc0275501dbe9bb317c2d2b1f9b0" diff --git a/pyproject.toml b/pyproject.toml index a0ca924a3..6242a6f8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,8 @@ packages = [ { include = "testcontainers", from = "modules/postgres" }, { include = "testcontainers", from = "modules/rabbitmq" }, { include = "testcontainers", from = "modules/redis" }, - { include = "testcontainers", from = "modules/selenium" } + { include = "testcontainers", from = "modules/selenium" }, + { include = "testcontainers", from = "modules/weaviate" } ] [tool.poetry.urls] @@ -85,6 +86,7 @@ cx_Oracle = { version = "*", optional = true } pika = { version = "*", optional = true } redis = { version = "*", optional = true } selenium = { version = "*", optional = true } +weaviate-client = { version = "^4.5.4", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -109,6 +111,7 @@ postgres = [] rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] +weaviate = ["weaviate-client"] [tool.poetry.group.dev.dependencies] mypy = "1.7.1" @@ -241,6 +244,7 @@ mypy_path = [ # "modules/rabbitmq", # "modules/redis", # "modules/selenium" +# "modules/weaviate" ] enable_error_code = [ "ignore-without-code", From 472b2c24aec232a04c00dd7dcd9a9f05f2dfaa66 Mon Sep 17 00:00:00 2001 From: Vemund Santi Date: Sun, 31 Mar 2024 00:17:11 +0100 Subject: [PATCH 331/425] fix(ryuk): Enable Ryuk test suite. Ryuk image 0.5.1 -> 0.7.0. Add RYUK_RECONNECTION_TIMEOUT env variable (#509) - Re-enables the Ryuk test suite - Bumps Ryuk container image from 0.5.1 - > 0.7.0 - Add env variable `RYUK_RECONNECTION_TIMEOUT` (As documented in the [official Ryuk repo](https://github.com/testcontainers/moby-ryuk?tab=readme-ov-file#ryuk-configuration)) --- README.md | 13 +++--- core/testcontainers/core/config.py | 3 +- core/testcontainers/core/container.py | 17 ++++++-- core/tests/test_ryuk.py | 59 ++++++++++++++++++--------- index.rst | 2 +- 5 files changed, 64 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 7f4699143..036723d61 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,10 @@ The snippet above will spin up a postgres database in a container. The `get_conn ## Configuration -| Env Variable | Example | Description | -| ----------------------------------------- | ----------------------------- | ---------------------------------------- | -| `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk | -| `TESTCONTAINERS_RYUK_PRIVILEGED` | `false` | Run ryuk as a privileged container | -| `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk | -| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.5.1` | Custom image for ryuk | +| Env Variable | Example | Description | +| --------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------- | +| `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk | +| `TESTCONTAINERS_RYUK_PRIVILEGED` | `false` | Run ryuk as a privileged container | +| `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk | +| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.7.0` | Custom image for ryuk | +| `RYUK_RECONNECTION_TIMEOUT` | `10s` | Reconnection timeout for Ryuk TCP socket before Ryuk reaps all dangling containers | diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 1bf9ad4dc..0c1b5e0c2 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -4,7 +4,8 @@ SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) TIMEOUT = MAX_TRIES * SLEEP_TIME -RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.5.1") +RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.7.0") RYUK_PRIVILEGED: bool = environ.get("TESTCONTAINERS_RYUK_PRIVILEGED", "false") == "true" RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true" RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock") +RYUK_RECONNECTION_TIMEOUT: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s") diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 42f4de526..3e1e1ba19 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,8 +1,17 @@ +import contextlib from platform import system from socket import socket from typing import TYPE_CHECKING, Optional -from testcontainers.core.config import RYUK_DISABLED, RYUK_DOCKER_SOCKET, RYUK_IMAGE, RYUK_PRIVILEGED +import docker.errors + +from testcontainers.core.config import ( + RYUK_DISABLED, + RYUK_DOCKER_SOCKET, + RYUK_IMAGE, + RYUK_PRIVILEGED, + RYUK_RECONNECTION_TIMEOUT, +) from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID @@ -177,8 +186,9 @@ def delete_instance(cls) -> None: Reaper._socket.close() Reaper._socket = None - if Reaper._container is not None: - Reaper._container.stop() + if Reaper._container is not None and Reaper._container._container is not None: + with contextlib.suppress(docker.errors.NotFound): + Reaper._container.stop() Reaper._container = None if Reaper._instance is not None: @@ -194,6 +204,7 @@ def _create_instance(cls) -> "Reaper": .with_exposed_ports(8080) .with_volume_mapping(RYUK_DOCKER_SOCKET, "/var/run/docker.sock", "rw") .with_kwargs(privileged=RYUK_PRIVILEGED, auto_remove=True) + .with_env("RYUK_RECONNECTION_TIMEOUT", RYUK_RECONNECTION_TIMEOUT) .start() ) wait_for_logs(Reaper._container, r".* Started!") diff --git a/core/tests/test_ryuk.py b/core/tests/test_ryuk.py index 4f3b431e3..e21b045ba 100644 --- a/core/tests/test_ryuk.py +++ b/core/tests/test_ryuk.py @@ -1,37 +1,58 @@ -from contextlib import contextmanager - +from time import sleep import pytest +from pytest import MonkeyPatch + +from docker import DockerClient +from docker.errors import NotFound -from testcontainers.core import container +from testcontainers.core import container as container_module from testcontainers.core.container import Reaper from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs -@pytest.mark.skip("invalid test - ryuk logs 'Removed' right before exiting") -def test_wait_for_reaper(): +def test_wait_for_reaper(monkeypatch: MonkeyPatch): + Reaper.delete_instance() + monkeypatch.setattr(container_module, "RYUK_RECONNECTION_TIMEOUT", "0.1s") + docker_client = DockerClient() container = DockerContainer("hello-world").start() + + container_id = container.get_wrapped_container().short_id + reaper_id = Reaper._container.get_wrapped_container().short_id + + assert docker_client.containers.get(container_id) is not None + assert docker_client.containers.get(reaper_id) is not None + wait_for_logs(container, "Hello from Docker!") - assert Reaper._socket is not None Reaper._socket.close() - assert Reaper._container is not None - wait_for_logs(Reaper._container, r".* Removed \d .*", timeout=30) + sleep(0.6) # Sleep until Ryuk reaps all dangling containers. 0.5 extra seconds for good measure. + with pytest.raises(NotFound): + docker_client.containers.get(container_id) + with pytest.raises(NotFound): + docker_client.containers.get(reaper_id) + + # Cleanup Ryuk class fields after manual Ryuk shutdown + Reaper.delete_instance() + + +def test_container_without_ryuk(monkeypatch: MonkeyPatch): Reaper.delete_instance() + monkeypatch.setattr(container_module, "RYUK_DISABLED", True) + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + assert Reaper._instance is None -@contextmanager -def reset_reaper_instance(): - old_value = Reaper._instance - Reaper._instance = None - yield - Reaper._instance = old_value +def test_ryuk_is_reused_in_same_process(): + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + reaper_instance = Reaper._instance + assert reaper_instance is not None -def test_container_without_ryuk(monkeypatch): - monkeypatch.setattr(container, "RYUK_DISABLED", True) - with reset_reaper_instance(), DockerContainer("hello-world") as cont: - wait_for_logs(cont, "Hello from Docker!") - assert Reaper._instance is None + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + assert reaper_instance is Reaper._instance diff --git a/index.rst b/index.rst index 6d15329f7..a716e09a8 100644 --- a/index.rst +++ b/index.rst @@ -106,7 +106,7 @@ Configuration +-------------------------------------------+-------------------------------+------------------------------------------+ | ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | +-------------------------------------------+-------------------------------+------------------------------------------+ -| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.5.1`` | Custom image for ryuk | +| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | +-------------------------------------------+-------------------------------+------------------------------------------+ Development and Contributing From 914f1e55bcb3b10260788c3affb8426f77eb9036 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Sun, 31 Mar 2024 00:23:39 +0100 Subject: [PATCH 332/425] fix: inconsistent test runs for community modules (#497) - Fixed inconsistencies for community module test runs: using all supported Python versions - Pinned runner version (best practice) fixes https://github.com/testcontainers/testcontainers-python/issues/482 --- .github/workflows/ci-community.yml | 6 +++--- .github/workflows/ci-core.yml | 3 +-- .github/workflows/ci-lint.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/pr-lint.yml | 2 +- .github/workflows/release-please.yml | 4 ++-- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 9284463cc..caebace06 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -14,7 +14,7 @@ on: jobs: track-modules: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout contents uses: actions/checkout@v4 @@ -38,14 +38,14 @@ jobs: outputs: changed_modules: ${{ steps.compute-changes.outputs.computed_modules }} test: + runs-on: ubuntu-22.04 needs: [track-modules] if: ${{ needs.track-modules.outputs.changed_modules != '[]' }} strategy: fail-fast: false matrix: - python-version: [ "3.11" ] + python-version: ["3.9", "3.10", "3.11", "3.12"] module: ${{ fromJSON(needs.track-modules.outputs.changed_modules) }} - runs-on: ubuntu-latest steps: - name: Checkout contents uses: actions/checkout@v4 diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index c39eb1ea0..f794f98e3 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -10,12 +10,11 @@ on: jobs: test: + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - os: [ ubuntu ] python-version: ["3.9", "3.10", "3.11", "3.12"] - runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v4 - name: Set up Python diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index a02136ece..f9da3b409 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -10,7 +10,7 @@ on: jobs: python: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Setup Env diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1dfb6c711..e27c89ad0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Set up Python diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index b99c3074a..84d805903 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -13,7 +13,7 @@ permissions: jobs: validate: name: validate-pull-request-title - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: validate pull request title uses: kontrolplane/pull-request-title-validator@ab2b54babb5337246f4b55cf8e0a1ecb0575e46d #v1 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index a5a9821a2..9176c6747 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -6,7 +6,7 @@ on: jobs: release: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: release_created: ${{ steps.track-release.outputs.release_created }} steps: @@ -16,7 +16,7 @@ jobs: manifest-file: .github/.release-please-manifest.json config-file: .github/release-please-config.json publish: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 environment: release permissions: id-token: write From 0729bf4af957f8b6638cc204b108358745c0cfc9 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 30 Mar 2024 19:52:11 -0400 Subject: [PATCH 333/425] fix: add chroma container (#515) based on #477 --------- Co-authored-by: Trayan Azarov --- index.rst | 1 + modules/chroma/README.rst | 2 + .../chroma/testcontainers/chroma/__init__.py | 81 +++++ modules/chroma/tests/test_chroma.py | 9 + poetry.lock | 299 +++++++++++++++++- pyproject.toml | 3 + 6 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 modules/chroma/README.rst create mode 100644 modules/chroma/testcontainers/chroma/__init__.py create mode 100644 modules/chroma/tests/test_chroma.py diff --git a/index.rst b/index.rst index a716e09a8..d9d4a010a 100644 --- a/index.rst +++ b/index.rst @@ -17,6 +17,7 @@ testcontainers-python facilitates the use of Docker containers for functional an core/README modules/arangodb/README modules/azurite/README + modules/chroma/README modules/clickhouse/README modules/elasticsearch/README modules/google/README diff --git a/modules/chroma/README.rst b/modules/chroma/README.rst new file mode 100644 index 000000000..f4e3199fe --- /dev/null +++ b/modules/chroma/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.chroma.ChromaContainer +.. title:: testcontainers.minio.ChromaContainer diff --git a/modules/chroma/testcontainers/chroma/__init__.py b/modules/chroma/testcontainers/chroma/__init__.py new file mode 100644 index 000000000..9e5744099 --- /dev/null +++ b/modules/chroma/testcontainers/chroma/__init__.py @@ -0,0 +1,81 @@ +from typing import TYPE_CHECKING + +from requests import ConnectionError, get + +from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.waiting_utils import wait_container_is_ready + +if TYPE_CHECKING: + from requests import Response + + +class ChromaContainer(DockerContainer): + """ + The example below spins up a ChromaDB container, performs a healthcheck and creates a collection. + The method :code:`get_client` can be used to create a client for the Chroma Python Client. + + Example: + + .. doctest:: + + >>> import chromadb + >>> from testcontainers.chroma import ChromaContainer + + >>> with ChromaContainer() as chroma: + ... config = chroma.get_config() + ... client = chromadb.HttpClient(host=config["host"], port=config["port"]) + ... col = client.get_or_create_collection("test") + ... col.name + 'test' + """ + + def __init__( + self, + image: str = "chromadb/chroma:latest", + port: int = 8000, + **kwargs, + ) -> None: + """ + Args: + image: Docker image to use for the MinIO container. + port: Port to expose on the container. + access_key: Access key for client connections. + secret_key: Secret key for client connections. + """ + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") + super().__init__(image, **kwargs) + self.port = port + + self.with_exposed_ports(self.port) + # self.with_command(f"server /data --address :{self.port}") + + def get_config(self) -> dict: + """This method returns the configuration of the Chroma container, + including the endpoint. + + Returns: + dict: {`endpoint`: str} + """ + host_ip = self.get_container_host_ip() + exposed_port = self.get_exposed_port(self.port) + return { + "endpoint": f"{host_ip}:{exposed_port}", + "host": host_ip, + "port": exposed_port, + } + + @wait_container_is_ready(ConnectionError) + def _healthcheck(self) -> None: + """This is an internal method used to check if the Chroma container + is healthy and ready to receive requests.""" + url = f"http://{self.get_config()['endpoint']}/api/v1/heartbeat" + response: Response = get(url) + response.raise_for_status() + + def start(self) -> "ChromaContainer": + """This method starts the Chroma container and runs the healthcheck + to verify that the container is ready to use.""" + super().start() + self._healthcheck() + return self diff --git a/modules/chroma/tests/test_chroma.py b/modules/chroma/tests/test_chroma.py new file mode 100644 index 000000000..fee55b788 --- /dev/null +++ b/modules/chroma/tests/test_chroma.py @@ -0,0 +1,9 @@ +from testcontainers.chroma import ChromaContainer +import chromadb + + +def test_docker_run_chroma(): + with ChromaContainer(image="chromadb/chroma:0.4.24") as chroma: + client = chromadb.HttpClient(host=chroma.get_config()["host"], port=chroma.get_config()["port"]) + col = client.get_or_create_collection("test") + assert col.name == "test" diff --git a/poetry.lock b/poetry.lock index 0622fbabb..a3f795aae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -209,6 +209,17 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = true +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "boto3" version = "1.34.59" @@ -446,6 +457,31 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "chromadb-client" +version = "0.4.25.dev0" +description = "Chroma Client." +optional = true +python-versions = ">=3.8" +files = [ + {file = "chromadb-client-0.4.25.dev0.tar.gz", hash = "sha256:18762d04720db1ca9ac6347ecd04371064e414b22401aadc2e78a1893fd46595"}, + {file = "chromadb_client-0.4.25.dev0-py3-none-any.whl", hash = "sha256:da52dd28e02bb168be6ab82177726c27f770f5c190ef7c3484b12c6014f2cc07"}, +] + +[package.dependencies] +numpy = ">=1.22.5" +opentelemetry-api = ">=1.2.0" +opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0" +opentelemetry-sdk = ">=1.2.0" +orjson = ">=3.9.12" +overrides = ">=7.3.1" +posthog = ">=2.4.0" +pydantic = ">=1.9" +PyYAML = ">=6.0.0" +requests = ">=2.28" +tenacity = ">=8.2.3" +typing-extensions = ">=4.5.0" + [[package]] name = "clickhouse-driver" version = "0.2.7" @@ -725,6 +761,23 @@ files = [ {file = "cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9"}, ] +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "deprecation" version = "2.1.0" @@ -1664,6 +1717,17 @@ pycryptodome = "*" typing-extensions = "*" urllib3 = "*" +[[package]] +name = "monotonic" +version = "1.6" +description = "An implementation of time.monotonic() for Python 2 & < 3.3" +optional = true +python-versions = "*" +files = [ + {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, + {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, +] + [[package]] name = "more-itertools" version = "10.2.0" @@ -1856,6 +1920,51 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = true +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + [[package]] name = "oauthlib" version = "3.2.2" @@ -1896,6 +2005,145 @@ develop = ["black", "botocore", "coverage (<8.0.0)", "jinja2", "mock", "myst-par docs = ["aiohttp (>=3,<4)", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] kerberos = ["requests-kerberos"] +[[package]] +name = "opentelemetry-api" +version = "1.16.0" +description = "OpenTelemetry Python API" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_api-1.16.0-py3-none-any.whl", hash = "sha256:79e8f0cf88dbdd36b6abf175d2092af1efcaa2e71552d0d2b3b181a9707bf4bc"}, + {file = "opentelemetry_api-1.16.0.tar.gz", hash = "sha256:4b0e895a3b1f5e1908043ebe492d33e33f9ccdbe6d02d3994c2f8721a63ddddb"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +setuptools = ">=16.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.16.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.16.0-py3-none-any.whl", hash = "sha256:ace2cedc43bc30e1b2475b14f72acf1a1528716965209d31fb0a72c59f0f4fe4"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.16.0.tar.gz", hash = "sha256:0853ea1e566c1fab5633e7f7bca2a650ba445b04ba02f93173920b0f5c561f63"}, +] + +[package.dependencies] +backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-proto = "1.16.0" +opentelemetry-sdk = ">=1.16.0,<1.17.0" + +[package.extras] +test = ["pytest-grpc"] + +[[package]] +name = "opentelemetry-proto" +version = "1.16.0" +description = "OpenTelemetry Python Proto" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_proto-1.16.0-py3-none-any.whl", hash = "sha256:160326d300faf43c3f72c4a916516ee5b63289ceb9828294b698ef943697cbd5"}, + {file = "opentelemetry_proto-1.16.0.tar.gz", hash = "sha256:e58832dfec64621972a9836f8ae163fb3063946eb02bdf43fae0f76f8cf46d0a"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.16.0" +description = "OpenTelemetry Python SDK" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_sdk-1.16.0-py3-none-any.whl", hash = "sha256:15f03915eec4839f885a5e6ed959cde59b8690c8c012d07c95b4b138c98dc43f"}, + {file = "opentelemetry_sdk-1.16.0.tar.gz", hash = "sha256:4d3bb91e9e209dbeea773b5565d901da4f76a29bf9dbc1c9500be3cabb239a4e"}, +] + +[package.dependencies] +opentelemetry-api = "1.16.0" +opentelemetry-semantic-conventions = "0.37b0" +setuptools = ">=16.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.37b0" +description = "OpenTelemetry Semantic Conventions" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_semantic_conventions-0.37b0-py3-none-any.whl", hash = "sha256:462982278a42dab01f68641cd89f8460fe1f93e87c68a012a76fb426dcdba5ee"}, + {file = "opentelemetry_semantic_conventions-0.37b0.tar.gz", hash = "sha256:087ce2e248e42f3ffe4d9fa2303111de72bb93baa06a0f4655980bc1557c4228"}, +] + +[[package]] +name = "orjson" +version = "3.10.0" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = true +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47af5d4b850a2d1328660661f0881b67fdbe712aea905dadd413bdea6f792c33"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c90681333619d78360d13840c7235fdaf01b2b129cb3a4f1647783b1971542b6"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:400c5b7c4222cb27b5059adf1fb12302eebcabf1978f33d0824aa5277ca899bd"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5dcb32e949eae80fb335e63b90e5808b4b0f64e31476b3777707416b41682db5"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7d507c7493252c0a0264b5cc7e20fa2f8622b8a83b04d819b5ce32c97cf57b"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e286a51def6626f1e0cc134ba2067dcf14f7f4b9550f6dd4535fd9d79000040b"}, + {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8acd4b82a5f3a3ec8b1dc83452941d22b4711964c34727eb1e65449eead353ca"}, + {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:30707e646080dd3c791f22ce7e4a2fc2438765408547c10510f1f690bd336217"}, + {file = "orjson-3.10.0-cp310-none-win32.whl", hash = "sha256:115498c4ad34188dcb73464e8dc80e490a3e5e88a925907b6fedcf20e545001a"}, + {file = "orjson-3.10.0-cp310-none-win_amd64.whl", hash = "sha256:6735dd4a5a7b6df00a87d1d7a02b84b54d215fb7adac50dd24da5997ffb4798d"}, + {file = "orjson-3.10.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9587053e0cefc284e4d1cd113c34468b7d3f17666d22b185ea654f0775316a26"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bef1050b1bdc9ea6c0d08468e3e61c9386723633b397e50b82fda37b3563d72"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d16c6963ddf3b28c0d461641517cd312ad6b3cf303d8b87d5ef3fa59d6844337"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4251964db47ef090c462a2d909f16c7c7d5fe68e341dabce6702879ec26d1134"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73bbbdc43d520204d9ef0817ac03fa49c103c7f9ea94f410d2950755be2c349c"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:414e5293b82373606acf0d66313aecb52d9c8c2404b1900683eb32c3d042dbd7"}, + {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:feaed5bb09877dc27ed0d37f037ddef6cb76d19aa34b108db270d27d3d2ef747"}, + {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5127478260db640323cea131ee88541cb1a9fbce051f0b22fa2f0892f44da302"}, + {file = "orjson-3.10.0-cp311-none-win32.whl", hash = "sha256:b98345529bafe3c06c09996b303fc0a21961820d634409b8639bc16bd4f21b63"}, + {file = "orjson-3.10.0-cp311-none-win_amd64.whl", hash = "sha256:658ca5cee3379dd3d37dbacd43d42c1b4feee99a29d847ef27a1cb18abdfb23f"}, + {file = "orjson-3.10.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4329c1d24fd130ee377e32a72dc54a3c251e6706fccd9a2ecb91b3606fddd998"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef0f19fdfb6553342b1882f438afd53c7cb7aea57894c4490c43e4431739c700"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4f60db24161534764277f798ef53b9d3063092f6d23f8f962b4a97edfa997a0"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1de3fd5c7b208d836f8ecb4526995f0d5877153a4f6f12f3e9bf11e49357de98"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f93e33f67729d460a177ba285002035d3f11425ed3cebac5f6ded4ef36b28344"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:237ba922aef472761acd697eef77fef4831ab769a42e83c04ac91e9f9e08fa0e"}, + {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98c1bfc6a9bec52bc8f0ab9b86cc0874b0299fccef3562b793c1576cf3abb570"}, + {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30d795a24be16c03dca0c35ca8f9c8eaaa51e3342f2c162d327bd0225118794a"}, + {file = "orjson-3.10.0-cp312-none-win32.whl", hash = "sha256:6a3f53dc650bc860eb26ec293dfb489b2f6ae1cbfc409a127b01229980e372f7"}, + {file = "orjson-3.10.0-cp312-none-win_amd64.whl", hash = "sha256:983db1f87c371dc6ffc52931eb75f9fe17dc621273e43ce67bee407d3e5476e9"}, + {file = "orjson-3.10.0-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a667769a96a72ca67237224a36faf57db0c82ab07d09c3aafc6f956196cfa1b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade1e21dfde1d37feee8cf6464c20a2f41fa46c8bcd5251e761903e46102dc6b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23c12bb4ced1c3308eff7ba5c63ef8f0edb3e4c43c026440247dd6c1c61cea4b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2d014cf8d4dc9f03fc9f870de191a49a03b1bcda51f2a957943fb9fafe55aac"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eadecaa16d9783affca33597781328e4981b048615c2ddc31c47a51b833d6319"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd583341218826f48bd7c6ebf3310b4126216920853cbc471e8dbeaf07b0b80e"}, + {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90bfc137c75c31d32308fd61951d424424426ddc39a40e367704661a9ee97095"}, + {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13b5d3c795b09a466ec9fcf0bd3ad7b85467d91a60113885df7b8d639a9d374b"}, + {file = "orjson-3.10.0-cp38-none-win32.whl", hash = "sha256:5d42768db6f2ce0162544845facb7c081e9364a5eb6d2ef06cd17f6050b048d8"}, + {file = "orjson-3.10.0-cp38-none-win_amd64.whl", hash = "sha256:33e6655a2542195d6fd9f850b428926559dee382f7a862dae92ca97fea03a5ad"}, + {file = "orjson-3.10.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4050920e831a49d8782a1720d3ca2f1c49b150953667eed6e5d63a62e80f46a2"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1897aa25a944cec774ce4a0e1c8e98fb50523e97366c637b7d0cddabc42e6643"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bf565a69e0082ea348c5657401acec3cbbb31564d89afebaee884614fba36b4"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6ebc17cfbbf741f5c1a888d1854354536f63d84bee537c9a7c0335791bb9009"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2817877d0b69f78f146ab305c5975d0618df41acf8811249ee64231f5953fee"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57d017863ec8aa4589be30a328dacd13c2dc49de1c170bc8d8c8a98ece0f2925"}, + {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:22c2f7e377ac757bd3476ecb7480c8ed79d98ef89648f0176deb1da5cd014eb7"}, + {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e62ba42bfe64c60c1bc84799944f80704e996592c6b9e14789c8e2a303279912"}, + {file = "orjson-3.10.0-cp39-none-win32.whl", hash = "sha256:60c0b1bdbccd959ebd1575bd0147bd5e10fc76f26216188be4a36b691c937077"}, + {file = "orjson-3.10.0-cp39-none-win_amd64.whl", hash = "sha256:175a41500ebb2fdf320bf78e8b9a75a1279525b62ba400b2b2444e274c2c8bee"}, + {file = "orjson-3.10.0.tar.gz", hash = "sha256:ba4d8cac5f2e2cff36bea6b6481cdb92b38c202bcec603d6f5ff91960595a1ed"}, +] + [[package]] name = "outcome" version = "1.3.0.post0" @@ -1910,6 +2158,17 @@ files = [ [package.dependencies] attrs = ">=19.2.0" +[[package]] +name = "overrides" +version = "7.7.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = true +python-versions = ">=3.6" +files = [ + {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, + {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, +] + [[package]] name = "packaging" version = "24.0" @@ -1996,6 +2255,29 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "posthog" +version = "3.5.0" +description = "Integrate PostHog into any python application." +optional = true +python-versions = "*" +files = [ + {file = "posthog-3.5.0-py2.py3-none-any.whl", hash = "sha256:3c672be7ba6f95d555ea207d4486c171d06657eb34b3ce25eb043bfe7b6b5b76"}, + {file = "posthog-3.5.0.tar.gz", hash = "sha256:8f7e3b2c6e8714d0c0c542a2109b83a7549f63b7113a133ab2763a89245ef2ef"}, +] + +[package.dependencies] +backoff = ">=1.10.0" +monotonic = ">=1.5" +python-dateutil = ">2.1" +requests = ">=2.7,<3.0" +six = ">=1.5" + +[package.extras] +dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"] +sentry = ["django", "sentry-sdk"] +test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest", "pytest-timeout"] + [[package]] name = "pre-commit" version = "3.6.2" @@ -3276,6 +3558,20 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +optional = true +python-versions = ">=3.7" +files = [ + {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, + {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, +] + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + [[package]] name = "tomli" version = "2.0.1" @@ -3613,6 +3909,7 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [extras] arangodb = ["python-arango"] azurite = ["azure-storage-blob"] +chroma = ["chromadb-client"] clickhouse = ["clickhouse-driver"] elasticsearch = [] google = ["google-cloud-datastore", "google-cloud-pubsub"] @@ -3638,4 +3935,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "15e0e0ba774e0e8babe3ed56b7c16f364790cc0275501dbe9bb317c2d2b1f9b0" +content-hash = "173a16b21517cede7cb30c8d67444bd179ed719015f78649f574e564c253ee81" diff --git a/pyproject.toml b/pyproject.toml index 6242a6f8f..dff8fd382 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ packages = [ { include = "testcontainers", from = "core" }, { include = "testcontainers", from = "modules/arangodb" }, { include = "testcontainers", from = "modules/azurite" }, + { include = "testcontainers", from = "modules/chroma" }, { include = "testcontainers", from = "modules/clickhouse" }, { include = "testcontainers", from = "modules/elasticsearch" }, { include = "testcontainers", from = "modules/google" }, @@ -87,6 +88,7 @@ pika = { version = "*", optional = true } redis = { version = "*", optional = true } selenium = { version = "*", optional = true } weaviate-client = { version = "^4.5.4", optional = true } +chromadb-client = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -112,6 +114,7 @@ rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] weaviate = ["weaviate-client"] +chroma = ["chromadb-client"] [tool.poetry.group.dev.dependencies] mypy = "1.7.1" From 507e466a1fa9ac64c254ceb9ae0d57f6bfd8c89d Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Sun, 31 Mar 2024 01:12:09 +0100 Subject: [PATCH 334/425] fix: Add CassandraContainer (#476) Co-authored-by: David Ankin --- .github/settings.yml | 1 + index.rst | 1 + modules/cassandra/README.rst | 2 + .../testcontainers/cassandra/__init__.py | 62 +++++++++++++++ modules/cassandra/tests/test_cassandra.py | 14 ++++ poetry.lock | 79 ++++++++++++++++++- pyproject.toml | 4 + 7 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 modules/cassandra/README.rst create mode 100644 modules/cassandra/testcontainers/cassandra/__init__.py create mode 100644 modules/cassandra/tests/test_cassandra.py diff --git a/.github/settings.yml b/.github/settings.yml index e72584e6f..122fd660d 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -63,6 +63,7 @@ labels: - { name: '🐧 linux', color: '#3ED4D',, description: '' } - { name: '👀 requires attention', color: '#fef2c0', description: '' } - { name: '📖 documentation', color: '#d93f0b', description: '' } + - { name: '📦 package: cassandra', color: '#0052CC', description: '' } - { name: '📦 package: clickhouse', color: '#0052CC', description: '' } - { name: '📦 package: compose', color: '#0052CC', description: '' } - { name: '📦 package: core', color: '#0052CC', description: '' } diff --git a/index.rst b/index.rst index d9d4a010a..90cf7ba6f 100644 --- a/index.rst +++ b/index.rst @@ -17,6 +17,7 @@ testcontainers-python facilitates the use of Docker containers for functional an core/README modules/arangodb/README modules/azurite/README + modules/cassandra/README modules/chroma/README modules/clickhouse/README modules/elasticsearch/README diff --git a/modules/cassandra/README.rst b/modules/cassandra/README.rst new file mode 100644 index 000000000..44216d6be --- /dev/null +++ b/modules/cassandra/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.cassandra.CassandraContainer +.. title:: testcontainers.cassandra.CassandraContainer diff --git a/modules/cassandra/testcontainers/cassandra/__init__.py b/modules/cassandra/testcontainers/cassandra/__init__.py new file mode 100644 index 000000000..4e6618b7b --- /dev/null +++ b/modules/cassandra/testcontainers/cassandra/__init__.py @@ -0,0 +1,62 @@ +# +# 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. +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +class CassandraContainer(DockerContainer): + """ + Cassandra database container. + + Example: + + .. doctest:: + + >>> from testcontainers.cassandra import CassandraContainer + >>> from cassandra.cluster import Cluster, DCAwareRoundRobinPolicy + + >>> with CassandraContainer("cassandra:4.1.4") as cassandra, Cluster( + ... cassandra.get_contact_points(), + ... load_balancing_policy=DCAwareRoundRobinPolicy(cassandra.get_local_datacenter()), + ... ) as cluster: + ... session = cluster.connect() + ... result = session.execute("SELECT release_version FROM system.local;") + ... result.one().release_version + '4.1.4' + """ + + CQL_PORT = 9042 + DEFAULT_LOCAL_DATACENTER = "datacenter1" + + def __init__(self, image: str = "cassandra:latest", **kwargs) -> None: + super().__init__(image=image, **kwargs) + self.with_exposed_ports(self.CQL_PORT) + self.with_env("JVM_OPTS", "-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0") + self.with_env("HEAP_NEWSIZE", "128M") + self.with_env("MAX_HEAP_SIZE", "1024M") + self.with_env("CASSANDRA_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch") + self.with_env("CASSANDRA_DC", self.DEFAULT_LOCAL_DATACENTER) + + def _connect(self): + wait_for_logs(self, "Startup complete") + + def start(self) -> "CassandraContainer": + super().start() + self._connect() + return self + + def get_contact_points(self) -> list[tuple[str, int]]: + return [(self.get_container_host_ip(), int(self.get_exposed_port(self.CQL_PORT)))] + + def get_local_datacenter(self) -> str: + return self.env.get("CASSANDRA_DC", self.DEFAULT_LOCAL_DATACENTER) diff --git a/modules/cassandra/tests/test_cassandra.py b/modules/cassandra/tests/test_cassandra.py new file mode 100644 index 000000000..1aa5858b7 --- /dev/null +++ b/modules/cassandra/tests/test_cassandra.py @@ -0,0 +1,14 @@ +from cassandra.cluster import Cluster, DCAwareRoundRobinPolicy + +from testcontainers.cassandra import CassandraContainer + + +def test_docker_run_cassandra(): + with CassandraContainer("cassandra:4.1.4") as cassandra: + cluster = Cluster( + cassandra.get_contact_points(), + load_balancing_policy=DCAwareRoundRobinPolicy(cassandra.get_local_datacenter()), + ) + session = cluster.connect() + result = session.execute("SELECT release_version FROM system.local;") + assert result.one().release_version == "4.1.4" diff --git a/poetry.lock b/poetry.lock index a3f795aae..f05b8b4b5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -272,6 +272,53 @@ files = [ {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] +[[package]] +name = "cassandra-driver" +version = "3.29.1" +description = "DataStax Driver for Apache Cassandra" +optional = false +python-versions = "*" +files = [ + {file = "cassandra-driver-3.29.1.tar.gz", hash = "sha256:38e9c2a2f2a9664bb03f1f852d5fccaeff2163942b5db35dffcf8bf32a51cfe5"}, + {file = "cassandra_driver-3.29.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8f175c7616a63ca48cb8bd4acc443e2a3d889964d5157cead761f23cc8db7bd"}, + {file = "cassandra_driver-3.29.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d66398952b9cd21c40edff56e22b6d3bce765edc94b207ddb5896e7bc9aa088"}, + {file = "cassandra_driver-3.29.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bbc6f575ef109ce5d4abfa2033bf36c394032abd83e32ab671159ce68e7e17b"}, + {file = "cassandra_driver-3.29.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78f241af75696adb3e470209e2fbb498804c99e2b197d24d74774eee6784f283"}, + {file = "cassandra_driver-3.29.1-cp310-cp310-win32.whl", hash = "sha256:54d9e651a742d6ca3d874ef8d06a40fa032d2dba97142da2d36f60c5675e39f8"}, + {file = "cassandra_driver-3.29.1-cp310-cp310-win_amd64.whl", hash = "sha256:630dc5423cd40eba0ee9db31065e2238098ff1a25a6b1bd36360f85738f26e4b"}, + {file = "cassandra_driver-3.29.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b841d38c96bb878d31df393954863652d6d3a85f47bcc00fd1d70a5ea73023f"}, + {file = "cassandra_driver-3.29.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19cc7375f673e215bd4cbbefae2de9f07830be7dabef55284a2d2ff8d8691efe"}, + {file = "cassandra_driver-3.29.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b74b355be3dcafe652fffda8f14f385ccc1a8dae9df28e6080cc660da39b45f"}, + {file = "cassandra_driver-3.29.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e6dac7eddd3f4581859f180383574068a3f113907811b4dad755a8ace4c3fbd"}, + {file = "cassandra_driver-3.29.1-cp311-cp311-win32.whl", hash = "sha256:293a79dba417112b56320ed0013d71fd7520f5fc4a5fd2ac8000c762c6dd5b07"}, + {file = "cassandra_driver-3.29.1-cp311-cp311-win_amd64.whl", hash = "sha256:7c2374fdf1099047a6c9c8329c79d71ad11e61d9cca7de92a0f49655da4bdd8a"}, + {file = "cassandra_driver-3.29.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4431a0c836f33a33c733c84997fbdb6398be005c4d18a8c8525c469fdc29393c"}, + {file = "cassandra_driver-3.29.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23b08381b171a9e42ace483a82457edcddada9e8367e31677b97538cde2dc34"}, + {file = "cassandra_driver-3.29.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4beb29a0139e63a10a5b9a3c7b72c30a4e6e20c9f0574f9d22c0d4144fe3d348"}, + {file = "cassandra_driver-3.29.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b206423cc454a78f16b411e7cb641dddc26168ac2e18f2c13665f5f3c89868c"}, + {file = "cassandra_driver-3.29.1-cp312-cp312-win32.whl", hash = "sha256:ac898cca7303a3a2a3070513eee12ef0f1be1a0796935c5b8aa13dae8c0a7f7e"}, + {file = "cassandra_driver-3.29.1-cp312-cp312-win_amd64.whl", hash = "sha256:4ad0c9fb2229048ad6ff8c6ddbf1fdc78b111f2b061c66237c2257fcc4a31b14"}, + {file = "cassandra_driver-3.29.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4282c5deac462e4bb0f6fd0553a33d514dbd5ee99d0812594210080330ddd1a2"}, + {file = "cassandra_driver-3.29.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:41ca7eea069754002418d3bdfbd3dfd150ea12cb9db474ab1a01fa4679a05bcb"}, + {file = "cassandra_driver-3.29.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6639ccb268c4dc754bc45e03551711780d0e02cb298ab26cde1f42b7bcc74f8"}, + {file = "cassandra_driver-3.29.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a9d7d3b1be24a7f113b5404186ccccc977520401303a8fe78ba34134cad2482"}, + {file = "cassandra_driver-3.29.1-cp38-cp38-win32.whl", hash = "sha256:81c8fd556c6e1bb93577e69c1f10a3fadf7ddb93958d226ccbb72389396e9a92"}, + {file = "cassandra_driver-3.29.1-cp38-cp38-win_amd64.whl", hash = "sha256:cfe70ed0f27af949de2767ea9cef4092584e8748759374a55bf23c30746c7b23"}, + {file = "cassandra_driver-3.29.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2c03c1d834ac1a0ae39f9af297a8cd38829003ce910b08b324fb3abe488ce2b"}, + {file = "cassandra_driver-3.29.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9a3e1e2b01f3b7a5cf75c97401bce830071d99c42464352087d7475e0161af93"}, + {file = "cassandra_driver-3.29.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90c42006665a4e490b0766b70f3d637f36a30accbef2da35d6d4081c0e0bafc3"}, + {file = "cassandra_driver-3.29.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c1aca41f45772f9759e8246030907d92bc35fbbdc91525a3cb9b49939b80ad7"}, + {file = "cassandra_driver-3.29.1-cp39-cp39-win32.whl", hash = "sha256:ce4a66245d4a0c8b07fdcb6398698c2c42eb71245fb49cff39435bb702ff7be6"}, + {file = "cassandra_driver-3.29.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cae69ceb1b1d9383e988a1b790115253eacf7867ceb15ed2adb736e3ce981be"}, +] + +[package.dependencies] +geomet = ">=0.1,<0.3" + +[package.extras] +cle = ["cryptography (>=35.0)"] +graph = ["gremlinpython (==3.4.6)"] + [[package]] name = "certifi" version = "2024.2.2" @@ -482,6 +529,20 @@ requests = ">=2.28" tenacity = ">=8.2.3" typing-extensions = ">=4.5.0" +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "clickhouse-driver" version = "0.2.7" @@ -885,6 +946,21 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "geomet" +version = "0.2.1.post1" +description = "GeoJSON <-> WKT/WKB conversion utilities" +optional = false +python-versions = ">2.6, !=3.3.*, <4" +files = [ + {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, + {file = "geomet-0.2.1.post1.tar.gz", hash = "sha256:91d754f7c298cbfcabd3befdb69c641c27fe75e808b27aa55028605761d17e95"}, +] + +[package.dependencies] +click = "*" +six = "*" + [[package]] name = "google-api-core" version = "2.17.1" @@ -3909,6 +3985,7 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [extras] arangodb = ["python-arango"] azurite = ["azure-storage-blob"] +cassandra = [] chroma = ["chromadb-client"] clickhouse = ["clickhouse-driver"] elasticsearch = [] @@ -3935,4 +4012,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "173a16b21517cede7cb30c8d67444bd179ed719015f78649f574e564c253ee81" +content-hash = "c5659a08d1acbd86baa459df9d85b647e6611c546b955a702242558ca0fa0c9d" diff --git a/pyproject.toml b/pyproject.toml index dff8fd382..c6a691c9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ packages = [ { include = "testcontainers", from = "core" }, { include = "testcontainers", from = "modules/arangodb" }, { include = "testcontainers", from = "modules/azurite" }, + { include = "testcontainers", from = "modules/cassandra" }, { include = "testcontainers", from = "modules/chroma" }, { include = "testcontainers", from = "modules/clickhouse" }, { include = "testcontainers", from = "modules/elasticsearch" }, @@ -93,6 +94,7 @@ chromadb-client = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] azurite = ["azure-storage-blob"] +cassandra = ["cassandra-driver"] clickhouse = ["clickhouse-driver"] elasticsearch = [] google = ["google-cloud-pubsub", "google-cloud-datastore"] @@ -130,6 +132,7 @@ pg8000 = "*" sqlalchemy = "*" psycopg = "*" kafka-python = "^2.0.2" +cassandra-driver = "*" [[tool.poetry.source]] name = "PyPI" @@ -228,6 +231,7 @@ mypy_path = [ "core", # "modules/arangodb", # "modules/azurite", +# "modules/cassandra", # "modules/clickhouse", # "modules/elasticsearch", # "modules/google", From e8876f422abeb29a7236f2174f7e7a324b7d26cb Mon Sep 17 00:00:00 2001 From: Anush Date: Sun, 31 Mar 2024 05:57:59 +0530 Subject: [PATCH 335/425] fix: Qdrant module (#463) This PR adds a module to spawn a [Qdrant](https://qdrant.tech) test container. --------- Co-authored-by: David Ankin --- conf.py | 5 + index.rst | 1 + modules/qdrant/README.rst | 2 + .../qdrant/testcontainers/qdrant/__init__.py | 156 ++++++++++++++++++ modules/qdrant/tests/test_config.yaml | 6 + modules/qdrant/tests/test_qdrant.py | 80 +++++++++ poetry.lock | 86 +++++++++- pyproject.toml | 3 + 8 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 modules/qdrant/README.rst create mode 100644 modules/qdrant/testcontainers/qdrant/__init__.py create mode 100644 modules/qdrant/tests/test_config.yaml create mode 100644 modules/qdrant/tests/test_qdrant.py diff --git a/conf.py b/conf.py index 4c5ff938a..e95d3d135 100644 --- a/conf.py +++ b/conf.py @@ -31,6 +31,7 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", + "sphinx.ext.intersphinx", "sphinx.ext.napoleon", ] @@ -156,3 +157,7 @@ "Miscellaneous", ), ] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} diff --git a/index.rst b/index.rst index 90cf7ba6f..e9fccc23a 100644 --- a/index.rst +++ b/index.rst @@ -36,6 +36,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/opensearch/README modules/oracle/README modules/postgres/README + modules/qdrant/README modules/rabbitmq/README modules/redis/README modules/selenium/README diff --git a/modules/qdrant/README.rst b/modules/qdrant/README.rst new file mode 100644 index 000000000..643096a37 --- /dev/null +++ b/modules/qdrant/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.qdrant.QdrantContainer +.. title:: testcontainers.qdrant.QdrantContainer diff --git a/modules/qdrant/testcontainers/qdrant/__init__.py b/modules/qdrant/testcontainers/qdrant/__init__.py new file mode 100644 index 000000000..ac9279955 --- /dev/null +++ b/modules/qdrant/testcontainers/qdrant/__init__.py @@ -0,0 +1,156 @@ +# +# 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 os +from functools import cached_property +from pathlib import Path +from typing import Optional + +from testcontainers.core.config import TIMEOUT +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class QdrantContainer(DbContainer): + """ + Qdrant vector database container. + + Example: + .. doctest:: + + >>> from testcontainers.qdrant import QdrantContainer + + >>> with QdrantContainer() as qdrant: + ... client = qdrant.get_client() + ... client.get_collections() + CollectionsResponse(collections=[]) + """ + + QDRANT_CONFIG_FILE_PATH = "/qdrant/config/config.yaml" + + def __init__( + self, + image: str = "qdrant/qdrant:v1.8.3", + rest_port: int = 6333, + grpc_port: int = 6334, + api_key: Optional[str] = None, + config_file_path: Optional[Path] = None, + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + self._rest_port = rest_port + self._grpc_port = grpc_port + self._api_key = api_key or os.getenv("QDRANT_CONTAINER_API_KEY") + + if config_file_path: + self.with_volume_mapping(host=str(config_file_path), container=QdrantContainer.QDRANT_CONFIG_FILE_PATH) + + self.with_exposed_ports(self._rest_port, self._grpc_port) + + def _configure(self) -> None: + self.with_env("QDRANT__SERVICE__API_KEY", self._api_key) + + @wait_container_is_ready() + def _connect(self) -> None: + wait_for_logs(self, ".*Actix runtime found; starting in Actix runtime.*", TIMEOUT) + + def get_client(self, **kwargs): + """ + Get a `qdrant_client.QdrantClient` instance associated with the container. + + Args: + **kwargs: Additional keyword arguments to be passed to the `qdrant_client.QdrantClient` constructor. + + Returns: + QdrantClient: An instance of the `qdrant_client.QdrantClient` class. + + """ + + try: + from qdrant_client import QdrantClient + except ImportError as e: + raise ImportError("To use the `get_client` method, you must install the `qdrant_client` package.") from e + return QdrantClient( + host=self.get_container_host_ip(), + port=self.get_exposed_port(self._rest_port), + grpc_port=self.get_exposed_port(self._grpc_port), + api_key=self._api_key, + https=False, + **kwargs, + ) + + def get_async_client(self, **kwargs): + """ + Get a `qdrant_client.AsyncQdrantClient` instance associated with the container. + + Args: + **kwargs: Additional keyword arguments to be passed to the `qdrant_client.AsyncQdrantClient` constructor. + + Returns: + QdrantClient: An instance of the `qdrant_client.AsyncQdrantClient` class. + + """ + + try: + from qdrant_client import AsyncQdrantClient + except ImportError as e: + raise ImportError( + "To use the `get_async_client` method, you must install the `qdrant_client` package." + ) from e + return AsyncQdrantClient( + host=self.get_container_host_ip(), + port=self.get_exposed_port(self._rest_port), + grpc_port=self.get_exposed_port(self._grpc_port), + api_key=self._api_key, + https=False, + **kwargs, + ) + + @cached_property + def rest_host_address(self) -> str: + """ + Get the REST host address of the Qdrant container. + + Returns: + str: The REST host address of the Qdrant container. + """ + return f"{self.get_container_host_ip()}:{self.exposed_rest_port}" + + @cached_property + def grpc_host_address(self) -> str: + """ + Get the GRPC host address of the Qdrant container. + + Returns: + str: The GRPC host address of the Qdrant container. + """ + return f"{self.get_container_host_ip()}:{self.exposed_grpc_port}" + + @cached_property + def exposed_rest_port(self) -> int: + """ + Get the exposed REST port of the Qdrant container. + + Returns: + int: The REST port of the Qdrant container. + """ + return self.get_exposed_port(self._rest_port) + + @cached_property + def exposed_grpc_port(self) -> int: + """ + Get the exposed GRPC port of the Qdrant container. + + Returns: + int: The GRPC port of the Qdrant container. + """ + return self.get_exposed_port(self._grpc_port) diff --git a/modules/qdrant/tests/test_config.yaml b/modules/qdrant/tests/test_config.yaml new file mode 100644 index 000000000..7b13dabd1 --- /dev/null +++ b/modules/qdrant/tests/test_config.yaml @@ -0,0 +1,6 @@ +# Qdrant image configuration file for testing +# Reference: https://qdrant.tech/documentation/guides/configuration/#configuration-file-example +log_level: INFO + +service: + api_key: "SOME_TEST_KEY" diff --git a/modules/qdrant/tests/test_qdrant.py b/modules/qdrant/tests/test_qdrant.py new file mode 100644 index 000000000..d3b59e57c --- /dev/null +++ b/modules/qdrant/tests/test_qdrant.py @@ -0,0 +1,80 @@ +import pytest +from testcontainers.qdrant import QdrantContainer +import uuid +from grpc import RpcError +from pathlib import Path + +import qdrant_client + + +def test_docker_run_qdrant(): + with QdrantContainer() as qdrant: + client = qdrant.get_client() + collections = client.get_collections().collections + assert len(collections) == 0 + + client = qdrant.get_client(prefer_grpc=True) + collections = client.get_collections().collections + assert len(collections) == 0 + + +def test_qdrant_with_api_key_http(): + api_key = uuid.uuid4().hex + + with QdrantContainer(api_key=api_key) as qdrant: + with pytest.raises(qdrant_client.http.exceptions.UnexpectedResponse) as e: + # Construct a client without an API key + qdrant_client.QdrantClient(location=f"http://{qdrant.rest_host_address}").get_collections() + + assert "Must provide an API key" in str(e.value) + + # Construct a client with an API key + collections = ( + qdrant_client.QdrantClient(location=f"http://{qdrant.rest_host_address}", api_key=api_key) + .get_collections() + .collections + ) + + assert len(collections) == 0 + + # Get an automatically configured client instance + collections = qdrant.get_client().get_collections().collections + + assert len(collections) == 0 + + +def test_qdrant_with_api_key_grpc(): + api_key = uuid.uuid4().hex + + with QdrantContainer(api_key=api_key) as qdrant: + with pytest.raises(RpcError) as e: + qdrant_client.QdrantClient( + url=f"http://{qdrant.grpc_host_address}", + grpc_port=qdrant.exposed_grpc_port, + prefer_grpc=True, + ).get_collections() + + assert "Must provide an API key" in str(e.value) + + collections = ( + qdrant_client.QdrantClient( + url=f"http://{qdrant.grpc_host_address}", + grpc_port=qdrant.exposed_grpc_port, + prefer_grpc=True, + api_key=api_key, + ) + .get_collections() + .collections + ) + + assert len(collections) == 0 + + +def test_qdrant_with_config_file(): + config_file_path = Path(__file__).with_name("test_config.yaml") + + with QdrantContainer(config_file_path=config_file_path) as qdrant: + with pytest.raises(qdrant_client.http.exceptions.UnexpectedResponse) as e: + qdrant_client.QdrantClient(location=f"http://{qdrant.rest_host_address}").get_collections() + + assert "Must provide an API key" in str(e.value) diff --git a/poetry.lock b/poetry.lock index f05b8b4b5..0e22f67a8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1362,6 +1362,32 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = true +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = true +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + [[package]] name = "httpcore" version = "1.0.5" @@ -1397,6 +1423,7 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" +h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} httpcore = "==1.*" idna = "*" sniffio = "*" @@ -1407,6 +1434,17 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = true +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + [[package]] name = "identify" version = "2.5.35" @@ -2331,6 +2369,25 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "portalocker" +version = "2.8.2" +description = "Wraps the portalocker recipe for easy usage" +optional = true +python-versions = ">=3.8" +files = [ + {file = "portalocker-2.8.2-py3-none-any.whl", hash = "sha256:cfb86acc09b9aa7c3b43594e19be1345b9d16af3feb08bf92f23d4dce513a28e"}, + {file = "portalocker-2.8.2.tar.gz", hash = "sha256:2b035aa7828e46c58e9b31390ee1f169b98e1066ab10b9a6a861fe7e25ee4f33"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] + [[package]] name = "posthog" version = "3.5.0" @@ -3144,6 +3201,32 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "qdrant-client" +version = "1.8.2" +description = "Client library for the Qdrant vector search engine" +optional = true +python-versions = ">=3.8" +files = [ + {file = "qdrant_client-1.8.2-py3-none-any.whl", hash = "sha256:ee5341c0486d09e4346b0f5ef7781436e6d8cdbf1d5ecddfde7adb3647d353a8"}, + {file = "qdrant_client-1.8.2.tar.gz", hash = "sha256:65078d5328bc0393f42a46a31cd319a989b8285bf3958360acf1dffffdf4cc4e"}, +] + +[package.dependencies] +grpcio = ">=1.41.0" +grpcio-tools = ">=1.41.0" +httpx = {version = ">=0.20.0", extras = ["http2"]} +numpy = [ + {version = ">=1.21", markers = "python_version >= \"3.8\" and python_version < \"3.12\""}, + {version = ">=1.26", markers = "python_version >= \"3.12\""}, +] +portalocker = ">=2.7.0,<3.0.0" +pydantic = ">=1.10.8" +urllib3 = ">=1.26.14,<3" + +[package.extras] +fastembed = ["fastembed (==0.2.5)"] + [[package]] name = "reactivex" version = "4.0.4" @@ -4004,6 +4087,7 @@ nginx = [] opensearch = ["opensearch-py"] oracle = ["cx_Oracle", "sqlalchemy"] postgres = [] +qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] @@ -4012,4 +4096,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "c5659a08d1acbd86baa459df9d85b647e6611c546b955a702242558ca0fa0c9d" +content-hash = "f1ab6b554828820bb90956e79bb8e5b1c8a5d9b5a23a24eaa650bca048396ab2" diff --git a/pyproject.toml b/pyproject.toml index c6a691c9a..dc1e6f07e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ packages = [ { include = "testcontainers", from = "modules/opensearch" }, { include = "testcontainers", from = "modules/oracle" }, { include = "testcontainers", from = "modules/postgres" }, + { include = "testcontainers", from = "modules/qdrant" }, { include = "testcontainers", from = "modules/rabbitmq" }, { include = "testcontainers", from = "modules/redis" }, { include = "testcontainers", from = "modules/selenium" }, @@ -90,6 +91,7 @@ redis = { version = "*", optional = true } selenium = { version = "*", optional = true } weaviate-client = { version = "^4.5.4", optional = true } chromadb-client = { version = "*", optional = true } +qdrant-client = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -112,6 +114,7 @@ nginx = [] opensearch = ["opensearch-py"] oracle = ["sqlalchemy", "cx_Oracle"] postgres = [] +qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] selenium = ["selenium"] From 302c73ddaa7a6b5bc071ab0cc36d15461cae348b Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Sat, 30 Mar 2024 21:16:43 -0400 Subject: [PATCH 336/425] fix(nats): Client-Free(ish) NATS container (#462) Co-authored-by: bstrausser Co-authored-by: David Ankin --- index.rst | 1 + modules/nats/README.rst | 2 + modules/nats/testcontainers/nats/__init__.py | 75 +++++++++++++++++ modules/nats/tests/test_nats.py | 87 ++++++++++++++++++++ poetry.lock | 38 ++++++++- pyproject.toml | 4 + 6 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 modules/nats/README.rst create mode 100644 modules/nats/testcontainers/nats/__init__.py create mode 100644 modules/nats/tests/test_nats.py diff --git a/index.rst b/index.rst index e9fccc23a..71af37256 100644 --- a/index.rst +++ b/index.rst @@ -31,6 +31,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/mongodb/README modules/mssql/README modules/mysql/README + modules/nats/README modules/neo4j/README modules/nginx/README modules/opensearch/README diff --git a/modules/nats/README.rst b/modules/nats/README.rst new file mode 100644 index 000000000..785892939 --- /dev/null +++ b/modules/nats/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.nats.NatsContainer +.. title:: testcontainers.nats.NatsContainer diff --git a/modules/nats/testcontainers/nats/__init__.py b/modules/nats/testcontainers/nats/__init__.py new file mode 100644 index 000000000..8ffeca4da --- /dev/null +++ b/modules/nats/testcontainers/nats/__init__.py @@ -0,0 +1,75 @@ +# +# 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. + + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class NatsContainer(DockerContainer): + """ + Nats container. + + Example: + + .. doctest:: + + >>> import asyncio + >>> from nats import connect as nats_connect + >>> from testcontainers.nats import NatsContainer + + >>> async def test_doctest_usage(): + ... with NatsContainer() as nats_container: + ... client = await nats_connect(nats_container.nats_uri()) + ... sub_tc = await client.subscribe("tc") + ... await client.publish("tc", b"Test-Containers") + ... next_message = await sub_tc.next_msg(timeout=5.0) + ... await client.close() + ... return next_message.data + >>> asyncio.run(test_doctest_usage()) + b'Test-Containers' + """ + + def __init__( + self, + image: str = "nats:latest", + client_port: int = 4222, + management_port: int = 8222, + expected_ready_log: str = "Server is ready", + ready_timeout_secs: int = 120, + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + self.client_port = client_port + self.management_port = management_port + self._expected_ready_log = expected_ready_log + self._ready_timeout_secs = max(ready_timeout_secs, 0) + self.with_exposed_ports(self.client_port, self.management_port) + + @wait_container_is_ready() + def _healthcheck(self) -> None: + wait_for_logs(self, self._expected_ready_log, timeout=self._ready_timeout_secs) + + def nats_uri(self) -> str: + return f"nats://{self.get_container_host_ip()}:{self.get_exposed_port(self.client_port)}" + + def nats_host_and_port(self) -> tuple[str, int]: + return self.get_container_host_ip(), self.get_exposed_port(self.client_port) + + def nats_management_uri(self) -> str: + return f"nats://{self.get_container_host_ip()}:{self.get_exposed_port(self.management_port)}" + + def start(self) -> "NatsContainer": + super().start() + self._healthcheck() + return self diff --git a/modules/nats/tests/test_nats.py b/modules/nats/tests/test_nats.py new file mode 100644 index 000000000..7b72ea81b --- /dev/null +++ b/modules/nats/tests/test_nats.py @@ -0,0 +1,87 @@ +from testcontainers.nats import NatsContainer +from uuid import uuid4 +import pytest + +from nats import connect as nats_connect +from nats.aio.client import Client as NATSClient + + +async def get_client(container: NatsContainer) -> "NATSClient": + """ + Get a nats client. + + Returns: + client: Nats client to connect to the container. + """ + conn_string = container.nats_uri() + client = await nats_connect(conn_string) + return client + + +def test_basic_container_ops(): + with NatsContainer() as container: + # Not sure how to get type information without doing this + container: NatsContainer = container + h, p = container.nats_host_and_port() + assert h == "localhost" + uri = container.nats_uri() + management_uri = container.nats_management_uri() + + assert uri != management_uri + + +@pytest.mark.asyncio +async def test_pubsub(anyio_backend): + with NatsContainer() as container: + nc: NATSClient = await get_client(container) + + topic = str(uuid4()) + + sub = await nc.subscribe(topic) + sent_message = b"Test-Containers" + await nc.publish(topic, b"Test-Containers") + received_msg = await sub.next_msg() + print("Received:", received_msg) + assert sent_message == received_msg.data + await nc.flush() + await nc.close() + + +@pytest.mark.asyncio +async def test_more_complex_example(anyio_backend): + with NatsContainer() as container: + nc: NATSClient = await get_client(container) + + sub = await nc.subscribe("greet.*") + await nc.publish("greet.joe", b"hello") + + try: + await sub.next_msg(timeout=0.1) + except TimeoutError: + pass + + await nc.publish("greet.joe", b"hello.joe") + await nc.publish("greet.pam", b"hello.pam") + + first = await sub.next_msg(timeout=0.1) + assert b"hello.joe" == first.data + + second = await sub.next_msg(timeout=0.1) + assert b"hello.pam" == second.data + + await nc.publish("greet.bob", b"hello") + + await sub.unsubscribe() + await nc.drain() + + +@pytest.mark.asyncio +async def test_doctest_usage(): + """simpler to run test to mirror what is in the doctest""" + with NatsContainer() as nats_container: + client = await nats_connect(nats_container.nats_uri()) + sub_tc = await client.subscribe("tc") + await client.publish("tc", b"Test-Containers") + next_message = await sub_tc.next_msg(timeout=5.0) + await client.close() + assert next_message.data == b"Test-Containers" diff --git a/poetry.lock b/poetry.lock index 0e22f67a8..2213839cf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -1977,6 +1977,21 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nats-py" +version = "2.7.2" +description = "NATS client for Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "nats-py-2.7.2.tar.gz", hash = "sha256:0c97b4a57bed0ef1ff9ae6c19bc115ec7ca8ede5ab3e001fd00a377056a547cf"}, +] + +[package.extras] +aiohttp = ["aiohttp"] +fast-parse = ["fast-mail-parser"] +nkeys = ["nkeys"] + [[package]] name = "neo4j" version = "5.18.0" @@ -3021,6 +3036,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.5" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"}, + {file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "pytest-cov" version = "4.1.0" @@ -4082,6 +4115,7 @@ minio = ["minio"] mongodb = ["pymongo"] mssql = ["pymssql", "sqlalchemy"] mysql = ["pymysql", "sqlalchemy"] +nats = ["nats-py"] neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] @@ -4096,4 +4130,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "f1ab6b554828820bb90956e79bb8e5b1c8a5d9b5a23a24eaa650bca048396ab2" +content-hash = "f7634dce2f2de72261f7c7ac7ce59235f188c396d2ecf81421d3cd5dc26f335a" diff --git a/pyproject.toml b/pyproject.toml index dc1e6f07e..2d33643b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ packages = [ { include = "testcontainers", from = "modules/mongodb" }, { include = "testcontainers", from = "modules/mssql" }, { include = "testcontainers", from = "modules/mysql" }, + { include = "testcontainers", from = "modules/nats" }, { include = "testcontainers", from = "modules/neo4j" }, { include = "testcontainers", from = "modules/nginx" }, { include = "testcontainers", from = "modules/opensearch" }, @@ -79,6 +80,7 @@ pyyaml = { version = "*", optional = true } python-keycloak = { version = "*", optional = true } boto3 = { version = "*", optional = true } minio = { version = "*", optional = true } +nats-py = { version = "*", optional = true } pymongo = { version = "*", optional = true } sqlalchemy = { version = "*", optional = true } pymssql = { version = "*", optional = true } @@ -109,6 +111,7 @@ minio = ["minio"] mongodb = ["pymongo"] mssql = ["sqlalchemy", "pymssql"] mysql = ["sqlalchemy", "pymysql"] +nats = ["nats-py"] neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] @@ -136,6 +139,7 @@ sqlalchemy = "*" psycopg = "*" kafka-python = "^2.0.2" cassandra-driver = "*" +pytest-asyncio = "0.23.5" [[tool.poetry.source]] name = "PyPI" From 451d27865873bb75f4a09a26442572745408d013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Ragnar=20Brynjarsson?= Date: Sun, 31 Mar 2024 02:54:27 +0000 Subject: [PATCH 337/425] fix(kafka): Add redpanda testcontainer module (#441) Co-authored-by: Gudjon Ragnar Brynjarsson Co-authored-by: Dave Ankin --- modules/kafka/README.rst | 1 + .../kafka/testcontainers/kafka/__init__.py | 6 ++ .../kafka/testcontainers/kafka/_redpanda.py | 82 +++++++++++++++++++ modules/kafka/tests/test_redpanda.py | 54 ++++++++++++ poetry.lock | 16 ++-- pyproject.toml | 2 +- 6 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 modules/kafka/testcontainers/kafka/_redpanda.py create mode 100644 modules/kafka/tests/test_redpanda.py diff --git a/modules/kafka/README.rst b/modules/kafka/README.rst index 144c0fc2a..a54107a02 100644 --- a/modules/kafka/README.rst +++ b/modules/kafka/README.rst @@ -1,2 +1,3 @@ .. autoclass:: testcontainers.kafka.KafkaContainer .. title:: testcontainers.kafka.KafkaContainer +.. autoclass:: testcontainers.kafka.RedpandaContainer diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index 577416504..648140d4d 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -6,6 +6,12 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.kafka._redpanda import RedpandaContainer + +__all__ = [ + "KafkaContainer", + "RedpandaContainer", +] class KafkaContainer(DockerContainer): diff --git a/modules/kafka/testcontainers/kafka/_redpanda.py b/modules/kafka/testcontainers/kafka/_redpanda.py new file mode 100644 index 000000000..90d02c4cb --- /dev/null +++ b/modules/kafka/testcontainers/kafka/_redpanda.py @@ -0,0 +1,82 @@ +import tarfile +import time +from io import BytesIO +from textwrap import dedent + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +class RedpandaContainer(DockerContainer): + """ + Redpanda container. + + Example: + + .. doctest:: + + >>> from testcontainers.kafka import RedpandaContainer + + >>> with RedpandaContainer() as redpanda: + ... connection = redpanda.get_bootstrap_server() + """ + + TC_START_SCRIPT = "/tc-start.sh" + + def __init__( + self, + image: str = "docker.redpanda.com/redpandadata/redpanda:v23.1.13", + **kwargs, + ) -> None: + kwargs["entrypoint"] = "sh" + super().__init__(image, **kwargs) + self.redpanda_port = 9092 + self.schema_registry_port = 8081 + self.with_exposed_ports(self.redpanda_port, self.schema_registry_port) + + def get_bootstrap_server(self) -> str: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.redpanda_port) + return f"{host}:{port}" + + def get_schema_registry_address(self) -> str: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.schema_registry_port) + return f"http://{host}:{port}" + + def tc_start(self) -> None: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.redpanda_port) + + data = ( + dedent( + f""" + #!/bin/bash + /usr/bin/rpk redpanda start --mode dev-container --smp 1 --memory 1G \ + --kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 \ + --advertise-kafka-addr PLAINTEXT://127.0.0.1:29092,OUTSIDE://{host}:{port} + """ + ) + .strip() + .encode("utf-8") + ) + + self.create_file(data, RedpandaContainer.TC_START_SCRIPT) + + def start(self, timeout=10) -> "RedpandaContainer": + script = RedpandaContainer.TC_START_SCRIPT + command = f'-c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' + self.with_command(command) + super().start() + self.tc_start() + wait_for_logs(self, r".*Started Kafka API server.*", timeout=timeout) + return self + + def create_file(self, content: bytes, path: str) -> None: + with BytesIO() as archive, tarfile.TarFile(fileobj=archive, mode="w") as tar: + tarinfo = tarfile.TarInfo(name=path) + tarinfo.size = len(content) + tarinfo.mtime = time.time() + tar.addfile(tarinfo, BytesIO(content)) + archive.seek(0) + self.get_wrapped_container().put_archive("/", archive) diff --git a/modules/kafka/tests/test_redpanda.py b/modules/kafka/tests/test_redpanda.py new file mode 100644 index 000000000..7cee9fa8a --- /dev/null +++ b/modules/kafka/tests/test_redpanda.py @@ -0,0 +1,54 @@ +import pytest +from requests import post, get +from json import dumps + +from kafka import KafkaConsumer, KafkaProducer, TopicPartition, KafkaAdminClient +from kafka.admin import NewTopic + +from testcontainers.kafka import RedpandaContainer + + +def test_redpanda_producer_consumer(): + with RedpandaContainer() as container: + produce_and_consume_message(container) + + +@pytest.mark.parametrize("version", ["v23.1.13", "v23.3.10"]) +def test_redpanda_confluent_version(version): + with RedpandaContainer(image=f"docker.redpanda.com/redpandadata/redpanda:{version}") as container: + produce_and_consume_message(container) + + +def test_schema_registry(): + with RedpandaContainer() as container: + address = container.get_schema_registry_address() + subject_name = "test-subject-value" + url = f"{address}/subjects" + + payload = {"schema": dumps({"type": "string"})} + headers = {"Content-Type": "application/vnd.schemaregistry.v1+json"} + create_result = post(f"{url}/{subject_name}/versions", data=dumps(payload), headers=headers) + assert create_result.status_code == 200 + + result = get(url) + assert result.status_code == 200 + assert subject_name in result.json() + + +def produce_and_consume_message(container): + topic = "test-topic" + bootstrap_server = container.get_bootstrap_server() + + admin = KafkaAdminClient(bootstrap_servers=[bootstrap_server]) + admin.create_topics([NewTopic(topic, 1, 1)]) + + producer = KafkaProducer(bootstrap_servers=[bootstrap_server]) + future = producer.send(topic, b"verification message") + future.get(timeout=10) + producer.close() + + consumer = KafkaConsumer(bootstrap_servers=[bootstrap_server]) + tp = TopicPartition(topic, 0) + consumer.assign([tp]) + consumer.seek_to_beginning() + assert consumer.end_offsets([tp])[tp] == 1, "Expected exactly one test message to be present on test topic !" diff --git a/poetry.lock b/poetry.lock index 2213839cf..7297f586f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1647,18 +1647,22 @@ cryptography = ">=3.4" typing-extensions = ">=4.5.0" [[package]] -name = "kafka-python" -version = "2.0.2" +name = "kafka-python-ng" +version = "2.2.0" description = "Pure Python client for Apache Kafka" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "kafka-python-2.0.2.tar.gz", hash = "sha256:04dfe7fea2b63726cd6f3e79a2d86e709d608d74406638c5da33a01d45a9d7e3"}, - {file = "kafka_python-2.0.2-py2.py3-none-any.whl", hash = "sha256:2d92418c7cb1c298fa6c7f0fb3519b520d0d7526ac6cb7ae2a4fc65a51a94b6e"}, + {file = "kafka-python-ng-2.2.0.tar.gz", hash = "sha256:31d7082fd0ea78702a1eb3c20b5cbb3663d599d916d64a2a517a55ef7c9ebe58"}, + {file = "kafka_python_ng-2.2.0-py2.py3-none-any.whl", hash = "sha256:8f7f1f18ee0d09d905530e8990cf27b0cda0c05faf8098d74284c1069c5e6097"}, ] [package.extras] +boto = ["botocore"] crc32c = ["crc32c"] +lz4 = ["lz4"] +snappy = ["python-snappy"] +zstd = ["zstandard"] [[package]] name = "keyring" @@ -4130,4 +4134,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "f7634dce2f2de72261f7c7ac7ce59235f188c396d2ecf81421d3cd5dc26f335a" +content-hash = "b1683eae41b087e97eca3bfa0c2105824827f2911756f6dad5d69424eb1e976a" diff --git a/pyproject.toml b/pyproject.toml index 2d33643b1..9281283a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,9 +137,9 @@ psycopg2-binary = "*" pg8000 = "*" sqlalchemy = "*" psycopg = "*" -kafka-python = "^2.0.2" cassandra-driver = "*" pytest-asyncio = "0.23.5" +kafka-python-ng = "^2.2.0" [[tool.poetry.source]] name = "PyPI" From 0f554fbaa9511e0221806f57de971abedf1c0bf2 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Sun, 31 Mar 2024 05:15:52 +0200 Subject: [PATCH 338/425] fix(new): add a new Docker Registry test container (#389) I added a new test container for spinning up a [Docker registry](https://hub.docker.com/_/registry). --- index.rst | 1 + modules/registry/README.rst | 8 ++ .../testcontainers/registry/__init__.py | 77 +++++++++++++++++++ modules/registry/tests/test_registry.py | 27 +++++++ poetry.lock | 43 ++++++++++- pyproject.toml | 3 + 6 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 modules/registry/README.rst create mode 100644 modules/registry/testcontainers/registry/__init__.py create mode 100644 modules/registry/tests/test_registry.py diff --git a/index.rst b/index.rst index 71af37256..4459624cd 100644 --- a/index.rst +++ b/index.rst @@ -40,6 +40,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/qdrant/README modules/rabbitmq/README modules/redis/README + modules/registry/README modules/selenium/README modules/weaviate/README diff --git a/modules/registry/README.rst b/modules/registry/README.rst new file mode 100644 index 000000000..f503f338e --- /dev/null +++ b/modules/registry/README.rst @@ -0,0 +1,8 @@ +.. autoclass:: testcontainers.registry.DockerRegistryContainer + +When building Docker containers with Docker Buildx there is currently no option to test your containers locally without +a local registry. Otherwise Buildx pushes your image to Docker Hub, which is not what you want in a test case. More +and more you need to use Buildx for efficiently building images and especially multi arch images. + +When you use Docker Python libraries like docker-py or python-on-whales to build and test Docker images, what a lot of +persons and DevOps engineers like me nowadays do, a test container comes in very handy. diff --git a/modules/registry/testcontainers/registry/__init__.py b/modules/registry/testcontainers/registry/__init__.py new file mode 100644 index 000000000..7b846ad5c --- /dev/null +++ b/modules/registry/testcontainers/registry/__init__.py @@ -0,0 +1,77 @@ +import time +from io import BytesIO +from tarfile import TarFile, TarInfo +from typing import TYPE_CHECKING, Optional + +import bcrypt +from requests import get +from requests.auth import HTTPBasicAuth +from requests.exceptions import ConnectionError, ReadTimeout + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + +if TYPE_CHECKING: + from requests import Response + + +class DockerRegistryContainer(DockerContainer): + # https://docs.docker.com/registry/ + credentials_path: str = "/htpasswd/credentials.txt" + + def __init__( + self, + image: str = "registry:2", + port: int = 5000, + username: Optional[str] = None, + password: Optional[str] = None, + **kwargs, + ) -> None: + super().__init__(image=image, **kwargs) + self.port: int = port + self.username: Optional[str] = username + self.password: Optional[str] = password + self.with_exposed_ports(self.port) + + def _copy_credentials(self) -> None: + # Create credentials and write them to the container + hashed_password: str = bcrypt.hashpw( + self.password.encode("utf-8"), + bcrypt.gensalt(rounds=12, prefix=b"2a"), + ).decode("utf-8") + content: bytes = f"{self.username}:{hashed_password}".encode("utf-8") # noqa: UP012 + + with BytesIO() as tar_archive_object, TarFile(fileobj=tar_archive_object, mode="w") as tmp_tarfile: + tarinfo: TarInfo = TarInfo(name=self.credentials_path) + tarinfo.size = len(content) + tarinfo.mtime = time.time() + + tmp_tarfile.addfile(tarinfo, BytesIO(content)) + tar_archive_object.seek(0) + self.get_wrapped_container().put_archive("/", tar_archive_object) + + @wait_container_is_ready(ConnectionError, ReadTimeout) + def _readiness_probe(self) -> None: + url: str = f"http://{self.get_registry()}/v2" + if self.username and self.password: + response: Response = get(url, auth=HTTPBasicAuth(self.username, self.password), timeout=1) + else: + response: Response = get(url, timeout=1) + response.raise_for_status() + + def start(self): + if self.username and self.password: + self.with_env("REGISTRY_AUTH_HTPASSWD_REALM", "local-registry") + self.with_env("REGISTRY_AUTH_HTPASSWD_PATH", self.credentials_path) + super().start() + self._copy_credentials() + else: + super().start() + + self._readiness_probe() + return self + + def get_registry(self) -> str: + host: str = self.get_container_host_ip() + port: str = self.get_exposed_port(self.port) + return f"{host}:{port}" diff --git a/modules/registry/tests/test_registry.py b/modules/registry/tests/test_registry.py new file mode 100644 index 000000000..0aa568ee5 --- /dev/null +++ b/modules/registry/tests/test_registry.py @@ -0,0 +1,27 @@ +from requests import Response, get +from requests.auth import HTTPBasicAuth +from testcontainers.registry import DockerRegistryContainer + + +REGISTRY_USERNAME: str = "foo" +REGISTRY_PASSWORD: str = "bar" + + +def test_registry(): + with DockerRegistryContainer().with_bind_ports(5000, 5000) as registry_container: + url: str = f"http://{registry_container.get_registry()}/v2/_catalog" + + response: Response = get(url) + + assert response.status_code == 200 + + +def test_registry_with_authentication(): + with DockerRegistryContainer(username=REGISTRY_USERNAME, password=REGISTRY_PASSWORD).with_bind_ports( + 5000, 5000 + ) as registry_container: + url: str = f"http://{registry_container.get_registry()}/v2/_catalog" + + response: Response = get(url, auth=HTTPBasicAuth(REGISTRY_USERNAME, REGISTRY_PASSWORD)) + + assert response.status_code == 200 diff --git a/poetry.lock b/poetry.lock index 7297f586f..5e126249d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -220,6 +220,46 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "bcrypt" +version = "4.1.2" +description = "Modern password hashing for your software and your servers" +optional = true +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, + {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, + {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, + {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, + {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, + {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, + {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + [[package]] name = "boto3" version = "1.34.59" @@ -4128,10 +4168,11 @@ postgres = [] qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] +registry = ["bcrypt"] selenium = ["selenium"] weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "b1683eae41b087e97eca3bfa0c2105824827f2911756f6dad5d69424eb1e976a" +content-hash = "1f8acb3c00fa87c82b3e283406826f36b138a304428696454a9d108de7445120" diff --git a/pyproject.toml b/pyproject.toml index 9281283a7..9f70b7904 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ packages = [ { include = "testcontainers", from = "modules/qdrant" }, { include = "testcontainers", from = "modules/rabbitmq" }, { include = "testcontainers", from = "modules/redis" }, + { include = "testcontainers", from = "modules/registry" }, { include = "testcontainers", from = "modules/selenium" }, { include = "testcontainers", from = "modules/weaviate" } ] @@ -94,6 +95,7 @@ selenium = { version = "*", optional = true } weaviate-client = { version = "^4.5.4", optional = true } chromadb-client = { version = "*", optional = true } qdrant-client = { version = "*", optional = true } +bcrypt = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -120,6 +122,7 @@ postgres = [] qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] +registry = ["bcrypt"] selenium = ["selenium"] weaviate = ["weaviate-client"] chroma = ["chromadb-client"] From 6e6d8e3c919be3efa581704868193e66da54acf3 Mon Sep 17 00:00:00 2001 From: Gerald Venzl Date: Sat, 30 Mar 2024 23:51:52 -0700 Subject: [PATCH 339/425] fix: Improved Oracle DB module (#363) Hi, I took the liberty to improve the Oracle DB module for Testcontainers Python. The PR has several enhancements: * Leveraging `oracledb` thin Python driver * This makes Oracle DB tests on CI/CD now possible too * Usage of `gvenzl/oracle-free` image with the latest and greatest Oracle DB version * DB version independent readiness check * Support for various `gvenzl/oracle-free` image features (`ORACLE_DATABASE`, `APP_USER`, `APP_USER_PASSWORD`, etc) * Tests for Oracle DB for the various combinations Ideally, some more documentation on how the Container is supposed to be used would be handy but I couldn't really find a good example of how such a ReadMe should be structured. Any things are gladly appreciated! --------- Signed-off-by: gvenzl Signed-off-by: Gerald Venzl Co-authored-by: David Ankin --- index.rst | 2 +- modules/{oracle => oracle-free}/README.rst | 0 .../testcontainers/oracle/__init__.py | 76 +++++++++++++++++ modules/oracle-free/tests/test_oracle.py | 81 ++++++++++++++++++ .../oracle/testcontainers/oracle/__init__.py | 33 ------- modules/oracle/tests/test_oracle.py | 20 ----- poetry.lock | 85 +++++++++++++------ pyproject.toml | 8 +- 8 files changed, 220 insertions(+), 85 deletions(-) rename modules/{oracle => oracle-free}/README.rst (100%) create mode 100644 modules/oracle-free/testcontainers/oracle/__init__.py create mode 100644 modules/oracle-free/tests/test_oracle.py delete mode 100644 modules/oracle/testcontainers/oracle/__init__.py delete mode 100644 modules/oracle/tests/test_oracle.py diff --git a/index.rst b/index.rst index 4459624cd..2a2bc6599 100644 --- a/index.rst +++ b/index.rst @@ -35,7 +35,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/neo4j/README modules/nginx/README modules/opensearch/README - modules/oracle/README + modules/oracle-free/README modules/postgres/README modules/qdrant/README modules/rabbitmq/README diff --git a/modules/oracle/README.rst b/modules/oracle-free/README.rst similarity index 100% rename from modules/oracle/README.rst rename to modules/oracle-free/README.rst diff --git a/modules/oracle-free/testcontainers/oracle/__init__.py b/modules/oracle-free/testcontainers/oracle/__init__.py new file mode 100644 index 000000000..2b903ac54 --- /dev/null +++ b/modules/oracle-free/testcontainers/oracle/__init__.py @@ -0,0 +1,76 @@ +from os import environ +from secrets import randbits +from typing import Optional + +from testcontainers.core.generic import DbContainer + + +class OracleDbContainer(DbContainer): + """ + Oracle database container. + + Example: + + .. doctest:: + + >>> import sys, pytest + >>> if sys.platform.startswith('win') or sys.platform == 'darwin': + ... pytest.skip("linux only test") + + >>> import sqlalchemy + >>> from testcontainers.oracle import OracleDbContainer + + >>> with OracleDbContainer() as oracle: + ... engine = sqlalchemy.create_engine(oracle.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute(sqlalchemy.text("SELECT 1 FROM dual")) + ... result.fetchall() + [(1,)] + """ + + def __init__( + self, + image: str = "gvenzl/oracle-free:slim", + oracle_password: Optional[str] = None, + username: Optional[str] = None, + password: Optional[str] = None, + port: int = 1521, + dbname: Optional[str] = None, + **kwargs + ) -> None: + super().__init__(image=image, **kwargs) + + self.port = port + self.with_exposed_ports(self.port) + + self.oracle_password = oracle_password or environ.get("ORACLE_PASSWORD") or hex(randbits(24)) + self.username = username or environ.get("APP_USER") + self.password = password or environ.get("APP_USER_PASSWORD") + self.dbname = dbname or environ.get("ORACLE_DATABASE") + + def get_connection_url(self) -> str: + return super()._create_connection_url( + dialect="oracle+oracledb", + username=self.username or "system", + password=self.password or self.oracle_password, + port=self.port, + ) + "/?service_name={}".format(self.dbname or "FREEPDB1") + # Default DB is "FREEPDB1" + + def _configure(self) -> None: + # if self.oracle_password is not None: + # self.with_env("ORACLE_PASSWORD", self.oracle_password) + # # Either ORACLE_PASSWORD or ORACLE_RANDOM_PASSWORD need to be passed on + # else: + # self.with_env("ORACLE_RANDOM_PASSWORD", "y") + # this module is unusable with a random password + self.with_env("ORACLE_PASSWORD", self.oracle_password) + + if self.username is not None: + self.with_env("APP_USER", self.username) + if self.password is not None: + self.with_env("APP_USER_PASSWORD", self.password) + + # FREE and FREEPDB1 are predefined databases, do not pass them on as ORACLE_DATABASE + if self.dbname is not None and self.dbname.upper() not in ("FREE", "FREEPDB1"): + self.with_env("ORACLE_DATABASE", self.dbname) diff --git a/modules/oracle-free/tests/test_oracle.py b/modules/oracle-free/tests/test_oracle.py new file mode 100644 index 000000000..0c6d8998e --- /dev/null +++ b/modules/oracle-free/tests/test_oracle.py @@ -0,0 +1,81 @@ +import pytest +import sqlalchemy + +from testcontainers.core.utils import is_arm +from testcontainers.oracle import OracleDbContainer + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_docker_run_oracle_with_system_password(): + with OracleDbContainer(oracle_password="test") as oracledb: + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + test_val = 1 + result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + for row in result: + assert row[0] == test_val + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_docker_run_oracle_with_username_password(): + with OracleDbContainer(username="test", password="test") as oracledb: + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + test_val = 1 + result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + for row in result: + assert row[0] == test_val + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_docker_run_oracle_with_custom_db_and_system_username_password(): + with OracleDbContainer(oracle_password="coolpassword", dbname="myTestPDB") as oracledb: + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + test_val = 1 + result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + for row in result: + assert row[0] == test_val + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_docker_run_oracle_with_custom_db_and_app_username_password(): + with OracleDbContainer(username="mycooluser", password="123connect", dbname="anotherPDB") as oracledb: + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + test_val = 1 + result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + for row in result: + assert row[0] == test_val + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_docker_run_oracle_with_default_db_and_app_username_password(): + with OracleDbContainer(username="mycooluser", password="123connect") as oracledb: + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + test_val = 1 + result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + for row in result: + assert row[0] == test_val + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_docker_run_oracle_with_cdb_and_system_username(): + with OracleDbContainer(oracle_password="MyOraclePWD1", dbname="free") as oracledb: + engine = sqlalchemy.create_engine(oracledb.get_connection_url()) + with engine.begin() as connection: + test_val = 1 + result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + for row in result: + assert row[0] == test_val + + +@pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") +def test_doctest(): + with OracleDbContainer() as oracle: + print(oracle.get_connection_url()) + engine = sqlalchemy.create_engine(oracle.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("SELECT 1 FROM dual")) + assert result.fetchall() == [(1,)] diff --git a/modules/oracle/testcontainers/oracle/__init__.py b/modules/oracle/testcontainers/oracle/__init__.py deleted file mode 100644 index c0a5e657c..000000000 --- a/modules/oracle/testcontainers/oracle/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -from testcontainers.core.generic import DbContainer - - -class OracleDbContainer(DbContainer): - """ - Oracle database container. - - Example: - - .. code-block:: - - >>> import sqlalchemy - >>> from testcontainers.oracle import OracleDbContainer - - >>> with OracleDbContainer() as oracle: - ... engine = sqlalchemy.create_engine(oracle.get_connection_url()) - ... with engine.begin() as connection: - ... result = connection.execute(sqlalchemy.text("select * from V$VERSION")) - """ - - def __init__(self, image: str = "wnameless/oracle-xe-11g-r2:latest", **kwargs) -> None: - super().__init__(image=image, **kwargs) - self.container_port = 1521 - self.with_exposed_ports(self.container_port) - self.with_env("ORACLE_ALLOW_REMOTE", "true") - - def get_connection_url(self) -> str: - return super()._create_connection_url( - dialect="oracle", username="system", password="oracle", port=self.container_port, dbname="xe" - ) - - def _configure(self) -> None: - pass diff --git a/modules/oracle/tests/test_oracle.py b/modules/oracle/tests/test_oracle.py deleted file mode 100644 index 32d58b461..000000000 --- a/modules/oracle/tests/test_oracle.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest -import sqlalchemy - -from testcontainers.oracle import OracleDbContainer - - -@pytest.mark.skip(reason="needs oracle client libraries unavailable on Travis") -def test_docker_run_oracle(): - versions = { - "Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production", - "PL/SQL Release 11.2.0.2.0 - Production", - "CORE\t11.2.0.2.0\tProduction", - "TNS for Linux: Version 11.2.0.2.0 - Production", - "NLSRTL Version 11.2.0.2.0 - Production", - } - with OracleDbContainer() as oracledb: - engine = sqlalchemy.create_engine(oracledb.get_connection_url()) - with engine.begin() as connection: - result = connection.execute(sqlalchemy.text("select * from V$VERSION")) - assert {row[0] for row in result} == versions diff --git a/poetry.lock b/poetry.lock index 5e126249d..d84e26459 100644 --- a/poetry.lock +++ b/poetry.lock @@ -837,31 +837,6 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] -[[package]] -name = "cx-oracle" -version = "8.3.0" -description = "Python interface to Oracle" -optional = true -python-versions = "*" -files = [ - {file = "cx_Oracle-8.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6a23da225f03f50a81980c61dbd6a358c3575f212ca7f4c22bb65a9faf94f7f"}, - {file = "cx_Oracle-8.3.0-cp310-cp310-win32.whl", hash = "sha256:715a8bbda5982af484ded14d184304cc552c1096c82471dd2948298470e88a04"}, - {file = "cx_Oracle-8.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07f01608dfb6603a8f2a868fc7c7bdc951480f187df8dbc50f4d48c884874e6a"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4b3afe7a911cebaceda908228d36839f6441cbd38e5df491ec25960562bb01a0"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-win32.whl", hash = "sha256:076ffb71279d6b2dcbf7df028f62a01e18ce5bb73d8b01eab582bf14a62f4a61"}, - {file = "cx_Oracle-8.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b82e4b165ffd807a2bd256259a6b81b0a2452883d39f987509e2292d494ea163"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b902db61dcdcbbf8dd981f5a46d72fef40c5150c7fc0eb0f0698b462d6eb834e"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-win32.whl", hash = "sha256:4c82ca74442c298ceec56d207450c192e06ecf8ad52eb4aaad0812e147ceabf7"}, - {file = "cx_Oracle-8.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:54164974d526b76fdefb0b66a42b68e1fca5df78713d0eeb8c1d0047b83f6bcf"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:410747d542e5f94727f5f0e42e9706c772cf9094fb348ce965ab88b3a9e4d2d8"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-win32.whl", hash = "sha256:3baa878597c5fadb2c72f359f548431c7be001e722ce4a4ebdf3d2293a1bb70b"}, - {file = "cx_Oracle-8.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:de42bdc882abdc5cea54597da27a05593b44143728e5b629ad5d35decb1a2036"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df412238a9948340591beee9ec64fa62a2efacc0d91107034a7023e2991fba97"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-win32.whl", hash = "sha256:70d3cf030aefd71f99b45beba77237b2af448adf5e26be0db3d0d3dee6ea4230"}, - {file = "cx_Oracle-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf01ce87edb4ef663b2e5bd604e1e0154d2cc2f12b60301f788b569d9db8a900"}, - {file = "cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9"}, -] - [[package]] name = "deprecated" version = "1.2.14" @@ -1959,7 +1934,6 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -2257,6 +2231,49 @@ files = [ {file = "opentelemetry_semantic_conventions-0.37b0.tar.gz", hash = "sha256:087ce2e248e42f3ffe4d9fa2303111de72bb93baa06a0f4655980bc1557c4228"}, ] +[[package]] +name = "oracledb" +version = "2.1.1" +description = "Python interface to Oracle Database" +optional = true +python-versions = ">=3.7" +files = [ + {file = "oracledb-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0666088fcca29cfe8a8428888426655d2f7417adfb854ad0af79c40f1bae59aa"}, + {file = "oracledb-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8651ea5b9ef35493aa1bb80786458a26df3d728eb3e55b6bb9ddf2aa83f45be8"}, + {file = "oracledb-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06ede73b90c24387b47094aadae477a60e9c076d04275a7a5ae9deef0991e3a2"}, + {file = "oracledb-2.1.1-cp310-cp310-win32.whl", hash = "sha256:962e0fce942eefafe8c52481c1979dd53c6929d479544499d2e32a1eb4111837"}, + {file = "oracledb-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:ec1012362c6ba5a465d87730c14bbbc3bac3a0799cd4371c6514364f2121dd42"}, + {file = "oracledb-2.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a94793243936117b4a8ea4a17672aa77f71c9063a176f0330ea733a48679d82c"}, + {file = "oracledb-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cc8e853d33333c5e3cd4c2db0591c374c5fe06ebd713748b65c59be5b252054"}, + {file = "oracledb-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d29cb729cd94e7afbfbef8f0d272bd8e20b16be77ae747686b677e9e9df0ad3f"}, + {file = "oracledb-2.1.1-cp311-cp311-win32.whl", hash = "sha256:c0c29bdafe78d412bec20d91791dc91fc7feb9b63a39138a3ebd5835abf3639d"}, + {file = "oracledb-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:d539300667bf7af839baddb42896175c14d872cc0d903dace7850e91d90387f1"}, + {file = "oracledb-2.1.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166216272dcaaa2647fde3b7e13f9d3ffe0f05dc70791aacf7ba5e2c65c96aca"}, + {file = "oracledb-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb64b15c75b4d62fa060516d6bbd9f8f42272d677a48f6293597945836e9f0d"}, + {file = "oracledb-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c16782bc37087947052f3d398be6e7d4bb3143a6863a5a1517f63c92c1b2b99"}, + {file = "oracledb-2.1.1-cp312-cp312-win32.whl", hash = "sha256:50791fadf26e97a8cedcc1bf16ed5c5f0f2f1a0254e3725889e52eba1adc8f22"}, + {file = "oracledb-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:29b000e4892f6c4eac6abe099bc43df9ccd586d9a02fa6391e0bd8e4eb88df37"}, + {file = "oracledb-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:48057c0309d2e5f4b78804bd73b4e32654ff912515da17163262142a5f451e0e"}, + {file = "oracledb-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc53191a389a2b6119488635c63e626e76df3394403d0e907260de9ad01c0a6c"}, + {file = "oracledb-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0750de1b381f6d3508904e079491a99d61742db109131dc664d4b3d146ee6f17"}, + {file = "oracledb-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:7262457d256c30a738cf3dde4ec175d2d382c6c7b9e3395665c04c25b34d28b0"}, + {file = "oracledb-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ec05d1a56e9f45741ed60e7c24a8c9cc0f1982241aa555d57b04ed0879b59228"}, + {file = "oracledb-2.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2611a9bd530829ba2332db8a7083699579263cf3fb1093b0e1f5b5a57239e18e"}, + {file = "oracledb-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bba26b23d3e2905eb4c7627a9a1c31ad72c64442e184173c48f19bde2a92184"}, + {file = "oracledb-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e7f9cb56e2d79a11f4b5a9233067b9eb327a30e6e8dad8349b02eca64482ab5"}, + {file = "oracledb-2.1.1-cp38-cp38-win32.whl", hash = "sha256:1658760585f8c41e6530e25332ccdde3ca0b95b4e7c2a9a69719da0420da7fe6"}, + {file = "oracledb-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:bc013fac5009e6ebc65be58e7d3645aadd04ef8627553cdfe6e66a157963087a"}, + {file = "oracledb-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:479bcec29826ca2a49d1b113ca343ad31db8b94063d50223ab8d32c069faf4b3"}, + {file = "oracledb-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c7b0aa7038d7444ea133a2439dc9b645fb5c81832dd24818c181494423f752"}, + {file = "oracledb-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce1f93bea32abd2d3dcfc17ed0546a20d36bc7a92b36ced58b525165fd63c8a"}, + {file = "oracledb-2.1.1-cp39-cp39-win32.whl", hash = "sha256:95c581d4df786d3aa6e2e47c8519b9036b28c1ea1416cf843b9572ba611a54d6"}, + {file = "oracledb-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:1bf3ba201f21e8183a33b32ec1224b5d65b98ac0b1bf843b98b0b65ef34c1b26"}, + {file = "oracledb-2.1.1.tar.gz", hash = "sha256:e2e817cfa6dff36c7131736f34e2aaec75d726fd38d1910d8318061a278228fa"}, +] + +[package.dependencies] +cryptography = ">=3.2.1" + [[package]] name = "orjson" version = "3.10.0" @@ -2384,6 +2401,17 @@ gevent = ["gevent"] tornado = ["tornado"] twisted = ["twisted"] +[[package]] +name = "pip" +version = "24.0" +description = "The PyPA recommended tool for installing Python packages." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pip-24.0-py3-none-any.whl", hash = "sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"}, + {file = "pip-24.0.tar.gz", hash = "sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2"}, +] + [[package]] name = "pkginfo" version = "1.10.0" @@ -4163,7 +4191,8 @@ nats = ["nats-py"] neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] -oracle = ["cx_Oracle", "sqlalchemy"] +oracle = ["oracledb", "sqlalchemy"] +oracle-free = ["oracledb", "sqlalchemy"] postgres = [] qdrant = ["qdrant-client"] rabbitmq = ["pika"] @@ -4175,4 +4204,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "1f8acb3c00fa87c82b3e283406826f36b138a304428696454a9d108de7445120" +content-hash = "40229c4a25c2b3e738ee2c89644d00a0e2806fae710f9c10e2cae5b6962ff3c4" diff --git a/pyproject.toml b/pyproject.toml index 9f70b7904..c3ff09902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ packages = [ { include = "testcontainers", from = "modules/neo4j" }, { include = "testcontainers", from = "modules/nginx" }, { include = "testcontainers", from = "modules/opensearch" }, - { include = "testcontainers", from = "modules/oracle" }, + { include = "testcontainers", from = "modules/oracle-free" }, { include = "testcontainers", from = "modules/postgres" }, { include = "testcontainers", from = "modules/qdrant" }, { include = "testcontainers", from = "modules/rabbitmq" }, @@ -88,7 +88,7 @@ pymssql = { version = "*", optional = true } pymysql = { version = "*", extras = ["rsa"], optional = true } neo4j = { version = "*", optional = true } opensearch-py = { version = "*", optional = true } -cx_Oracle = { version = "*", optional = true } +oracledb = { version = "*", optional = true } pika = { version = "*", optional = true } redis = { version = "*", optional = true } selenium = { version = "*", optional = true } @@ -117,7 +117,8 @@ nats = ["nats-py"] neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] -oracle = ["sqlalchemy", "cx_Oracle"] +oracle = ["sqlalchemy", "oracledb"] +oracle-free = ["sqlalchemy", "oracledb"] postgres = [] qdrant = ["qdrant-client"] rabbitmq = ["pika"] @@ -143,6 +144,7 @@ psycopg = "*" cassandra-driver = "*" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" +pip = "^24.0" [[tool.poetry.source]] name = "PyPI" From dee20a76c88445b911d38b4704c2380114a66794 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sun, 31 Mar 2024 02:57:50 -0400 Subject: [PATCH 340/425] fix: remove accidentally added pip in dev dependencies (#516) --- poetry.lock | 13 +------------ pyproject.toml | 1 - 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index d84e26459..6bb9139d3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2401,17 +2401,6 @@ gevent = ["gevent"] tornado = ["tornado"] twisted = ["twisted"] -[[package]] -name = "pip" -version = "24.0" -description = "The PyPA recommended tool for installing Python packages." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pip-24.0-py3-none-any.whl", hash = "sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"}, - {file = "pip-24.0.tar.gz", hash = "sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2"}, -] - [[package]] name = "pkginfo" version = "1.10.0" @@ -4204,4 +4193,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "40229c4a25c2b3e738ee2c89644d00a0e2806fae710f9c10e2cae5b6962ff3c4" +content-hash = "c38637d4ecf32df935824d3dfa858bd9ef1fd4d90535f74587ec1ca87c94c285" diff --git a/pyproject.toml b/pyproject.toml index c3ff09902..fd9067707 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,7 +144,6 @@ psycopg = "*" cassandra-driver = "*" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" -pip = "^24.0" [[tool.poetry.source]] name = "PyPI" From bddbaeb20cbd147c429f8020395355402b8a7268 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 1 Apr 2024 07:38:31 -0400 Subject: [PATCH 341/425] fix(cassandra): make cassandra dependency optional/test-only (#518) ![image](https://github.com/testcontainers/testcontainers-python/assets/8921892/54e01421-f678-4e8e-856c-690951a7cd06) --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6bb9139d3..614e42e5f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4193,4 +4193,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "c38637d4ecf32df935824d3dfa858bd9ef1fd4d90535f74587ec1ca87c94c285" +content-hash = "af9f21cb52ebd761ba91c852c9839d982124c149e77abdc079fc5657cecb9ff7" diff --git a/pyproject.toml b/pyproject.toml index fd9067707..0f02966a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ bcrypt = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] azurite = ["azure-storage-blob"] -cassandra = ["cassandra-driver"] +cassandra = [] clickhouse = ["clickhouse-driver"] elasticsearch = [] google = ["google-cloud-pubsub", "google-cloud-datastore"] From f819c7a65045e10c5951626d212c2663d8cae5ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 07:50:56 -0400 Subject: [PATCH 342/425] chore(main): release testcontainers 4.3.0 (#500) :robot: I have created a release *beep* *boop* --- ## [4.3.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.2.0...testcontainers-v4.3.0) (2024-04-01) ### Features * **client:** Add custom User-Agent in Docker client as `tc-python/<version>` ([#507](https://github.com/testcontainers/testcontainers-python/issues/507)) ([dd55082](https://github.com/testcontainers/testcontainers-python/commit/dd55082991b3405038a90678a39e8c815f0d1fc8)) ### Bug Fixes * Add CassandraContainer ([#476](https://github.com/testcontainers/testcontainers-python/issues/476)) ([507e466](https://github.com/testcontainers/testcontainers-python/commit/507e466a1fa9ac64c254ceb9ae0d57f6bfd8c89d)) * add chroma container ([#515](https://github.com/testcontainers/testcontainers-python/issues/515)) ([0729bf4](https://github.com/testcontainers/testcontainers-python/commit/0729bf4af957f8b6638cc204b108358745c0cfc9)) * Add Weaviate module ([#492](https://github.com/testcontainers/testcontainers-python/issues/492)) ([90762e8](https://github.com/testcontainers/testcontainers-python/commit/90762e817bf49de6d6366212fb48e7edb67ab0c6)) * **cassandra:** make cassandra dependency optional/test-only ([#518](https://github.com/testcontainers/testcontainers-python/issues/518)) ([bddbaeb](https://github.com/testcontainers/testcontainers-python/commit/bddbaeb20cbd147c429f8020395355402b8a7268)) * **core:** allow setting docker command path for docker compose ([#512](https://github.com/testcontainers/testcontainers-python/issues/512)) ([63fcd52](https://github.com/testcontainers/testcontainers-python/commit/63fcd52ec2d6ded5f6413166a3690c1138e4dae0)) * **google:** add support for Datastore emulator ([#508](https://github.com/testcontainers/testcontainers-python/issues/508)) ([3d891a5](https://github.com/testcontainers/testcontainers-python/commit/3d891a5ec62944d01d1bf3d6f70e6aec83f6e516)) * Improved Oracle DB module ([#363](https://github.com/testcontainers/testcontainers-python/issues/363)) ([6e6d8e3](https://github.com/testcontainers/testcontainers-python/commit/6e6d8e3c919be3efa581704868193e66da54acf3)) * inconsistent test runs for community modules ([#497](https://github.com/testcontainers/testcontainers-python/issues/497)) ([914f1e5](https://github.com/testcontainers/testcontainers-python/commit/914f1e55bcb3b10260788c3affb8426f77eb9036)) * **kafka:** Add redpanda testcontainer module ([#441](https://github.com/testcontainers/testcontainers-python/issues/441)) ([451d278](https://github.com/testcontainers/testcontainers-python/commit/451d27865873bb75f4a09a26442572745408d013)) * **kafka:** wait_for_logs in kafka container to reduce lib requirement ([#377](https://github.com/testcontainers/testcontainers-python/issues/377)) ([909107b](https://github.com/testcontainers/testcontainers-python/commit/909107b221417a39516f961364beb518d2756f45)) * **keycloak:** container should use dedicated API endpoints to determine container readiness ([#490](https://github.com/testcontainers/testcontainers-python/issues/490)) ([2e27225](https://github.com/testcontainers/testcontainers-python/commit/2e272253148797759748bd40c42f797697d3163f)) * **nats:** Client-Free(ish) NATS container ([#462](https://github.com/testcontainers/testcontainers-python/issues/462)) ([302c73d](https://github.com/testcontainers/testcontainers-python/commit/302c73ddaa7a6b5bc071ab0cc36d15461cae348b)) * **new:** add a new Docker Registry test container ([#389](https://github.com/testcontainers/testcontainers-python/issues/389)) ([0f554fb](https://github.com/testcontainers/testcontainers-python/commit/0f554fbaa9511e0221806f57de971abedf1c0bf2)) * pass doctests, s/doctest/doctests/, run them in gha, s/asyncpg/psycopg/ in doctest, fix keycloak flakiness: wait for first user ([#505](https://github.com/testcontainers/testcontainers-python/issues/505)) ([545240d](https://github.com/testcontainers/testcontainers-python/commit/545240dfdcb2a565ad7cef0e9813f03b9b6f910e)) * pass updated keyword args to Publisher/Subscriber client in google/pubsub [#161](https://github.com/testcontainers/testcontainers-python/issues/161) ([#164](https://github.com/testcontainers/testcontainers-python/issues/164)) ([8addc11](https://github.com/testcontainers/testcontainers-python/commit/8addc111c94826c2a619a0880d48550673f4d7b9)) * Qdrant module ([#463](https://github.com/testcontainers/testcontainers-python/issues/463)) ([e8876f4](https://github.com/testcontainers/testcontainers-python/commit/e8876f422abeb29a7236f2174f7e7a324b7d26cb)) * remove accidentally added pip in dev dependencies ([#516](https://github.com/testcontainers/testcontainers-python/issues/516)) ([dee20a7](https://github.com/testcontainers/testcontainers-python/commit/dee20a76c88445b911d38b4704c2380114a66794)) * **ryuk:** Enable Ryuk test suite. Ryuk image 0.5.1 -> 0.7.0. Add RYUK_RECONNECTION_TIMEOUT env variable ([#509](https://github.com/testcontainers/testcontainers-python/issues/509)) ([472b2c2](https://github.com/testcontainers/testcontainers-python/commit/472b2c24aec232a04c00dd7dcd9a9f05f2dfaa66)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 29 +++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index e8e4b4dfa..fb3607581 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.2.0" + ".": "4.3.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index dea823097..4f26841b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## [4.3.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.2.0...testcontainers-v4.3.0) (2024-04-01) + + +### Features + +* **client:** Add custom User-Agent in Docker client as `tc-python/<version>` ([#507](https://github.com/testcontainers/testcontainers-python/issues/507)) ([dd55082](https://github.com/testcontainers/testcontainers-python/commit/dd55082991b3405038a90678a39e8c815f0d1fc8)) + + +### Bug Fixes + +* Add CassandraContainer ([#476](https://github.com/testcontainers/testcontainers-python/issues/476)) ([507e466](https://github.com/testcontainers/testcontainers-python/commit/507e466a1fa9ac64c254ceb9ae0d57f6bfd8c89d)) +* add chroma container ([#515](https://github.com/testcontainers/testcontainers-python/issues/515)) ([0729bf4](https://github.com/testcontainers/testcontainers-python/commit/0729bf4af957f8b6638cc204b108358745c0cfc9)) +* Add Weaviate module ([#492](https://github.com/testcontainers/testcontainers-python/issues/492)) ([90762e8](https://github.com/testcontainers/testcontainers-python/commit/90762e817bf49de6d6366212fb48e7edb67ab0c6)) +* **cassandra:** make cassandra dependency optional/test-only ([#518](https://github.com/testcontainers/testcontainers-python/issues/518)) ([bddbaeb](https://github.com/testcontainers/testcontainers-python/commit/bddbaeb20cbd147c429f8020395355402b8a7268)) +* **core:** allow setting docker command path for docker compose ([#512](https://github.com/testcontainers/testcontainers-python/issues/512)) ([63fcd52](https://github.com/testcontainers/testcontainers-python/commit/63fcd52ec2d6ded5f6413166a3690c1138e4dae0)) +* **google:** add support for Datastore emulator ([#508](https://github.com/testcontainers/testcontainers-python/issues/508)) ([3d891a5](https://github.com/testcontainers/testcontainers-python/commit/3d891a5ec62944d01d1bf3d6f70e6aec83f6e516)) +* Improved Oracle DB module ([#363](https://github.com/testcontainers/testcontainers-python/issues/363)) ([6e6d8e3](https://github.com/testcontainers/testcontainers-python/commit/6e6d8e3c919be3efa581704868193e66da54acf3)) +* inconsistent test runs for community modules ([#497](https://github.com/testcontainers/testcontainers-python/issues/497)) ([914f1e5](https://github.com/testcontainers/testcontainers-python/commit/914f1e55bcb3b10260788c3affb8426f77eb9036)) +* **kafka:** Add redpanda testcontainer module ([#441](https://github.com/testcontainers/testcontainers-python/issues/441)) ([451d278](https://github.com/testcontainers/testcontainers-python/commit/451d27865873bb75f4a09a26442572745408d013)) +* **kafka:** wait_for_logs in kafka container to reduce lib requirement ([#377](https://github.com/testcontainers/testcontainers-python/issues/377)) ([909107b](https://github.com/testcontainers/testcontainers-python/commit/909107b221417a39516f961364beb518d2756f45)) +* **keycloak:** container should use dedicated API endpoints to determine container readiness ([#490](https://github.com/testcontainers/testcontainers-python/issues/490)) ([2e27225](https://github.com/testcontainers/testcontainers-python/commit/2e272253148797759748bd40c42f797697d3163f)) +* **nats:** Client-Free(ish) NATS container ([#462](https://github.com/testcontainers/testcontainers-python/issues/462)) ([302c73d](https://github.com/testcontainers/testcontainers-python/commit/302c73ddaa7a6b5bc071ab0cc36d15461cae348b)) +* **new:** add a new Docker Registry test container ([#389](https://github.com/testcontainers/testcontainers-python/issues/389)) ([0f554fb](https://github.com/testcontainers/testcontainers-python/commit/0f554fbaa9511e0221806f57de971abedf1c0bf2)) +* pass doctests, s/doctest/doctests/, run them in gha, s/asyncpg/psycopg/ in doctest, fix keycloak flakiness: wait for first user ([#505](https://github.com/testcontainers/testcontainers-python/issues/505)) ([545240d](https://github.com/testcontainers/testcontainers-python/commit/545240dfdcb2a565ad7cef0e9813f03b9b6f910e)) +* pass updated keyword args to Publisher/Subscriber client in google/pubsub [#161](https://github.com/testcontainers/testcontainers-python/issues/161) ([#164](https://github.com/testcontainers/testcontainers-python/issues/164)) ([8addc11](https://github.com/testcontainers/testcontainers-python/commit/8addc111c94826c2a619a0880d48550673f4d7b9)) +* Qdrant module ([#463](https://github.com/testcontainers/testcontainers-python/issues/463)) ([e8876f4](https://github.com/testcontainers/testcontainers-python/commit/e8876f422abeb29a7236f2174f7e7a324b7d26cb)) +* remove accidentally added pip in dev dependencies ([#516](https://github.com/testcontainers/testcontainers-python/issues/516)) ([dee20a7](https://github.com/testcontainers/testcontainers-python/commit/dee20a76c88445b911d38b4704c2380114a66794)) +* **ryuk:** Enable Ryuk test suite. Ryuk image 0.5.1 -> 0.7.0. Add RYUK_RECONNECTION_TIMEOUT env variable ([#509](https://github.com/testcontainers/testcontainers-python/issues/509)) ([472b2c2](https://github.com/testcontainers/testcontainers-python/commit/472b2c24aec232a04c00dd7dcd9a9f05f2dfaa66)) + ## [4.2.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.1.0...testcontainers-v4.2.0) (2024-03-24) diff --git a/pyproject.toml b/pyproject.toml index 0f02966a4..135d74cbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.2.0" # auto-incremented by release-please +version = "4.3.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From b5c7a1b95af5470ee1b5109ed1fb8e1b3af52cf7 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Tue, 2 Apr 2024 06:50:13 -0400 Subject: [PATCH 343/425] fix: Pin MongoDB images and improve test coverage for maintained versions (#448) for some reason this causes an issue, see #401 for details --------- Co-authored-by: Vemund Santi --- .../testcontainers/mongodb/__init__.py | 23 ++++----- modules/mongodb/tests/test_mongodb.py | 48 +++---------------- 2 files changed, 15 insertions(+), 56 deletions(-) diff --git a/modules/mongodb/testcontainers/mongodb/__init__.py b/modules/mongodb/testcontainers/mongodb/__init__.py index eee623c6d..32e1f7484 100644 --- a/modules/mongodb/testcontainers/mongodb/__init__.py +++ b/modules/mongodb/testcontainers/mongodb/__init__.py @@ -30,25 +30,20 @@ class MongoDbContainer(DbContainer): >>> from testcontainers.mongodb import MongoDbContainer - >>> with MongoDbContainer("mongo:latest") as mongo: + >>> with MongoDbContainer("mongo:7.0.7") as mongo: ... db = mongo.get_connection_client().test ... # Insert a database entry ... result = db.restaurants.insert_one( ... { - ... "address": { - ... "street": "2 Avenue", - ... "zipcode": "10075", - ... "building": "1480", - ... "coord": [-73.9557413, 40.7720266] - ... }, - ... "borough": "Manhattan", - ... "cuisine": "Italian", ... "name": "Vella", - ... "restaurant_id": "41704620" + ... "cuisine": "Italian", + ... "restaurant_id": "123456" ... } ... ) ... # Find the restaurant document - ... cursor = db.restaurants.find({"borough": "Manhattan"}) + ... result = db.restaurants.find_one({"name": "Vella"}) + ... result["restaurant_id"] + '123456' """ def __init__( @@ -62,9 +57,9 @@ def __init__( ) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image=image, **kwargs) - self.username = username or os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") - self.password = password or os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") - self.dbname = dbname or os.environ.get("MONGO_DB", "test") + self.username = username if username else os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") + self.password = password if password else os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") + self.dbname = dbname if dbname else os.environ.get("MONGO_DB", "test") self.port = port self.with_exposed_ports(self.port) diff --git a/modules/mongodb/tests/test_mongodb.py b/modules/mongodb/tests/test_mongodb.py index 5b9d6be21..34642103e 100644 --- a/modules/mongodb/tests/test_mongodb.py +++ b/modules/mongodb/tests/test_mongodb.py @@ -2,42 +2,12 @@ from pymongo import MongoClient from pymongo.errors import OperationFailure -from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for from testcontainers.mongodb import MongoDbContainer -def test_docker_generic_db(): - with DockerContainer("mongo:latest").with_bind_ports(27017, 27017) as mongo_container: - - def connect(): - host = mongo_container.get_container_host_ip() - port = mongo_container.get_exposed_port(27017) - return MongoClient(f"mongodb://{host}:{port}") - - db = wait_for(connect).primer - result = db.restaurants.insert_one( - { - "address": { - "street": "2 Avenue", - "zipcode": "10075", - "building": "1480", - "coord": [-73.9557413, 40.7720266], - }, - "borough": "Manhattan", - "cuisine": "Italian", - "name": "Vella", - "restaurant_id": "41704620", - } - ) - assert result.inserted_id - cursor = db.restaurants.find({"borough": "Manhattan"}) - for document in cursor: - assert document - - -def test_docker_run_mongodb(): - with MongoDbContainer("mongo:latest") as mongo: +@pytest.mark.parametrize("version", ["7.0.7", "6.0.14", "5.0.26"]) +def test_docker_run_mongodb(version: str): + with MongoDbContainer(f"mongo:{version}") as mongo: db = mongo.get_connection_client().test doc = { "address": { @@ -51,14 +21,8 @@ def test_docker_run_mongodb(): "name": "Vella", "restaurant_id": "41704620", } - db.restaurants.insert_one(doc) + result = db.restaurants.insert_one(doc) + assert result.inserted_id + cursor = db.restaurants.find({"borough": "Manhattan"}) assert cursor.next()["restaurant_id"] == doc["restaurant_id"] - - -def test_docker_run_mongodb_connect_without_credentials(): - with MongoDbContainer() as mongo: - connection_url = f"mongodb://{mongo.get_container_host_ip()}:" f"{mongo.get_exposed_port(mongo.port)}" - db = MongoClient(connection_url).test - with pytest.raises(OperationFailure): - db.restaurants.insert_one({}) From 4872ea5759347e10150c0d80e4e7bbce3d59c410 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Tue, 2 Apr 2024 11:16:47 -0400 Subject: [PATCH 344/425] fix(core): env vars not being respected due to constructor call (#524) fix #521 --- core/testcontainers/core/docker_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index b4e968d02..c72c48e0c 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -41,7 +41,8 @@ def __init__(self, **kwargs) -> None: if docker_host: LOGGER.info(f"using host {docker_host}") - self.client = docker.DockerClient(base_url=docker_host) + os.environ["DOCKER_HOST"] = docker_host + self.client = docker.from_env(**kwargs) else: self.client = docker.from_env(**kwargs) self.client.api.headers["x-tc-sid"] = SESSION_ID From 0fb4aefbed3cfd1085cd8b4c9f176a4058daa525 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:38:45 -0400 Subject: [PATCH 345/425] chore(main): release testcontainers 4.3.1 (#522) :robot: I have created a release *beep* *boop* --- ## [4.3.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.0...testcontainers-v4.3.1) (2024-04-02) ### Bug Fixes * **core:** env vars not being respected due to constructor call ([#524](https://github.com/testcontainers/testcontainers-python/issues/524)) ([4872ea5](https://github.com/testcontainers/testcontainers-python/commit/4872ea5759347e10150c0d80e4e7bbce3d59c410)), closes [#521](https://github.com/testcontainers/testcontainers-python/issues/521) * Pin MongoDB images and improve test coverage for maintained versions ([#448](https://github.com/testcontainers/testcontainers-python/issues/448)) ([b5c7a1b](https://github.com/testcontainers/testcontainers-python/commit/b5c7a1b95af5470ee1b5109ed1fb8e1b3af52cf7)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index fb3607581..fa1ad28ce 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.3.0" + ".": "4.3.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f26841b1..761b2b2cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [4.3.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.0...testcontainers-v4.3.1) (2024-04-02) + + +### Bug Fixes + +* **core:** env vars not being respected due to constructor call ([#524](https://github.com/testcontainers/testcontainers-python/issues/524)) ([4872ea5](https://github.com/testcontainers/testcontainers-python/commit/4872ea5759347e10150c0d80e4e7bbce3d59c410)), closes [#521](https://github.com/testcontainers/testcontainers-python/issues/521) +* Pin MongoDB images and improve test coverage for maintained versions ([#448](https://github.com/testcontainers/testcontainers-python/issues/448)) ([b5c7a1b](https://github.com/testcontainers/testcontainers-python/commit/b5c7a1b95af5470ee1b5109ed1fb8e1b3af52cf7)) + ## [4.3.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.2.0...testcontainers-v4.3.0) (2024-04-01) diff --git a/pyproject.toml b/pyproject.toml index 135d74cbc..2d2fbeb55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.3.0" # auto-incremented by release-please +version = "4.3.1" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From d5b855323be06f8d1395dd480a347f0efef75703 Mon Sep 17 00:00:00 2001 From: Vemund Santi Date: Wed, 3 Apr 2024 14:40:39 +0200 Subject: [PATCH 346/425] fix(core): Improve typing for common container usage scenarios (#523) Improves type hints for type checking in a common use cases: ```python with MySqlContainer("mysql:8").with_env("some", "value") as mysql: url = mysql.get_connection_url() # get_connection_url would previously be an unknown member here ``` And, also improved type hinting for the custom `DockerClient`'s `run` command, where the linter no longer reports an error due to missing parameter types: ```python DockerClient.run("nginx") # Previously this would report "Argument missing for parameter "image" ``` --- core/testcontainers/core/container.py | 24 ++++++++++++----------- core/testcontainers/core/docker_client.py | 17 ++++++++++++++-- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 3e1e1ba19..559a4ffe7 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional import docker.errors +from typing_extensions import Self from testcontainers.core.config import ( RYUK_DISABLED, @@ -53,29 +54,29 @@ def __init__( self._name = None self._kwargs = kwargs - def with_env(self, key: str, value: str) -> "DockerContainer": + def with_env(self, key: str, value: str) -> Self: self.env[key] = value return self - def with_bind_ports(self, container: int, host: Optional[int] = None) -> "DockerContainer": + def with_bind_ports(self, container: int, host: Optional[int] = None) -> Self: self.ports[container] = host return self - def with_exposed_ports(self, *ports: int) -> "DockerContainer": + def with_exposed_ports(self, *ports: int) -> Self: for port in ports: self.ports[port] = None return self - def with_kwargs(self, **kwargs) -> "DockerContainer": + def with_kwargs(self, **kwargs) -> Self: self._kwargs = kwargs return self - def maybe_emulate_amd64(self) -> "DockerContainer": + def maybe_emulate_amd64(self) -> Self: if is_arm(): return self.with_kwargs(platform="linux/amd64") return self - def start(self): + def start(self) -> Self: if not RYUK_DISABLED and self.image != RYUK_IMAGE: logger.debug("Creating Ryuk container") Reaper.get_instance() @@ -95,10 +96,11 @@ def start(self): return self def stop(self, force=True, delete_volume=True) -> None: - self._container.remove(force=force, v=delete_volume) + if self._container: + self._container.remove(force=force, v=delete_volume) self.get_docker_client().client.close() - def __enter__(self): + def __enter__(self) -> Self: return self.start() def __exit__(self, exc_type, exc_val, exc_tb) -> None: @@ -138,15 +140,15 @@ def get_exposed_port(self, port: int) -> str: return port return mapped_port - def with_command(self, command: str) -> "DockerContainer": + def with_command(self, command: str) -> Self: self._command = command return self - def with_name(self, name: str) -> "DockerContainer": + def with_name(self, name: str) -> Self: self._name = name return self - def with_volume_mapping(self, host: str, container: str, mode: str = "ro") -> "DockerContainer": + def with_volume_mapping(self, host: str, container: str, mode: str = "ro") -> Self: mapping = {"bind": container, "mode": mode} self.volumes[host] = mapping return self diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index c72c48e0c..22a9a4ef8 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -18,10 +18,11 @@ import urllib.parse from os.path import exists from pathlib import Path -from typing import Optional, Union +from typing import Callable, Optional, TypeVar, Union import docker from docker.models.containers import Container, ContainerCollection +from typing_extensions import ParamSpec from testcontainers.core.labels import SESSION_ID, create_labels from testcontainers.core.utils import default_gateway_ip, inside_container, setup_logger @@ -30,6 +31,18 @@ TC_FILE = ".testcontainers.properties" TC_GLOBAL = Path.home() / TC_FILE +_P = ParamSpec("_P") +_T = TypeVar("_T") + + +def _wrapped_container_collection(function: Callable[_P, _T]) -> Callable[_P, _T]: + + @ft.wraps(ContainerCollection.run) + def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: + return function(*args, **kwargs) + + return wrapper + class DockerClient: """ @@ -48,7 +61,7 @@ def __init__(self, **kwargs) -> None: self.client.api.headers["x-tc-sid"] = SESSION_ID self.client.api.headers["User-Agent"] = "tc-python/" + importlib.metadata.version("testcontainers") - @ft.wraps(ContainerCollection.run) + @_wrapped_container_collection def run( self, image: str, From 9a897483686c977982d527e43d42634116d1dca7 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Fri, 5 Apr 2024 12:33:09 +0200 Subject: [PATCH 347/425] chore(codestyle): switch to `ruff` from `black` for code formatting (#529) Changed pre-commit config. Some files became re-formatted. --- .pre-commit-config.yaml | 10 +++------- core/testcontainers/core/docker_client.py | 1 - modules/influxdb/testcontainers/influxdb.py | 1 + modules/mongodb/testcontainers/mongodb/__init__.py | 2 +- modules/mssql/testcontainers/mssql/__init__.py | 2 +- modules/mysql/testcontainers/mysql/__init__.py | 2 +- modules/mysql/tests/test_mysql.py | 7 ++++--- .../opensearch/testcontainers/opensearch/__init__.py | 2 +- modules/oracle-free/testcontainers/oracle/__init__.py | 2 +- modules/rabbitmq/testcontainers/rabbitmq/__init__.py | 2 +- 10 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5b94bdde..5808a0000 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,18 +9,14 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - - repo: https://github.com/psf/black-pre-commit-mirror - rev: '24.2.0' - hooks: - - id: black - args: [ '--config', 'pyproject.toml' ] - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.3.0' + rev: 'v0.3.5' hooks: - id: ruff # Explicitly setting config to prevent Ruff from using `pyproject.toml` in sub packages. args: [ '--fix', '--exit-non-zero-on-fix', '--config', 'pyproject.toml' ] + - id: ruff-format + args: [ '--config', 'pyproject.toml' ] # - repo: local # hooks: diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 22a9a4ef8..89db0fbfc 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -36,7 +36,6 @@ def _wrapped_container_collection(function: Callable[_P, _T]) -> Callable[_P, _T]: - @ft.wraps(ContainerCollection.run) def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: return function(*args, **kwargs) diff --git a/modules/influxdb/testcontainers/influxdb.py b/modules/influxdb/testcontainers/influxdb.py index 4b9d9b905..d8956e992 100644 --- a/modules/influxdb/testcontainers/influxdb.py +++ b/modules/influxdb/testcontainers/influxdb.py @@ -26,6 +26,7 @@ - because the InfluxDB clients are different for 1.x and 2.x versions, so you won't have to install dependencies that you do not need """ + from typing import Optional from requests import get diff --git a/modules/mongodb/testcontainers/mongodb/__init__.py b/modules/mongodb/testcontainers/mongodb/__init__.py index 32e1f7484..4a436b195 100644 --- a/modules/mongodb/testcontainers/mongodb/__init__.py +++ b/modules/mongodb/testcontainers/mongodb/__init__.py @@ -53,7 +53,7 @@ def __init__( username: Optional[str] = None, password: Optional[str] = None, dbname: Optional[str] = None, - **kwargs + **kwargs, ) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image=image, **kwargs) diff --git a/modules/mssql/testcontainers/mssql/__init__.py b/modules/mssql/testcontainers/mssql/__init__.py index 98b668269..3bfe861b4 100644 --- a/modules/mssql/testcontainers/mssql/__init__.py +++ b/modules/mssql/testcontainers/mssql/__init__.py @@ -30,7 +30,7 @@ def __init__( port: int = 1433, dbname: str = "tempdb", dialect: str = "mssql+pymssql", - **kwargs + **kwargs, ) -> None: raise_for_deprecated_parameter(kwargs, "user", "username") super().__init__(image, **kwargs) diff --git a/modules/mysql/testcontainers/mysql/__init__.py b/modules/mysql/testcontainers/mysql/__init__.py index 65b317b0c..a5b839273 100644 --- a/modules/mysql/testcontainers/mysql/__init__.py +++ b/modules/mysql/testcontainers/mysql/__init__.py @@ -48,7 +48,7 @@ def __init__( password: Optional[str] = None, dbname: Optional[str] = None, port: int = 3306, - **kwargs + **kwargs, ) -> None: raise_for_deprecated_parameter(kwargs, "MYSQL_USER", "username") raise_for_deprecated_parameter(kwargs, "MYSQL_ROOT_PASSWORD", "root_password") diff --git a/modules/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py index a84df4d13..3506960bf 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/modules/mysql/tests/test_mysql.py @@ -40,9 +40,10 @@ def test_docker_run_mariadb(): def test_docker_env_variables(): - with mock.patch.dict("os.environ", MYSQL_USER="demo", MYSQL_DATABASE="custom_db"), MySqlContainer( - "mariadb:10.6.5" - ).with_bind_ports(3306, 32785).maybe_emulate_amd64() as container: + with ( + mock.patch.dict("os.environ", MYSQL_USER="demo", MYSQL_DATABASE="custom_db"), + MySqlContainer("mariadb:10.6.5").with_bind_ports(3306, 32785).maybe_emulate_amd64() as container, + ): url = container.get_connection_url() pattern = r"mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db" assert re.match(pattern, url) diff --git a/modules/opensearch/testcontainers/opensearch/__init__.py b/modules/opensearch/testcontainers/opensearch/__init__.py index f889c9934..06d3a7671 100644 --- a/modules/opensearch/testcontainers/opensearch/__init__.py +++ b/modules/opensearch/testcontainers/opensearch/__init__.py @@ -35,7 +35,7 @@ def __init__( image: str = "opensearchproject/opensearch:2.4.0", port: int = 9200, security_enabled: bool = False, - **kwargs + **kwargs, ) -> None: """ Args: diff --git a/modules/oracle-free/testcontainers/oracle/__init__.py b/modules/oracle-free/testcontainers/oracle/__init__.py index 2b903ac54..03f525a71 100644 --- a/modules/oracle-free/testcontainers/oracle/__init__.py +++ b/modules/oracle-free/testcontainers/oracle/__init__.py @@ -36,7 +36,7 @@ def __init__( password: Optional[str] = None, port: int = 1521, dbname: Optional[str] = None, - **kwargs + **kwargs, ) -> None: super().__init__(image=image, **kwargs) diff --git a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py index 0a5486025..3e5ad0b33 100644 --- a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -31,7 +31,7 @@ def __init__( port: Optional[int] = None, username: Optional[str] = None, password: Optional[str] = None, - **kwargs + **kwargs, ) -> None: """Initialize the RabbitMQ test container. From 13262785dedf32a97e392afc1a758616995dc9d9 Mon Sep 17 00:00:00 2001 From: Jakob Beckmann <32326425+f4z3r@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:45:41 +0200 Subject: [PATCH 348/425] fix(vault): add support for HashiCorp Vault container (#366) Add support for a Vault container. --- index.rst | 1 + modules/vault/README.rst | 2 + .../vault/testcontainers/vault/__init__.py | 74 +++++++++++++++++++ modules/vault/tests/test_vault.py | 41 ++++++++++ poetry.lock | 20 ++++- pyproject.toml | 4 + 6 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 modules/vault/README.rst create mode 100644 modules/vault/testcontainers/vault/__init__.py create mode 100644 modules/vault/tests/test_vault.py diff --git a/index.rst b/index.rst index 2a2bc6599..4f3dad802 100644 --- a/index.rst +++ b/index.rst @@ -42,6 +42,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/redis/README modules/registry/README modules/selenium/README + modules/vault/README modules/weaviate/README Getting Started diff --git a/modules/vault/README.rst b/modules/vault/README.rst new file mode 100644 index 000000000..71c079dfa --- /dev/null +++ b/modules/vault/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.vault.VaultContainer +.. title:: testcontainers.vault.VaultContainer diff --git a/modules/vault/testcontainers/vault/__init__.py b/modules/vault/testcontainers/vault/__init__.py new file mode 100644 index 000000000..5f50cdd4e --- /dev/null +++ b/modules/vault/testcontainers/vault/__init__.py @@ -0,0 +1,74 @@ +# +# 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. + +from http.client import HTTPException +from urllib.request import urlopen + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class VaultContainer(DockerContainer): + """ + Vault container. + + Example: + + .. doctest:: + + >>> from testcontainers.vault import VaultContainer + >>> import hvac + + >>> with VaultContainer("hashicorp/vault:1.16.1") as vault_container: + ... connection_url = vault_container.get_connection_url() + ... client = hvac.Client(url=connection_url, token=vault_container.root_token) + ... assert client.is_authenticated() + ... # use root client to perform desired actions, e.g. + ... policies = client.sys.list_acl_policies() + """ + + def __init__( + self, + image: str = "hashicorp/vault:latest", + port: int = 8200, + root_token: str = "toor", + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + self.port = port + self.root_token = root_token + self.with_exposed_ports(self.port) + self.with_env("VAULT_DEV_ROOT_TOKEN_ID", self.root_token) + + def get_connection_url(self) -> str: + """ + Get the connection URL used to connect to the Vault container. + + Returns: + str: The address to connect to. + """ + host_ip = self.get_container_host_ip() + exposed_port = self.get_exposed_port(self.port) + return f"http://{host_ip}:{exposed_port}" + + @wait_container_is_ready(HTTPException) + def _healthcheck(self) -> None: + url = f"{self.get_connection_url()}/v1/sys/health" + with urlopen(url) as res: + if res.status > 299: + raise HTTPException() + + def start(self) -> "VaultContainer": + super().start() + self._healthcheck() + return self diff --git a/modules/vault/tests/test_vault.py b/modules/vault/tests/test_vault.py new file mode 100644 index 000000000..54017d2f9 --- /dev/null +++ b/modules/vault/tests/test_vault.py @@ -0,0 +1,41 @@ +import hvac +from testcontainers.vault import VaultContainer + + +def test_docker_run_vault(): + config = VaultContainer("hashicorp/vault:1.16.1") + with config as vault: + url = vault.get_connection_url() + client = hvac.Client(url=url) + status = client.sys.read_health_status() + assert status.status_code == 200 + + +def test_docker_run_vault_act_as_root(): + config = VaultContainer("hashicorp/vault:1.16.1") + with config as vault: + url = vault.get_connection_url() + client = hvac.Client(url=url, token=vault.root_token) + assert client.is_authenticated() + assert client.sys.is_initialized() + assert not client.sys.is_sealed() + + client.sys.enable_secrets_engine( + backend_type="kv", + path="secrets", + config={ + "version": "2", + }, + ) + client.secrets.kv.v2.create_or_update_secret( + path="my-secret", + mount_point="secrets", + secret={ + "pssst": "this is secret", + }, + ) + resp = client.secrets.kv.v2.read_secret( + path="my-secret", + mount_point="secrets", + ) + assert resp["data"]["data"]["pssst"] == "this is secret" diff --git a/poetry.lock b/poetry.lock index 614e42e5f..a2f81d3b9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1449,6 +1449,23 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +[[package]] +name = "hvac" +version = "2.1.0" +description = "HashiCorp Vault API client" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "hvac-2.1.0-py3-none-any.whl", hash = "sha256:73bc91e58c3fc7c6b8107cdaca9cb71fa0a893dfd80ffbc1c14e20f24c0c29d7"}, + {file = "hvac-2.1.0.tar.gz", hash = "sha256:b48bcda11a4ab0a7b6c47232c7ba7c87fda318ae2d4a7662800c465a78742894"}, +] + +[package.dependencies] +requests = ">=2.27.1,<3.0.0" + +[package.extras] +parser = ["pyhcl (>=0.4.4,<0.5.0)"] + [[package]] name = "hyperframe" version = "6.0.1" @@ -4188,9 +4205,10 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] +vault = [] weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "af9f21cb52ebd761ba91c852c9839d982124c149e77abdc079fc5657cecb9ff7" +content-hash = "233dfd72d07a555973aafc3fe3b6676574403b9fe4bb2c0230d455cff8aa2933" diff --git a/pyproject.toml b/pyproject.toml index 2d2fbeb55..08c9c68bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ packages = [ { include = "testcontainers", from = "modules/redis" }, { include = "testcontainers", from = "modules/registry" }, { include = "testcontainers", from = "modules/selenium" }, + { include = "testcontainers", from = "modules/vault" }, { include = "testcontainers", from = "modules/weaviate" } ] @@ -125,6 +126,7 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] +vault = [] weaviate = ["weaviate-client"] chroma = ["chromadb-client"] @@ -144,6 +146,7 @@ psycopg = "*" cassandra-driver = "*" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" +hvac = "*" [[tool.poetry.source]] name = "PyPI" @@ -262,6 +265,7 @@ mypy_path = [ # "modules/rabbitmq", # "modules/redis", # "modules/selenium" +# "modules/vault" # "modules/weaviate" ] enable_error_code = [ From 3be6da335ba2026b4800dfd6a19cda4ca8e52be8 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Mon, 8 Apr 2024 05:24:39 -0400 Subject: [PATCH 349/425] fix(core): make config editable to avoid monkeypatching.1 (#532) see #531: I am using testcontainers within a library that provides some pytest-fixtures. In order for this to work I have change some settings. As I can not guarantee that that my lib is imported before testcontainers I need to monkeypatch the settings. This is much easier if I only need to monkeypatch the config file and not all modules that use configurations. I would argue that for a potential library as this, this is a better design. Also one can easier see that the given UPERCASE variable is not a constant but rather a setting. Co-authored-by: Carli* Freudenberg --- core/testcontainers/core/config.py | 61 +++++++++++++++++++ core/testcontainers/core/container.py | 18 ++---- core/testcontainers/core/docker_client.py | 28 +-------- core/testcontainers/core/labels.py | 4 +- core/testcontainers/core/waiting_utils.py | 10 +-- core/tests/test_ryuk.py | 6 +- .../testcontainers/arangodb/__init__.py | 4 +- modules/k3s/testcontainers/k3s/__init__.py | 4 +- .../neo4j/testcontainers/neo4j/__init__.py | 4 +- .../testcontainers/postgres/__init__.py | 8 +-- .../qdrant/testcontainers/qdrant/__init__.py | 4 +- 11 files changed, 91 insertions(+), 60 deletions(-) diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 0c1b5e0c2..391c88bfa 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -1,4 +1,7 @@ +from dataclasses import dataclass, field from os import environ +from os.path import exists +from pathlib import Path MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120)) SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) @@ -9,3 +12,61 @@ RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true" RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock") RYUK_RECONNECTION_TIMEOUT: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s") + +TC_FILE = ".testcontainers.properties" +TC_GLOBAL = Path.home() / TC_FILE + + +def read_tc_properties() -> dict[str, str]: + """ + Read the .testcontainers.properties for settings. (see the Java implementation for details) + Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later. + + :return: the merged properties from the sources. + """ + tc_files = [item for item in [TC_GLOBAL] if exists(item)] + if not tc_files: + return {} + settings = {} + + for file in tc_files: + with open(file) as contents: + tuples = [line.split("=") for line in contents.readlines() if "=" in line] + settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} + return settings + + +@dataclass +class TestcontainersConfiguration: + max_tries: int = MAX_TRIES + sleep_time: int = SLEEP_TIME + ryuk_image: str = RYUK_IMAGE + ryuk_privileged: bool = RYUK_PRIVILEGED + ryuk_disabled: bool = RYUK_DISABLED + ryuk_docker_socket: str = RYUK_DOCKER_SOCKET + ryuk_reconnection_timeout: str = RYUK_RECONNECTION_TIMEOUT + tc_properties: dict[str, str] = field(default_factory=read_tc_properties) + + def tc_properties_get_tc_host(self): + return self.tc_properties.get("tc.host") + + @property + def timeout(self): + return self.max_tries * self.sleep_time + + +testcontainers_config = TestcontainersConfiguration() + +__all__ = [ + # the public API of this module + "testcontainers_config", + # and all the legacy things that are deprecated: + "MAX_TRIES", + "SLEEP_TIME", + "TIMEOUT", + "RYUK_IMAGE", + "RYUK_PRIVILEGED", + "RYUK_DISABLED", + "RYUK_DOCKER_SOCKET", + "RYUK_RECONNECTION_TIMEOUT", +] diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 559a4ffe7..efa06734d 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -6,13 +6,7 @@ import docker.errors from typing_extensions import Self -from testcontainers.core.config import ( - RYUK_DISABLED, - RYUK_DOCKER_SOCKET, - RYUK_IMAGE, - RYUK_PRIVILEGED, - RYUK_RECONNECTION_TIMEOUT, -) +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID @@ -77,7 +71,7 @@ def maybe_emulate_amd64(self) -> Self: return self def start(self) -> Self: - if not RYUK_DISABLED and self.image != RYUK_IMAGE: + if not c.ryuk_disabled and self.image != c.ryuk_image: logger.debug("Creating Ryuk container") Reaper.get_instance() logger.info("Pulling image %s", self.image) @@ -201,12 +195,12 @@ def _create_instance(cls) -> "Reaper": logger.debug(f"Creating new Reaper for session: {SESSION_ID}") Reaper._container = ( - DockerContainer(RYUK_IMAGE) + DockerContainer(c.ryuk_image) .with_name(f"testcontainers-ryuk-{SESSION_ID}") .with_exposed_ports(8080) - .with_volume_mapping(RYUK_DOCKER_SOCKET, "/var/run/docker.sock", "rw") - .with_kwargs(privileged=RYUK_PRIVILEGED, auto_remove=True) - .with_env("RYUK_RECONNECTION_TIMEOUT", RYUK_RECONNECTION_TIMEOUT) + .with_volume_mapping(c.ryuk_docker_socket, "/var/run/docker.sock", "rw") + .with_kwargs(privileged=c.ryuk_privileged, auto_remove=True) + .with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout) .start() ) wait_for_logs(Reaper._container, r".* Started!") diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 89db0fbfc..9ff6170e6 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -16,20 +16,17 @@ import os import urllib import urllib.parse -from os.path import exists -from pathlib import Path from typing import Callable, Optional, TypeVar, Union import docker from docker.models.containers import Container, ContainerCollection from typing_extensions import ParamSpec +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.labels import SESSION_ID, create_labels from testcontainers.core.utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) -TC_FILE = ".testcontainers.properties" -TC_GLOBAL = Path.home() / TC_FILE _P = ParamSpec("_P") _T = TypeVar("_T") @@ -185,26 +182,5 @@ def host(self) -> str: return "localhost" -@ft.cache -def read_tc_properties() -> dict[str, str]: - """ - Read the .testcontainers.properties for settings. (see the Java implementation for details) - Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later. - - :return: the merged properties from the sources. - """ - tc_files = [item for item in [TC_GLOBAL] if exists(item)] - if not tc_files: - return {} - settings = {} - - for file in tc_files: - tuples = [] - with open(file) as contents: - tuples = [line.split("=") for line in contents.readlines() if "=" in line] - settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} - return settings - - def get_docker_host() -> Optional[str]: - return read_tc_properties().get("tc.host") or os.getenv("DOCKER_HOST") + return c.tc_properties_get_tc_host() or os.getenv("DOCKER_HOST") diff --git a/core/testcontainers/core/labels.py b/core/testcontainers/core/labels.py index 13937a5e8..144e4365e 100644 --- a/core/testcontainers/core/labels.py +++ b/core/testcontainers/core/labels.py @@ -1,7 +1,7 @@ from typing import Optional from uuid import uuid4 -from testcontainers.core.config import RYUK_IMAGE +from testcontainers.core.config import testcontainers_config as c SESSION_ID: str = str(uuid4()) LABEL_SESSION_ID = "org.testcontainers.session-id" @@ -13,7 +13,7 @@ def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str labels = {} labels[LABEL_LANG] = "python" - if image == RYUK_IMAGE: + if image == c.ryuk_image: return labels labels[LABEL_SESSION_ID] = SESSION_ID diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index ea52683d5..4eb7ad890 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -19,7 +19,7 @@ import wrapt -from testcontainers.core import config +from testcontainers.core.config import testcontainers_config as config from testcontainers.core.utils import setup_logger if TYPE_CHECKING: @@ -54,18 +54,18 @@ def wrapper(wrapped: Callable, instance: Any, args: list, kwargs: dict) -> Any: logger.info("Waiting for %s to be ready ...", instance) exception = None - for attempt_no in range(config.MAX_TRIES): + for attempt_no in range(config.max_tries): try: return wrapped(*args, **kwargs) except transient_exceptions as e: logger.debug( - f"Connection attempt '{attempt_no + 1}' of '{config.MAX_TRIES + 1}' " + f"Connection attempt '{attempt_no + 1}' of '{config.max_tries + 1}' " f"failed: {traceback.format_exc()}" ) - time.sleep(config.SLEEP_TIME) + time.sleep(config.sleep_time) exception = e raise TimeoutError( - f"Wait time ({config.TIMEOUT}s) exceeded for {wrapped.__name__}(args: {args}, kwargs: " + f"Wait time ({config.timeout}s) exceeded for {wrapped.__name__}(args: {args}, kwargs: " f"{kwargs}). Exception: {exception}" ) diff --git a/core/tests/test_ryuk.py b/core/tests/test_ryuk.py index e21b045ba..e081d2c07 100644 --- a/core/tests/test_ryuk.py +++ b/core/tests/test_ryuk.py @@ -5,7 +5,7 @@ from docker import DockerClient from docker.errors import NotFound -from testcontainers.core import container as container_module +from testcontainers.core.config import testcontainers_config from testcontainers.core.container import Reaper from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -13,7 +13,7 @@ def test_wait_for_reaper(monkeypatch: MonkeyPatch): Reaper.delete_instance() - monkeypatch.setattr(container_module, "RYUK_RECONNECTION_TIMEOUT", "0.1s") + monkeypatch.setattr(testcontainers_config, "ryuk_reconnection_timeout", "0.1s") docker_client = DockerClient() container = DockerContainer("hello-world").start() @@ -40,7 +40,7 @@ def test_wait_for_reaper(monkeypatch: MonkeyPatch): def test_container_without_ryuk(monkeypatch: MonkeyPatch): Reaper.delete_instance() - monkeypatch.setattr(container_module, "RYUK_DISABLED", True) + monkeypatch.setattr(testcontainers_config, "ryuk_disabled", True) with DockerContainer("hello-world") as container: wait_for_logs(container, "Hello from Docker!") assert Reaper._instance is None diff --git a/modules/arangodb/testcontainers/arangodb/__init__.py b/modules/arangodb/testcontainers/arangodb/__init__.py index a7c954652..9ea36f6ea 100644 --- a/modules/arangodb/testcontainers/arangodb/__init__.py +++ b/modules/arangodb/testcontainers/arangodb/__init__.py @@ -5,7 +5,7 @@ import typing from os import environ -from testcontainers.core.config import TIMEOUT +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_for_logs @@ -90,4 +90,4 @@ def get_connection_url(self) -> str: return f"http://{self.get_container_host_ip()}:{port}" def _connect(self) -> None: - wait_for_logs(self, predicate="is ready for business", timeout=TIMEOUT) + wait_for_logs(self, predicate="is ready for business", timeout=c.timeout) diff --git a/modules/k3s/testcontainers/k3s/__init__.py b/modules/k3s/testcontainers/k3s/__init__.py index 045e2eb5d..2682df356 100644 --- a/modules/k3s/testcontainers/k3s/__init__.py +++ b/modules/k3s/testcontainers/k3s/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -from testcontainers.core.config import MAX_TRIES +from testcontainers.core.config import testcontainers_config from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -46,7 +46,7 @@ def __init__(self, image="rancher/k3s:latest", **kwargs) -> None: self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw") def _connect(self) -> None: - wait_for_logs(self, predicate="Node controller sync successful", timeout=MAX_TRIES) + wait_for_logs(self, predicate="Node controller sync successful", timeout=testcontainers_config.timeout) def start(self) -> "K3SContainer": super().start() diff --git a/modules/neo4j/testcontainers/neo4j/__init__.py b/modules/neo4j/testcontainers/neo4j/__init__.py index 26f46dc61..7939c013f 100644 --- a/modules/neo4j/testcontainers/neo4j/__init__.py +++ b/modules/neo4j/testcontainers/neo4j/__init__.py @@ -15,7 +15,7 @@ from typing import Optional from neo4j import Driver, GraphDatabase -from testcontainers.core.config import TIMEOUT +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -62,7 +62,7 @@ def get_connection_url(self) -> str: @wait_container_is_ready() def _connect(self) -> None: - wait_for_logs(self, "Remote interface available at", TIMEOUT) + wait_for_logs(self, "Remote interface available at", c.timeout) # Then we actually check that the container really is listening with self.get_driver() as driver: diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index 3810ea0f2..9b347aa61 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -14,7 +14,7 @@ from time import sleep from typing import Optional -from testcontainers.core.config import MAX_TRIES, SLEEP_TIME +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -91,15 +91,15 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] = @wait_container_is_ready() def _connect(self) -> None: - wait_for_logs(self, ".*database system is ready to accept connections.*", MAX_TRIES, SLEEP_TIME) + wait_for_logs(self, ".*database system is ready to accept connections.*", c.max_tries, c.sleep_time) count = 0 - while count < MAX_TRIES: + while count < c.max_tries: status, _ = self.exec(f"pg_isready -hlocalhost -p{self.port} -U{self.username}") if status == 0: return - sleep(SLEEP_TIME) + sleep(c.sleep_time) count += 1 raise RuntimeError("Postgres could not get into a ready state") diff --git a/modules/qdrant/testcontainers/qdrant/__init__.py b/modules/qdrant/testcontainers/qdrant/__init__.py index ac9279955..d36fe62ee 100644 --- a/modules/qdrant/testcontainers/qdrant/__init__.py +++ b/modules/qdrant/testcontainers/qdrant/__init__.py @@ -15,7 +15,7 @@ from pathlib import Path from typing import Optional -from testcontainers.core.config import TIMEOUT +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.generic import DbContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -61,7 +61,7 @@ def _configure(self) -> None: @wait_container_is_ready() def _connect(self) -> None: - wait_for_logs(self, ".*Actix runtime found; starting in Actix runtime.*", TIMEOUT) + wait_for_logs(self, ".*Actix runtime found; starting in Actix runtime.*", c.timeout) def get_client(self, **kwargs): """ From fe2275879b2a6badb2441f67094607ac9d3dce8a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 05:27:17 -0400 Subject: [PATCH 350/425] chore(main): release testcontainers 4.3.2 (#530) :robot: I have created a release *beep* *boop* --- ## [4.3.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.1...testcontainers-v4.3.2) (2024-04-08) ### Bug Fixes * **core:** Improve typing for common container usage scenarios ([#523](https://github.com/testcontainers/testcontainers-python/issues/523)) ([d5b8553](https://github.com/testcontainers/testcontainers-python/commit/d5b855323be06f8d1395dd480a347f0efef75703)) * **core:** make config editable to avoid monkeypatching.1 ([#532](https://github.com/testcontainers/testcontainers-python/issues/532)) ([3be6da3](https://github.com/testcontainers/testcontainers-python/commit/3be6da335ba2026b4800dfd6a19cda4ca8e52be8)) * **vault:** add support for HashiCorp Vault container ([#366](https://github.com/testcontainers/testcontainers-python/issues/366)) ([1326278](https://github.com/testcontainers/testcontainers-python/commit/13262785dedf32a97e392afc1a758616995dc9d9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index fa1ad28ce..b2b1cb883 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.3.1" + ".": "4.3.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 761b2b2cc..b7a007b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [4.3.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.1...testcontainers-v4.3.2) (2024-04-08) + + +### Bug Fixes + +* **core:** Improve typing for common container usage scenarios ([#523](https://github.com/testcontainers/testcontainers-python/issues/523)) ([d5b8553](https://github.com/testcontainers/testcontainers-python/commit/d5b855323be06f8d1395dd480a347f0efef75703)) +* **core:** make config editable to avoid monkeypatching.1 ([#532](https://github.com/testcontainers/testcontainers-python/issues/532)) ([3be6da3](https://github.com/testcontainers/testcontainers-python/commit/3be6da335ba2026b4800dfd6a19cda4ca8e52be8)) +* **vault:** add support for HashiCorp Vault container ([#366](https://github.com/testcontainers/testcontainers-python/issues/366)) ([1326278](https://github.com/testcontainers/testcontainers-python/commit/13262785dedf32a97e392afc1a758616995dc9d9)) + ## [4.3.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.0...testcontainers-v4.3.1) (2024-04-02) diff --git a/pyproject.toml b/pyproject.toml index 08c9c68bc..59e94e162 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.3.1" # auto-incremented by release-please +version = "4.3.2" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From ef86d15f5c63159dcbeb3dbefe9b8fa1964177d9 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Tue, 9 Apr 2024 09:51:54 -0400 Subject: [PATCH 351/425] fix: missing typing-extensions dependency (#534) fix #533 --- poetry.lock | 8 ++++---- pyproject.toml | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index a2f81d3b9..e4d5e1ee2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3913,13 +3913,13 @@ urllib3 = ">=1.26.0" [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -4211,4 +4211,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "233dfd72d07a555973aafc3fe3b6676574403b9fe4bb2c0230d455cff8aa2933" +content-hash = "54136d629f04e2b87cf7dc0905b0ce877b5db2eaf6e1d6616d7648d4b730a759" diff --git a/pyproject.toml b/pyproject.toml index 59e94e162..cfe28c2d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ python = ">=3.9,<4.0" docker = "*" # ">=4.0" urllib3 = "*" # "<2.0" wrapt = "*" # "^1.16.0" +typing-extensions = "*" # community modules python-arango = { version = "^7.8", optional = true } From 5ccec1755c2c976c2ad01bd558549989708b86a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:53:37 -0400 Subject: [PATCH 352/425] chore(main): release testcontainers 4.3.3 (#535) :robot: I have created a release *beep* *boop* --- ## [4.3.3](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.2...testcontainers-v4.3.3) (2024-04-09) ### Bug Fixes * missing typing-extensions dependency ([#534](https://github.com/testcontainers/testcontainers-python/issues/534)) ([ef86d15](https://github.com/testcontainers/testcontainers-python/commit/ef86d15f5c63159dcbeb3dbefe9b8fa1964177d9)), closes [#533](https://github.com/testcontainers/testcontainers-python/issues/533) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index b2b1cb883..816ade1f5 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.3.2" + ".": "4.3.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a007b4a..f47bf08d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [4.3.3](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.2...testcontainers-v4.3.3) (2024-04-09) + + +### Bug Fixes + +* missing typing-extensions dependency ([#534](https://github.com/testcontainers/testcontainers-python/issues/534)) ([ef86d15](https://github.com/testcontainers/testcontainers-python/commit/ef86d15f5c63159dcbeb3dbefe9b8fa1964177d9)), closes [#533](https://github.com/testcontainers/testcontainers-python/issues/533) + ## [4.3.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.1...testcontainers-v4.3.2) (2024-04-08) diff --git a/pyproject.toml b/pyproject.toml index cfe28c2d1..a765fd96f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.3.2" # auto-incremented by release-please +version = "4.3.3" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 29b51790ba31acf732eb5f017108bcb6622468f9 Mon Sep 17 00:00:00 2001 From: Max Pfeiffer Date: Sun, 14 Apr 2024 07:19:43 +0200 Subject: [PATCH 353/425] fix: tests for Kafka container running on ARM64 CPU (#536) v5.4.3 was not supporting ARM CPUs. Removed an obsolet test. Fixes https://github.com/testcontainers/testcontainers-python/issues/450 ![Screenshot 2024-04-09 at 19 59 56](https://github.com/testcontainers/testcontainers-python/assets/13573675/ae11c272-83da-4364-ad7a-a86a128bfd24) --- modules/kafka/testcontainers/kafka/__init__.py | 2 +- modules/kafka/tests/test_kafka.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index 648140d4d..7dd71b633 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -30,7 +30,7 @@ class KafkaContainer(DockerContainer): TC_START_SCRIPT = "/tc-start.sh" - def __init__(self, image: str = "confluentinc/cp-kafka:5.4.3", port: int = 9093, **kwargs) -> None: + def __init__(self, image: str = "confluentinc/cp-kafka:7.6.0", port: int = 9093, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image, **kwargs) self.port = port diff --git a/modules/kafka/tests/test_kafka.py b/modules/kafka/tests/test_kafka.py index c47aa111d..1f3826adf 100644 --- a/modules/kafka/tests/test_kafka.py +++ b/modules/kafka/tests/test_kafka.py @@ -14,11 +14,6 @@ def test_kafka_producer_consumer_custom_port(): produce_and_consume_kafka_message(container) -def test_kafka_confluent_7_1_3(): - with KafkaContainer(image="confluentinc/cp-kafka:7.1.3") as container: - produce_and_consume_kafka_message(container) - - def produce_and_consume_kafka_message(container): topic = "test-topic" bootstrap_server = container.get_bootstrap_server() From 11964deb9e84c0559a391280202811b83a065ab8 Mon Sep 17 00:00:00 2001 From: Mathias Loesch Date: Tue, 16 Apr 2024 12:14:38 +0200 Subject: [PATCH 354/425] feat(network): Add network context manager (#367) This PR adds a `Network` helper class that allows to create networks and connect containers programmatically. The networks are context-managed resources like containers created via `DockerContainer`. Please also see tests for a usage example :) --------- Co-authored-by: Kevin Wittek Co-authored-by: Max Pfeiffer Co-authored-by: Jakob Beckmann <32326425+f4z3r@users.noreply.github.com> Co-authored-by: David Ankin Co-authored-by: Carli* Freudenberg Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Vemund Santi --- core/testcontainers/core/container.py | 13 ++++++++ core/testcontainers/core/network.py | 41 +++++++++++++++++++++++++ core/tests/test_network.py | 43 +++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 core/testcontainers/core/network.py create mode 100644 core/tests/test_network.py diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index efa06734d..d2605490b 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -10,6 +10,7 @@ from testcontainers.core.docker_client import DockerClient from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID +from testcontainers.core.network import Network from testcontainers.core.utils import inside_container, is_arm, setup_logger from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -46,6 +47,8 @@ def __init__( self._container = None self._command = None self._name = None + self._network: Optional[Network] = None + self._network_aliases: Optional[list[str]] = None self._kwargs = kwargs def with_env(self, key: str, value: str) -> Self: @@ -61,6 +64,14 @@ def with_exposed_ports(self, *ports: int) -> Self: self.ports[port] = None return self + def with_network(self, network: Network) -> Self: + self._network = network + return self + + def with_network_aliases(self, *aliases) -> Self: + self._network_aliases = aliases + return self + def with_kwargs(self, **kwargs) -> Self: self._kwargs = kwargs return self @@ -87,6 +98,8 @@ def start(self) -> Self: **self._kwargs, ) logger.info("Container started: %s", self._container.short_id) + if self._network: + self._network.connect(self._container.id, self._network_aliases) return self def stop(self, force=True, delete_volume=True) -> None: diff --git a/core/testcontainers/core/network.py b/core/testcontainers/core/network.py new file mode 100644 index 000000000..9903d0710 --- /dev/null +++ b/core/testcontainers/core/network.py @@ -0,0 +1,41 @@ +# +# 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 uuid +from typing import Optional + +from testcontainers.core.docker_client import DockerClient + + +class Network: + """ + Network context manager for programmatically connecting containers. + """ + + def __init__(self, docker_client_kw: Optional[dict] = None, docker_network_kw: Optional[dict] = None) -> None: + self.name = str(uuid.uuid4()) + self._docker = DockerClient(**(docker_client_kw or {})) + self._docker_network_kw = docker_network_kw or {} + + def connect(self, container_id: str, network_aliases: Optional[list] = None): + self._network.connect(container_id, aliases=network_aliases) + + def remove(self) -> None: + self._network.remove() + + def __enter__(self) -> "Network": + self._network = self._docker.client.networks.create(self.name, **self._docker_network_kw) + self.id = self._network.id + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.remove() diff --git a/core/tests/test_network.py b/core/tests/test_network.py new file mode 100644 index 000000000..4b0764d4d --- /dev/null +++ b/core/tests/test_network.py @@ -0,0 +1,43 @@ +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.network import Network + +NGINX_ALPINE_SLIM_IMAGE = "nginx:1.25.4-alpine-slim" + + +def test_network_gets_created_and_cleaned_up(): + with Network() as network: + docker = DockerClient() + networks_list = docker.client.networks.list(network.name) + assert networks_list[0].name == network.name + assert networks_list[0].id == network.id + assert not docker.client.networks.list(network.name) + + +def test_containers_can_communicate_over_network(): + with Network() as network: + with ( + DockerContainer(NGINX_ALPINE_SLIM_IMAGE) + .with_name("alpine1") + .with_network_aliases("alpine1-alias-1", "alpine1-alias-2") + .with_network(network) as alpine1 + ): + with ( + DockerContainer(NGINX_ALPINE_SLIM_IMAGE) + .with_name("alpine2") + .with_network_aliases("alpine2-alias-1", "alpine2-alias-2") + .with_network(network) as alpine2 + ): + assert_can_ping(alpine1, "alpine2") + assert_can_ping(alpine1, "alpine2-alias-1") + assert_can_ping(alpine1, "alpine2-alias-2") + + assert_can_ping(alpine2, "alpine1") + assert_can_ping(alpine2, "alpine1-alias-1") + assert_can_ping(alpine2, "alpine1-alias-2") + + +def assert_can_ping(container: DockerContainer, remote_name: str): + status, output = container.exec("ping -c 1 %s" % remote_name) + assert status == 0 + assert "64 bytes" in str(output) From fefb9d0845bf6e0cbddad6868da5336b5b82bcb0 Mon Sep 17 00:00:00 2001 From: Vemund Santi Date: Tue, 16 Apr 2024 14:25:17 +0200 Subject: [PATCH 355/425] fix(dependencies): remove usage of `sqlalchemy` in DB extras. Add default wait timeout for `wait_for_logs` (#525) Removes usage of `sqlalchemy`, as part of the work described in https://github.com/testcontainers/testcontainers-python/issues/526. - Adds default timeout to the `wait_for_logs` waiting strategy, the same timeout used by default in the `wait_container_is_ready` strategy. - Changes wait strategy for `mysql` container to wait for logs indicating that the DB engine is ready to accept connections (MySQL performs a restart as part of its startup procedure, so the logs will always appear twice. - Add More tests for different `mysql` and `mariadb` versions to ensure consistency in wait strategy. - Remove x86 emulation for ARM devices for MariaDB, as it MariaDB images support ARM architectures already. - Change wait strategy for `oracle-free`, as the images produce a consistent `DATABASE IS READY TO USE!` log message on startup. Next steps will be to remove `sqlalchemy` as a bundled dependency entirely, but I have not included it in this PR as I consider it a bigger change than just changing wait strategies as an internal implementation detail. I plan to do this as part of a bigger rework where i remove the `DbContainer` class and standardize configuration hooks and wait strategies across containers (not just DB containers, all containers in need of a configuration and readiness step). See https://github.com/testcontainers/testcontainers-python/pull/527 for WIP. --------- Co-authored-by: David Ankin --- core/testcontainers/core/waiting_utils.py | 6 +++--- .../mssql/testcontainers/mssql/__init__.py | 8 +++++++- modules/mssql/tests/test_mssql.py | 14 ++++++++----- .../mysql/testcontainers/mysql/__init__.py | 8 ++++++++ modules/mysql/tests/test_mysql.py | 20 +++++++++---------- .../testcontainers/oracle/__init__.py | 4 ++++ 6 files changed, 41 insertions(+), 19 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index 4eb7ad890..cc3351d11 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -15,7 +15,7 @@ import re import time import traceback -from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from typing import TYPE_CHECKING, Any, Callable, Union import wrapt @@ -78,7 +78,7 @@ def wait_for(condition: Callable[..., bool]) -> bool: def wait_for_logs( - container: "DockerContainer", predicate: Union[Callable, str], timeout: Optional[float] = None, interval: float = 1 + container: "DockerContainer", predicate: Union[Callable, str], timeout: float = config.timeout, interval: float = 1 ) -> float: """ Wait for the container to emit logs satisfying the predicate. @@ -103,6 +103,6 @@ def wait_for_logs( stderr = container.get_logs()[1].decode() if predicate(stdout) or predicate(stderr): return duration - if timeout and duration > timeout: + if duration > timeout: raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " "seconds") time.sleep(interval) diff --git a/modules/mssql/testcontainers/mssql/__init__.py b/modules/mssql/testcontainers/mssql/__init__.py index 3bfe861b4..6cee36813 100644 --- a/modules/mssql/testcontainers/mssql/__init__.py +++ b/modules/mssql/testcontainers/mssql/__init__.py @@ -3,6 +3,7 @@ from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.waiting_utils import wait_container_is_ready class SqlServerContainer(DbContainer): @@ -16,7 +17,7 @@ class SqlServerContainer(DbContainer): >>> import sqlalchemy >>> from testcontainers.mssql import SqlServerContainer - >>> with SqlServerContainer() as mssql: + >>> with SqlServerContainer("mcr.microsoft.com/mssql/server:2022-CU12-ubuntu-22.04") as mssql: ... engine = sqlalchemy.create_engine(mssql.get_connection_url()) ... with engine.begin() as connection: ... result = connection.execute(sqlalchemy.text("select @@VERSION")) @@ -49,6 +50,11 @@ def _configure(self) -> None: self.with_env("SQLSERVER_DBNAME", self.dbname) self.with_env("ACCEPT_EULA", "Y") + @wait_container_is_ready(AssertionError) + def _connect(self) -> None: + status, _ = self.exec(f"/opt/mssql-tools/bin/sqlcmd -U {self.username} -P {self.password} -Q 'SELECT 1'") + assert status == 0, "Cannot run 'SELECT 1': container is not ready" + def get_connection_url(self) -> str: return super()._create_connection_url( dialect=self.dialect, username=self.username, password=self.password, dbname=self.dbname, port=self.port diff --git a/modules/mssql/tests/test_mssql.py b/modules/mssql/tests/test_mssql.py index 6f48f0a13..e7273042f 100644 --- a/modules/mssql/tests/test_mssql.py +++ b/modules/mssql/tests/test_mssql.py @@ -1,19 +1,23 @@ +import pytest import sqlalchemy +from testcontainers.core.utils import is_arm from testcontainers.mssql import SqlServerContainer -def test_docker_run_mssql(): - image = "mcr.microsoft.com/azure-sql-edge" - dialect = "mssql+pymssql" - with SqlServerContainer(image, dialect=dialect) as mssql: +@pytest.mark.skipif(is_arm(), reason="mssql container not available for ARM") +@pytest.mark.parametrize("version", ["2022-CU12-ubuntu-22.04", "2019-CU25-ubuntu-20.04"]) +def test_docker_run_mssql(version: str): + with SqlServerContainer(f"mcr.microsoft.com/mssql/server:{version}", password="1Secure*Password2") as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select @@servicename")) for row in result: assert row[0] == "MSSQLSERVER" - with SqlServerContainer(image, password="1Secure*Password2", dialect=dialect) as mssql: + +def test_docker_run_azure_sql_edge(): + with SqlServerContainer("mcr.microsoft.com/azure-sql-edge:1.0.7") as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select @@servicename")) diff --git a/modules/mysql/testcontainers/mysql/__init__.py b/modules/mysql/testcontainers/mysql/__init__.py index a5b839273..1b0751bc8 100644 --- a/modules/mysql/testcontainers/mysql/__init__.py +++ b/modules/mysql/testcontainers/mysql/__init__.py @@ -10,11 +10,13 @@ # 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 from os import environ from typing import Optional from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.waiting_utils import wait_for_logs class MySqlContainer(DbContainer): @@ -74,6 +76,12 @@ def _configure(self) -> None: self.with_env("MYSQL_USER", self.username) self.with_env("MYSQL_PASSWORD", self.password) + def _connect(self) -> None: + wait_for_logs( + self, + re.compile(".*: ready for connections.*: ready for connections.*", flags=re.DOTALL | re.MULTILINE).search, + ) + def get_connection_url(self) -> str: return super()._create_connection_url( dialect="mysql+pymysql", username=self.username, password=self.password, dbname=self.dbname, port=self.port diff --git a/modules/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py index 3506960bf..40eb536b0 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/modules/mysql/tests/test_mysql.py @@ -8,41 +8,41 @@ from testcontainers.mysql import MySqlContainer -@pytest.mark.skipif(is_arm(), reason="mysql container not available for ARM") def test_docker_run_mysql(): - config = MySqlContainer("mysql:5.7.17") + config = MySqlContainer("mysql:8.3.0") with config as mysql: engine = sqlalchemy.create_engine(mysql.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select version()")) for row in result: - assert row[0].startswith("5.7.17") + assert row[0].startswith("8.3.0") @pytest.mark.skipif(is_arm(), reason="mysql container not available for ARM") -def test_docker_run_mysql_8(): - config = MySqlContainer("mysql:8") +def test_docker_run_legacy_mysql(): + config = MySqlContainer("mysql:5.7.44") with config as mysql: engine = sqlalchemy.create_engine(mysql.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select version()")) for row in result: - assert row[0].startswith("8") + assert row[0].startswith("5.7.44") -def test_docker_run_mariadb(): - with MySqlContainer("mariadb:10.6.5").maybe_emulate_amd64() as mariadb: +@pytest.mark.parametrize("version", ["11.3.2", "10.11.7"]) +def test_docker_run_mariadb(version: str): + with MySqlContainer(f"mariadb:{version}") as mariadb: engine = sqlalchemy.create_engine(mariadb.get_connection_url()) with engine.begin() as connection: result = connection.execute(sqlalchemy.text("select version()")) for row in result: - assert row[0].startswith("10.6.5") + assert row[0].startswith(version) def test_docker_env_variables(): with ( mock.patch.dict("os.environ", MYSQL_USER="demo", MYSQL_DATABASE="custom_db"), - MySqlContainer("mariadb:10.6.5").with_bind_ports(3306, 32785).maybe_emulate_amd64() as container, + MySqlContainer("mariadb:10.6.5").with_bind_ports(3306, 32785) as container, ): url = container.get_connection_url() pattern = r"mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db" diff --git a/modules/oracle-free/testcontainers/oracle/__init__.py b/modules/oracle-free/testcontainers/oracle/__init__.py index 03f525a71..781be4280 100644 --- a/modules/oracle-free/testcontainers/oracle/__init__.py +++ b/modules/oracle-free/testcontainers/oracle/__init__.py @@ -3,6 +3,7 @@ from typing import Optional from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_for_logs class OracleDbContainer(DbContainer): @@ -57,6 +58,9 @@ def get_connection_url(self) -> str: ) + "/?service_name={}".format(self.dbname or "FREEPDB1") # Default DB is "FREEPDB1" + def _connect(self) -> None: + wait_for_logs(self, "DATABASE IS READY TO USE!") + def _configure(self) -> None: # if self.oracle_password is not None: # self.with_env("ORACLE_PASSWORD", self.oracle_password) From e04b7ac78ccf6b79fce75ebd3a4626e00d764aa9 Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Wed, 17 Apr 2024 06:10:23 -0400 Subject: [PATCH 356/425] feat(labels):Add common testcontainers labels (#519) - Closes: https://github.com/testcontainers/testcontainers-python/issues/510 Aligns with other test container projects Example: I also contribute to go project, those labels look like ``` "maintainer": "docker@couchbase.com", "org.opencontainers.image.ref.name": "ubuntu", "org.opencontainers.image.version": "20.04", "org.testcontainers": "true", "org.testcontainers.lang": "go", "org.testcontainers.sessionId": "e01aa90cfb75a53fbd53776b8c2eb84a99e3f1c8a7103512468cf75735421176", "org.testcontainers.version": "0.30.0" ``` Java appears to do similar - https://github.com/testcontainers/testcontainers-java/blob/main/core/src/main/java/org/testcontainers/DockerClientFactory.java#L51 I didn't add in the image info as there wasn't an obvious way to get a handle on that nor obvious value. Another thing is that the python prefers `session-id` to `sessionId`. I'm not sure if there are any cross-language reasons to have those be identical, I left it alone. Also this adds in tests for the label code. --------- Co-authored-by: bstrausser Co-authored-by: David Ankin --- core/testcontainers/core/labels.py | 12 +++++++ core/tests/test_labels.py | 58 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 core/tests/test_labels.py diff --git a/core/testcontainers/core/labels.py b/core/testcontainers/core/labels.py index 144e4365e..df9c617b1 100644 --- a/core/testcontainers/core/labels.py +++ b/core/testcontainers/core/labels.py @@ -1,17 +1,29 @@ +import importlib from typing import Optional from uuid import uuid4 from testcontainers.core.config import testcontainers_config as c SESSION_ID: str = str(uuid4()) +TESTCONTAINERS_NAMESPACE = "org.testcontainers" + +LABEL_TESTCONTAINERS = TESTCONTAINERS_NAMESPACE LABEL_SESSION_ID = "org.testcontainers.session-id" +LABEL_VERSION = "org.testcontainers.version" LABEL_LANG = "org.testcontainers.lang" def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str]: if labels is None: labels = {} + else: + for k in labels: + if k.startswith(TESTCONTAINERS_NAMESPACE): + raise ValueError("The org.testcontainers namespace is reserved for interal use") + labels[LABEL_LANG] = "python" + labels[LABEL_TESTCONTAINERS] = "true" + labels[LABEL_VERSION] = importlib.metadata.version("testcontainers") if image == c.ryuk_image: return labels diff --git a/core/tests/test_labels.py b/core/tests/test_labels.py new file mode 100644 index 000000000..425aee7dd --- /dev/null +++ b/core/tests/test_labels.py @@ -0,0 +1,58 @@ +from testcontainers.core.labels import ( + LABEL_LANG, + LABEL_SESSION_ID, + LABEL_TESTCONTAINERS, + LABEL_VERSION, + create_labels, + TESTCONTAINERS_NAMESPACE, +) +import pytest +from testcontainers.core.config import RYUK_IMAGE + + +def assert_in_with_value(labels: dict, label: str, value: str, known_before_test_time: bool) -> None: + assert label in labels + if known_before_test_time: + assert labels[label] == value + + +testdata = [ + (LABEL_LANG, "python", True), + (LABEL_TESTCONTAINERS, "true", True), + (LABEL_SESSION_ID, "some", False), + (LABEL_VERSION, "some", False), +] + + +@pytest.mark.parametrize("label,value,known_before_test_time", testdata) +def test_containers_creates_expected_labels(label, value, known_before_test_time): + actual_labels = create_labels("not-ryuk", None) + assert_in_with_value(actual_labels, label, value, known_before_test_time) + + +def test_containers_throws_on_namespace_collision(): + with pytest.raises(ValueError): + create_labels("not-ryuk", {TESTCONTAINERS_NAMESPACE: "fake"}) + + +def test_containers_respect_custom_labels_if_no_collision(): + custom_namespace = "org.foo.bar" + value = "fake" + actual_labels = create_labels("not-ryuk", {custom_namespace: value}) + assert_in_with_value(actual_labels, custom_namespace, value, True) + + +def test_if_ryuk_no_session(): + actual_labels = create_labels(RYUK_IMAGE, None) + assert LABEL_SESSION_ID not in actual_labels + + +def test_session_are_module_import_scoped(): + """ + Asserts that sessions are a module-level variable and don't differ between invocation + """ + first_labels = create_labels("not-ryuk", None) + second_labels = create_labels("not-ryuk", None) + assert LABEL_SESSION_ID in first_labels + assert LABEL_SESSION_ID in second_labels + assert first_labels[LABEL_SESSION_ID] == second_labels[LABEL_SESSION_ID] From 807387425913906b214f09c141a0bd0c337d788a Mon Sep 17 00:00:00 2001 From: Jan Meiswinkel Date: Wed, 17 Apr 2024 12:19:27 +0200 Subject: [PATCH 357/425] fix(core): add TESTCONTAINERS_HOST_OVERRIDE as alternative to TC_HOST (#384) Resolves #383 This PR aims to unify the configuration for the Testcontainer Hostvariable as it is in [Java](https://java.testcontainers.org/features/configuration/#customizing-docker-host-detection) and [Dotnet](https://dotnet.testcontainers.org/custom_configuration/) (and potentially others). --- core/testcontainers/core/docker_client.py | 2 ++ index.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 9ff6170e6..e43dddb41 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -166,6 +166,8 @@ def host(self) -> str: # https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 # if os env TC_HOST is set, use it host = os.environ.get("TC_HOST") + if not host: + host = os.environ.get("TESTCONTAINERS_HOST_OVERRIDE") if host: return host try: diff --git a/index.rst b/index.rst index 4f3dad802..0f9cd5a3c 100644 --- a/index.rst +++ b/index.rst @@ -80,6 +80,8 @@ This snippet does the same, however using a specific version and the driver is s Note, that the :code:`sqlalchemy` and :code:`psycopg` packages are no longer a dependency of :code:`testcontainers[postgres]` and not needed to launch the Postgres container. Your project therefore needs to declare a dependency on the used driver and db access methods you use in your code. +By default, Testcontainers will search for the container via the gateway IP. You can manually specify your own IP with the environment variable `TESTCONTAINERS_HOST_OVERRIDE`. + Installation ------------ From 90bb780c30f42d3cfa2f724fb9ca3b6048d1dd9f Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 17 Apr 2024 06:32:34 -0400 Subject: [PATCH 358/425] fix(core): #486 for colima delay for port avail for connect (#543) fix #486 --- core/testcontainers/core/container.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index d2605490b..73e3287d2 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -221,8 +221,26 @@ def _create_instance(cls) -> "Reaper": container_host = Reaper._container.get_container_host_ip() container_port = int(Reaper._container.get_exposed_port(8080)) - Reaper._socket = socket() - Reaper._socket.connect((container_host, container_port)) + last_connection_exception: Optional[Exception] = None + for _ in range(50): + try: + Reaper._socket = socket() + Reaper._socket.connect((container_host, container_port)) + last_connection_exception = None + break + except (ConnectionRefusedError, OSError) as e: + if Reaper._socket is not None: + with contextlib.suppress(Exception): + Reaper._socket.close() + Reaper._socket = None + last_connection_exception = e + + from time import sleep + + sleep(0.5) + if last_connection_exception: + raise last_connection_exception + Reaper._socket.send(f"label={LABEL_SESSION_ID}={SESSION_ID}\r\n".encode()) Reaper._instance = Reaper() From 056e48d6a418516a91d37aa4d57fc12442ea0fbc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 06:36:15 -0400 Subject: [PATCH 359/425] chore(main): release testcontainers 4.4.0 (#539) :robot: I have created a release *beep* *boop* --- ## [4.4.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.3...testcontainers-v4.4.0) (2024-04-17) ### Features * **labels:** Add common testcontainers labels ([#519](https://github.com/testcontainers/testcontainers-python/issues/519)) ([e04b7ac](https://github.com/testcontainers/testcontainers-python/commit/e04b7ac78ccf6b79fce75ebd3a4626e00d764aa9)) * **network:** Add network context manager ([#367](https://github.com/testcontainers/testcontainers-python/issues/367)) ([11964de](https://github.com/testcontainers/testcontainers-python/commit/11964deb9e84c0559a391280202811b83a065ab8)) ### Bug Fixes * **core:** [#486](https://github.com/testcontainers/testcontainers-python/issues/486) for colima delay for port avail for connect ([#543](https://github.com/testcontainers/testcontainers-python/issues/543)) ([90bb780](https://github.com/testcontainers/testcontainers-python/commit/90bb780c30f42d3cfa2f724fb9ca3b6048d1dd9f)) * **core:** add TESTCONTAINERS_HOST_OVERRIDE as alternative to TC_HOST ([#384](https://github.com/testcontainers/testcontainers-python/issues/384)) ([8073874](https://github.com/testcontainers/testcontainers-python/commit/807387425913906b214f09c141a0bd0c337d788a)) * **dependencies:** remove usage of `sqlalchemy` in DB extras. Add default wait timeout for `wait_for_logs` ([#525](https://github.com/testcontainers/testcontainers-python/issues/525)) ([fefb9d0](https://github.com/testcontainers/testcontainers-python/commit/fefb9d0845bf6e0cbddad6868da5336b5b82bcb0)) * tests for Kafka container running on ARM64 CPU ([#536](https://github.com/testcontainers/testcontainers-python/issues/536)) ([29b5179](https://github.com/testcontainers/testcontainers-python/commit/29b51790ba31acf732eb5f017108bcb6622468f9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 816ade1f5..b7c720789 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.3.3" + ".": "4.4.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f47bf08d8..56c4fe365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [4.4.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.3...testcontainers-v4.4.0) (2024-04-17) + + +### Features + +* **labels:** Add common testcontainers labels ([#519](https://github.com/testcontainers/testcontainers-python/issues/519)) ([e04b7ac](https://github.com/testcontainers/testcontainers-python/commit/e04b7ac78ccf6b79fce75ebd3a4626e00d764aa9)) +* **network:** Add network context manager ([#367](https://github.com/testcontainers/testcontainers-python/issues/367)) ([11964de](https://github.com/testcontainers/testcontainers-python/commit/11964deb9e84c0559a391280202811b83a065ab8)) + + +### Bug Fixes + +* **core:** [#486](https://github.com/testcontainers/testcontainers-python/issues/486) for colima delay for port avail for connect ([#543](https://github.com/testcontainers/testcontainers-python/issues/543)) ([90bb780](https://github.com/testcontainers/testcontainers-python/commit/90bb780c30f42d3cfa2f724fb9ca3b6048d1dd9f)) +* **core:** add TESTCONTAINERS_HOST_OVERRIDE as alternative to TC_HOST ([#384](https://github.com/testcontainers/testcontainers-python/issues/384)) ([8073874](https://github.com/testcontainers/testcontainers-python/commit/807387425913906b214f09c141a0bd0c337d788a)) +* **dependencies:** remove usage of `sqlalchemy` in DB extras. Add default wait timeout for `wait_for_logs` ([#525](https://github.com/testcontainers/testcontainers-python/issues/525)) ([fefb9d0](https://github.com/testcontainers/testcontainers-python/commit/fefb9d0845bf6e0cbddad6868da5336b5b82bcb0)) +* tests for Kafka container running on ARM64 CPU ([#536](https://github.com/testcontainers/testcontainers-python/issues/536)) ([29b5179](https://github.com/testcontainers/testcontainers-python/commit/29b51790ba31acf732eb5f017108bcb6622468f9)) + ## [4.3.3](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.2...testcontainers-v4.3.3) (2024-04-09) diff --git a/pyproject.toml b/pyproject.toml index a765fd96f..9570ce063 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.3.3" # auto-incremented by release-please +version = "4.4.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 6c5d227730d415111c54e7ea3cb5d86b549cc901 Mon Sep 17 00:00:00 2001 From: Lyndon Fan Date: Sat, 20 Apr 2024 20:40:44 +0100 Subject: [PATCH 360/425] fix: url quote passwords (#549) **Changes** Updated DbContainer to fix #547 by using `urllib.parse.quote`. I referenced sqlalchemy's implementation, but have not imported the library. I have chosen to make this behaviour occur at all times (can't opt in / out), as it is common, if not the standard for these urls. **Tests** Since DbContainer can't be tested on its own, I put the tests across various database containers. I have pasted the below as comment in the test files for the listed modules: ```python # This is a feature in the generic DbContainer class # but it can't be tested on its own # so is tested in various database modules: # - mysql / mariadb # - postgresql # - sqlserver # - mongodb ``` Note the discussion recommended me to test with oracle, but I was unable to spin the container up locally (even with colima), so opted to replace it with mongodb. Is there a template for PRs for the core library? I am unable to find one so have opted the above format. Please let me know if I have missed anything in this PR. Thanks! --- core/testcontainers/core/generic.py | 4 ++- modules/mongodb/tests/test_mongodb.py | 24 +++++++++++++++++ modules/mssql/tests/test_mssql.py | 32 ++++++++++++++++++++++ modules/mysql/tests/test_mysql.py | 28 ++++++++++++++++++++ modules/postgres/tests/test_postgres.py | 35 +++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 1 deletion(-) diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index a3bff96e2..515c2831b 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. from typing import Optional +from urllib.parse import quote from testcontainers.core.container import DockerContainer from testcontainers.core.exceptions import ContainerStartException @@ -60,7 +61,8 @@ def _create_connection_url( raise ContainerStartException("container has not been started") host = host or self.get_container_host_ip() port = self.get_exposed_port(port) - url = f"{dialect}://{username}:{password}@{host}:{port}" + quoted_password = quote(password, safe=" +") + url = f"{dialect}://{username}:{quoted_password}@{host}:{port}" if dbname: url = f"{url}/{dbname}" return url diff --git a/modules/mongodb/tests/test_mongodb.py b/modules/mongodb/tests/test_mongodb.py index 34642103e..da3465dbb 100644 --- a/modules/mongodb/tests/test_mongodb.py +++ b/modules/mongodb/tests/test_mongodb.py @@ -26,3 +26,27 @@ def test_docker_run_mongodb(version: str): cursor = db.restaurants.find({"borough": "Manhattan"}) assert cursor.next()["restaurant_id"] == doc["restaurant_id"] + + +# This is a feature in the generic DbContainer class +# but it can't be tested on its own +# so is tested in various database modules: +# - mysql / mariadb +# - postgresql +# - sqlserver +# - mongodb +def test_quoted_password(): + user = "root" + password = "p@$%25+0&%rd :/!=?" + quoted_password = "p%40%24%2525+0%26%25rd %3A%2F%21%3D%3F" + # driver = "pymongo" + kwargs = { + "username": user, + "password": password, + } + with MongoDbContainer("mongo:7.0.7", **kwargs) as container: + host = container.get_container_host_ip() + port = container.get_exposed_port(27017) + expected_url = f"mongodb://{user}:{quoted_password}@{host}:{port}" + url = container.get_connection_url() + assert url == expected_url diff --git a/modules/mssql/tests/test_mssql.py b/modules/mssql/tests/test_mssql.py index e7273042f..f7aabd3af 100644 --- a/modules/mssql/tests/test_mssql.py +++ b/modules/mssql/tests/test_mssql.py @@ -23,3 +23,35 @@ def test_docker_run_azure_sql_edge(): result = connection.execute(sqlalchemy.text("select @@servicename")) for row in result: assert row[0] == "MSSQLSERVER" + + +# This is a feature in the generic DbContainer class +# but it can't be tested on its own +# so is tested in various database modules: +# - mysql / mariadb +# - postgresql +# - sqlserver +# - mongodb +def test_quoted_password(): + user = "SA" + # spaces seem to cause issues? + password = "p@$%25+0&%rd:/!=?" + quoted_password = "p%40%24%2525+0%26%25rd%3A%2F%21%3D%3F" + driver = "pymssql" + port = 1433 + expected_url = f"mssql+{driver}://{user}:{quoted_password}@localhost:{port}/tempdb" + kwargs = { + "username": user, + "password": password, + } + with ( + SqlServerContainer("mcr.microsoft.com/azure-sql-edge:1.0.7", **kwargs) + .with_env("ACCEPT_EULA", "Y") + .with_env( + "MSSQL_SA_PASSWORD", "{" + password + "}" + ) # special characters have to be quoted in braces in env vars + ) as container: + exposed_port = container.get_exposed_port(container.port) + expected_url = expected_url.replace(f":{port}", f":{exposed_port}") + url = container.get_connection_url() + assert url == expected_url diff --git a/modules/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py index 40eb536b0..ee1e2b45e 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/modules/mysql/tests/test_mysql.py @@ -47,3 +47,31 @@ def test_docker_env_variables(): url = container.get_connection_url() pattern = r"mysql\+pymysql:\/\/demo:test@[\w,.]+:(3306|32785)\/custom_db" assert re.match(pattern, url) + + +# This is a feature in the generic DbContainer class +# but it can't be tested on its own +# so is tested in various database modules: +# - mysql / mariadb +# - postgresql +# - sqlserver +# - mongodb +def test_quoted_password(): + user = "root" + password = "p@$%25+0&%rd :/!=?" + quoted_password = "p%40%24%2525+0%26%25rd %3A%2F%21%3D%3F" + driver = "pymysql" + with MySqlContainer("mariadb:10.6.5", username=user, password=password) as container: + host = container.get_container_host_ip() + port = container.get_exposed_port(3306) + expected_url = f"mysql+{driver}://{user}:{quoted_password}@{host}:{port}/test" + url = container.get_connection_url() + assert url == expected_url + + with sqlalchemy.create_engine(expected_url).begin() as connection: + connection.execute(sqlalchemy.text("select version()")) + + raw_pass_url = f"mysql+{driver}://{user}:{password}@{host}:{port}/test" + with pytest.raises(Exception): + with sqlalchemy.create_engine(raw_pass_url).begin() as connection: + connection.execute(sqlalchemy.text("select version()")) diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index d0f61e64a..fbba6932d 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -42,3 +42,38 @@ def test_docker_run_postgres_with_driver_pg8000(): engine = sqlalchemy.create_engine(postgres.get_connection_url()) with engine.begin() as connection: connection.execute(sqlalchemy.text("select 1=1")) + + +# This is a feature in the generic DbContainer class +# but it can't be tested on its own +# so is tested in various database modules: +# - mysql / mariadb +# - postgresql +# - sqlserver +# - mongodb +def test_quoted_password(): + user = "root" + password = "p@$%25+0&%rd :/!=?" + quoted_password = "p%40%24%2525+0%26%25rd %3A%2F%21%3D%3F" + driver = "psycopg2" + kwargs = { + "driver": driver, + "username": user, + "password": password, + } + with PostgresContainer("postgres:16-alpine", **kwargs) as container: + port = container.get_exposed_port(5432) + host = container.get_container_host_ip() + expected_url = f"postgresql+{driver}://{user}:{quoted_password}@{host}:{port}/test" + + url = container.get_connection_url() + assert url == expected_url + + with sqlalchemy.create_engine(expected_url).begin() as connection: + connection.execute(sqlalchemy.text("select 1=1")) + + raw_pass_url = f"postgresql+{driver}://{user}:{password}@{host}:{port}/test" + with pytest.raises(Exception): + # it raises ValueError, but auth (OperationalError) = more interesting + with sqlalchemy.create_engine(raw_pass_url).begin() as connection: + connection.execute(sqlalchemy.text("select 1=1")) From f761b983613e16dc56e560a947247c01052c19f6 Mon Sep 17 00:00:00 2001 From: Stefan N Date: Sat, 11 May 2024 05:04:49 +0200 Subject: [PATCH 361/425] fix(keycloak): add realm imports (#565) This PR adds an option to start a keycloak container with importing one or more realms. This mirrors a feature present in the java keycloak testcontainer. --- .../testcontainers/keycloak/__init__.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index ca5702298..27b6b20d1 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -23,6 +23,8 @@ class KeycloakContainer(DockerContainer): + has_realm_imports = False + """ Keycloak container. @@ -43,12 +45,14 @@ def __init__( username: Optional[str] = None, password: Optional[str] = None, port: int = 8080, + cmd: Optional[str] = _DEFAULT_DEV_COMMAND, ) -> None: super().__init__(image=image) self.username = username or os.environ.get("KEYCLOAK_ADMIN", "test") self.password = password or os.environ.get("KEYCLOAK_ADMIN_PASSWORD", "test") self.port = port self.with_exposed_ports(self.port) + self.cmd = cmd def _configure(self) -> None: self.with_env("KEYCLOAK_ADMIN", self.username) @@ -56,9 +60,11 @@ def _configure(self) -> None: # Enable health checks # see: https://www.keycloak.org/server/health#_relevant_options self.with_env("KC_HEALTH_ENABLED", "true") - # Starting Keycloak in development mode + # Start Keycloak in development mode # see: https://www.keycloak.org/server/configuration#_starting_keycloak_in_development_mode - self.with_command(_DEFAULT_DEV_COMMAND) + if self.has_realm_imports: + self.cmd += " --import-realm" + self.with_command(self.cmd) def get_url(self) -> str: host = self.get_container_host_ip() @@ -67,10 +73,10 @@ def get_url(self) -> str: @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) def _readiness_probe(self) -> None: - # Keycloak provides an REST API endpoints for health checks: https://www.keycloak.org/server/health + # Keycloak provides REST API endpoints for health checks: https://www.keycloak.org/server/health response = requests.get(f"{self.get_url()}/health/ready", timeout=1) response.raise_for_status() - if self._command == _DEFAULT_DEV_COMMAND: + if _DEFAULT_DEV_COMMAND in self._command: wait_for_logs(self, "Added user .* to realm .*") def start(self) -> "KeycloakContainer": @@ -79,6 +85,22 @@ def start(self) -> "KeycloakContainer": self._readiness_probe() return self + def with_realm_import_file(self, realm_import_file: str) -> "KeycloakContainer": + file = os.path.abspath(realm_import_file) + if not os.path.exists(file): + raise FileNotFoundError(f"Realm file {file} does not exist") + self.with_volume_mapping(file, "/opt/keycloak/data/import/realm.json") + self.has_realm_imports = True + return self + + def with_realm_import_folder(self, realm_import_folder: str) -> "KeycloakContainer": + folder = os.path.abspath(realm_import_folder) + if not os.path.exists(folder): + raise FileNotFoundError(f"Realm folder {folder} does not exist") + self.with_volume_mapping(folder, "/opt/keycloak/data/import/") + self.has_realm_imports = True + return self + def get_client(self, **kwargs) -> KeycloakAdmin: default_kwargs = { "server_url": self.get_url(), From 396079a5af4c550084df2be5037a0ff52cd9fb5a Mon Sep 17 00:00:00 2001 From: Jb DOYON Date: Sat, 11 May 2024 04:59:01 +0100 Subject: [PATCH 362/425] fix(mysql): Add seed support in MySQL (#552) Ref #541. New capability of "seeding" a db container using image's support for /docker-entrypoint-initdb.d/ folder. Using the "transferable" system, borrowed from Kafka. Updates DbContainer to have a new (NOOP-default) `_transfer_seed()` method, run after `_start()` and before `_connect()`, to allow the folder transfer. Currently implemented only in MySQL, but extensible to others that use the `/docker-entrypoint-initdb.d/` system. --------- Co-authored-by: Jb DOYON --- core/testcontainers/core/generic.py | 4 +++ .../mysql/testcontainers/mysql/__init__.py | 32 +++++++++++++++++++ modules/mysql/tests/seeds/01-schema.sql | 6 ++++ modules/mysql/tests/seeds/02-seeds.sql | 3 ++ modules/mysql/tests/test_mysql.py | 13 ++++++++ 5 files changed, 58 insertions(+) create mode 100644 modules/mysql/tests/seeds/01-schema.sql create mode 100644 modules/mysql/tests/seeds/02-seeds.sql diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 515c2831b..6dd635e69 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -70,8 +70,12 @@ def _create_connection_url( def start(self) -> "DbContainer": self._configure() super().start() + self._transfer_seed() self._connect() return self def _configure(self) -> None: raise NotImplementedError + + def _transfer_seed(self) -> None: + pass diff --git a/modules/mysql/testcontainers/mysql/__init__.py b/modules/mysql/testcontainers/mysql/__init__.py index 1b0751bc8..46efbcfbc 100644 --- a/modules/mysql/testcontainers/mysql/__init__.py +++ b/modules/mysql/testcontainers/mysql/__init__.py @@ -11,7 +11,10 @@ # License for the specific language governing permissions and limitations # under the License. import re +import tarfile +from io import BytesIO from os import environ +from pathlib import Path from typing import Optional from testcontainers.core.generic import DbContainer @@ -40,6 +43,22 @@ class MySqlContainer(DbContainer): ... with engine.begin() as connection: ... result = connection.execute(sqlalchemy.text("select version()")) ... version, = result.fetchone() + + The optional :code:`seed` parameter enables arbitrary SQL files to be loaded. + This is perfect for schema and sample data. This works by mounting the seed to + `/docker-entrypoint-initdb./d`, which containerized MySQL are set up to load + automatically. + + .. doctest:: + >>> import sqlalchemy + >>> from testcontainers.mysql import MySqlContainer + >>> with MySqlContainer(seed="../../tests/seeds/") as mysql: + ... engine = sqlalchemy.create_engine(mysql.get_connection_url()) + ... with engine.begin() as connection: + ... query = "select * from stuff" # Can now rely on schema/data + ... result = connection.execute(sqlalchemy.text(query)) + ... first_stuff, = result.fetchone() + """ def __init__( @@ -50,6 +69,7 @@ def __init__( password: Optional[str] = None, dbname: Optional[str] = None, port: int = 3306, + seed: Optional[str] = None, **kwargs, ) -> None: raise_for_deprecated_parameter(kwargs, "MYSQL_USER", "username") @@ -67,6 +87,7 @@ def __init__( if self.username == "root": self.root_password = self.password + self.seed = seed def _configure(self) -> None: self.with_env("MYSQL_ROOT_PASSWORD", self.root_password) @@ -86,3 +107,14 @@ def get_connection_url(self) -> str: return super()._create_connection_url( dialect="mysql+pymysql", username=self.username, password=self.password, dbname=self.dbname, port=self.port ) + + def _transfer_seed(self) -> None: + if self.seed is None: + return + src_path = Path(self.seed) + dest_path = "/docker-entrypoint-initdb.d/" + with BytesIO() as archive, tarfile.TarFile(fileobj=archive, mode="w") as tar: + for filename in src_path.iterdir(): + tar.add(filename.absolute(), arcname=filename.relative_to(src_path)) + archive.seek(0) + self.get_wrapped_container().put_archive(dest_path, archive) diff --git a/modules/mysql/tests/seeds/01-schema.sql b/modules/mysql/tests/seeds/01-schema.sql new file mode 100644 index 000000000..ea3982445 --- /dev/null +++ b/modules/mysql/tests/seeds/01-schema.sql @@ -0,0 +1,6 @@ +-- Sample SQL schema, no data +CREATE TABLE `stuff` ( + `id` mediumint NOT NULL AUTO_INCREMENT, + `name` VARCHAR(63) NOT NULL, + PRIMARY KEY (`id`) +); diff --git a/modules/mysql/tests/seeds/02-seeds.sql b/modules/mysql/tests/seeds/02-seeds.sql new file mode 100644 index 000000000..7ce78903b --- /dev/null +++ b/modules/mysql/tests/seeds/02-seeds.sql @@ -0,0 +1,3 @@ +-- Sample data, to be loaded after the schema +INSERT INTO stuff (name) +VALUES ("foo"), ("bar"), ("qux"), ("frob"); diff --git a/modules/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py index ee1e2b45e..847f99df4 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/modules/mysql/tests/test_mysql.py @@ -1,3 +1,4 @@ +from pathlib import Path import re from unittest import mock @@ -29,6 +30,18 @@ def test_docker_run_legacy_mysql(): assert row[0].startswith("5.7.44") +@pytest.mark.skipif(is_arm(), reason="mysql container not available for ARM") +def test_docker_run_mysql_8_seed(): + # Avoid pytest CWD path issues + SEEDS_PATH = (Path(__file__).parent / "seeds").absolute() + config = MySqlContainer("mysql:8", seed=SEEDS_PATH) + with config as mysql: + engine = sqlalchemy.create_engine(mysql.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("select * from stuff")) + assert len(list(result)) == 4, "Should have gotten all the stuff" + + @pytest.mark.parametrize("version", ["11.3.2", "10.11.7"]) def test_docker_run_mariadb(version: str): with MySqlContainer(f"mariadb:{version}") as mariadb: From 690b9b4526dcdf930c0733c227009af208f47cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Jales?= Date: Sat, 11 May 2024 06:27:23 +0200 Subject: [PATCH 363/425] fix: Add memcached container (#322) Adding [Memcached](https://memcached.org/) container. You have implemented a new container and would like to contribute it? Great! Here are the necessary steps. - [x] Create a new feature directory and populate it with the package structure [described in the documentation](https://testcontainers-python.readthedocs.io/en/latest/#package-structure). Copying one of the existing features is likely the best way to get started. - [x] Implement the new feature (typically in `__init__.py`) and corresponding tests. - [x] Add a line `-e file:[feature name]` to `requirements.in` and run `make requirements`. This command will find any new requirements and generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Then run `pip install -r requirements/[your python version].txt` to install the new requirements. - [x] Update the feature `README.rst` and add it to the table of contents (`toctree` directive) in the top-level `README.rst`. - [x] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `master` branch. - [x] Rebase your development branch on `master` (or merge `master` into your development branch). --- index.rst | 1 + modules/memcached/README.rst | 1 + modules/memcached/setup.py | 17 ++++++ .../testcontainers/memcached/__init__.py | 59 +++++++++++++++++++ modules/memcached/tests/test_memcached.py | 28 +++++++++ poetry.lock | 11 ++-- pyproject.toml | 2 + 7 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 modules/memcached/README.rst create mode 100644 modules/memcached/setup.py create mode 100644 modules/memcached/testcontainers/memcached/__init__.py create mode 100644 modules/memcached/tests/test_memcached.py diff --git a/index.rst b/index.rst index 0f9cd5a3c..3c7fcc140 100644 --- a/index.rst +++ b/index.rst @@ -27,6 +27,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/kafka/README modules/keycloak/README modules/localstack/README + modules/memcached/README modules/minio/README modules/mongodb/README modules/mssql/README diff --git a/modules/memcached/README.rst b/modules/memcached/README.rst new file mode 100644 index 000000000..83cd9c82e --- /dev/null +++ b/modules/memcached/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.memcached.MemcachedContainer diff --git a/modules/memcached/setup.py b/modules/memcached/setup.py new file mode 100644 index 000000000..0f4dd3234 --- /dev/null +++ b/modules/memcached/setup.py @@ -0,0 +1,17 @@ +from setuptools import find_namespace_packages, setup + +description = "Memcached component of testcontainers-python." + +setup( + name="testcontainers-memcached", + version="0.0.1rc1", + packages=find_namespace_packages(), + description=description, + long_description=description, + long_description_content_type="text/x-rst", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + ], + python_requires=">=3.7", +) diff --git a/modules/memcached/testcontainers/memcached/__init__.py b/modules/memcached/testcontainers/memcached/__init__.py new file mode 100644 index 000000000..6da409e06 --- /dev/null +++ b/modules/memcached/testcontainers/memcached/__init__.py @@ -0,0 +1,59 @@ +# +# 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 socket + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class MemcachedNotReady(Exception): + pass + + +class MemcachedContainer(DockerContainer): + """ + Test container for Memcached. The example below spins up a Memcached server + + Example: + + .. doctest:: + + >>> from testcontainers.memcached import MemcachedContainer + + >>> with MemcachedContainer() as memcached_container: + ... host, port = memcached_container.get_host_and_port() + """ + + def __init__(self, image="memcached:1", port_to_expose=11211, **kwargs): + super().__init__(image, **kwargs) + self.port_to_expose = port_to_expose + self.with_exposed_ports(port_to_expose) + + @wait_container_is_ready(MemcachedNotReady) + def _connect(self): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + host = self.get_container_host_ip() + port = int(self.get_exposed_port(self.port_to_expose)) + s.connect((host, port)) + s.sendall(b"stats\n\r") + data = s.recv(1024) + if len(data) == 0: + raise MemcachedNotReady("Memcached not ready yet") + + def start(self): + super().start() + self._connect() + return self + + def get_host_and_port(self): + return self.get_container_host_ip(), int(self.get_exposed_port(self.port_to_expose)) diff --git a/modules/memcached/tests/test_memcached.py b/modules/memcached/tests/test_memcached.py new file mode 100644 index 000000000..853ede40a --- /dev/null +++ b/modules/memcached/tests/test_memcached.py @@ -0,0 +1,28 @@ +import socket + +from testcontainers.memcached import MemcachedContainer + +import pytest + + +def test_memcached_host_and_exposed_port(): + with MemcachedContainer("memcached:1.6-alpine") as memcached: + host, port = memcached.get_host_and_port() + assert host == "localhost" + assert port != 11211 + + +@pytest.mark.parametrize("image", ["memcached:1.6-bookworm", "memcached:1.6-alpine"]) +def test_memcached_can_connect_and_retrieve_data(image): + with MemcachedContainer(image) as memcached: + host, port = memcached.get_host_and_port() + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((host, port)) + s.sendall(b"stats\n\r") + data = s.recv(1024) + assert len(data) > 0, "We should have received some data from memcached" + + pid_stat, uptime_stat, *_ = data.decode().split("\r\n") + + assert pid_stat.startswith("STAT pid") + assert uptime_stat.startswith("STAT uptime") diff --git a/poetry.lock b/poetry.lock index e4d5e1ee2..272b0b238 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3412,13 +3412,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" -version = "1.4.0" +version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.4" files = [ - {file = "requests-oauthlib-1.4.0.tar.gz", hash = "sha256:acee623221e4a39abcbb919312c8ff04bd44e7e417087fb4bd5e2a2f53d5e79a"}, - {file = "requests_oauthlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:7a3130d94a17520169e38db6c8d75f2c974643788465ecc2e4b36d288bf13033"}, + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, ] [package.dependencies] @@ -4189,6 +4189,7 @@ k3s = ["kubernetes", "pyyaml"] kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] +memcached = [] minio = ["minio"] mongodb = ["pymongo"] mssql = ["pymssql", "sqlalchemy"] @@ -4211,4 +4212,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "54136d629f04e2b87cf7dc0905b0ce877b5db2eaf6e1d6616d7648d4b730a759" +content-hash = "95a2e0ef23d8dfb1cbc74d72f534028aeff5da8bc26cc194f464f6fe282ba38f" diff --git a/pyproject.toml b/pyproject.toml index 9570ce063..d658aab1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ packages = [ { include = "testcontainers", from = "modules/kafka" }, { include = "testcontainers", from = "modules/keycloak" }, { include = "testcontainers", from = "modules/localstack" }, + { include = "testcontainers", from = "modules/memcached" }, { include = "testcontainers", from = "modules/minio" }, { include = "testcontainers", from = "modules/mongodb" }, { include = "testcontainers", from = "modules/mssql" }, @@ -111,6 +112,7 @@ k3s = ["kubernetes", "pyyaml"] kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] +memcached = [] minio = ["minio"] mongodb = ["pymongo"] mssql = ["sqlalchemy", "pymssql"] From 38946d41dacdc4985fc696a5d58cf7d97e367a1c Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Sun, 12 May 2024 23:01:27 +0300 Subject: [PATCH 364/425] fix(core): remove version from compose tests (#571) This would address #570 Removing the version from the compose.yaml files used in tests. Please see https://forums.docker.com/t/docker-compose-yml-version-is-obsolete/141313 --- core/tests/compose_fixtures/basic/docker-compose.yaml | 2 -- core/tests/compose_fixtures/port_multiple/compose.yaml | 2 -- core/tests/compose_fixtures/port_single/compose.yaml | 2 -- 3 files changed, 6 deletions(-) diff --git a/core/tests/compose_fixtures/basic/docker-compose.yaml b/core/tests/compose_fixtures/basic/docker-compose.yaml index ff3f74220..f6fd6a2a1 100644 --- a/core/tests/compose_fixtures/basic/docker-compose.yaml +++ b/core/tests/compose_fixtures/basic/docker-compose.yaml @@ -1,5 +1,3 @@ -version: '3.0' - services: alpine: image: alpine:latest diff --git a/core/tests/compose_fixtures/port_multiple/compose.yaml b/core/tests/compose_fixtures/port_multiple/compose.yaml index 65717fc4a..e8e147bbd 100644 --- a/core/tests/compose_fixtures/port_multiple/compose.yaml +++ b/core/tests/compose_fixtures/port_multiple/compose.yaml @@ -1,5 +1,3 @@ -version: '3.0' - services: alpine: image: nginx:alpine-slim diff --git a/core/tests/compose_fixtures/port_single/compose.yaml b/core/tests/compose_fixtures/port_single/compose.yaml index d1bf9eb45..88c19ab61 100644 --- a/core/tests/compose_fixtures/port_single/compose.yaml +++ b/core/tests/compose_fixtures/port_single/compose.yaml @@ -1,5 +1,3 @@ -version: '3.0' - services: alpine: image: nginx:alpine-slim From 3c8006cb6b94d074d2e33d27e972409886bcc7f3 Mon Sep 17 00:00:00 2001 From: Yosef Shenhav <63923874+JosefShenhav@users.noreply.github.com> Date: Tue, 14 May 2024 10:23:17 +0300 Subject: [PATCH 365/425] fix: Add selenium video support #6 (#364) Support video in selenium testcontainer. Changes made: - Added network to default container. - Added video to selenium by write `with_video`. --- .../testcontainers/selenium/__init__.py | 50 ++++++++++++++++++- .../selenium/testcontainers/selenium/video.py | 39 +++++++++++++++ modules/selenium/tests/test_selenium.py | 22 ++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 modules/selenium/testcontainers/selenium/video.py diff --git a/modules/selenium/testcontainers/selenium/__init__.py b/modules/selenium/testcontainers/selenium/__init__.py index b46d46155..50cc566bd 100644 --- a/modules/selenium/testcontainers/selenium/__init__.py +++ b/modules/selenium/testcontainers/selenium/__init__.py @@ -10,17 +10,20 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +from pathlib import Path from typing import Optional import urllib3 +from typing_extensions import Self from selenium import webdriver from selenium.webdriver.common.options import ArgOptions from testcontainers.core.container import DockerContainer +from testcontainers.core.network import Network from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.selenium.video import SeleniumVideoContainer -IMAGES = {"firefox": "selenium/standalone-firefox-debug:latest", "chrome": "selenium/standalone-chrome-debug:latest"} +IMAGES = {"firefox": "selenium/standalone-firefox:latest", "chrome": "selenium/standalone-chrome:latest"} def get_image_name(capabilities: str) -> str: @@ -51,6 +54,8 @@ def __init__( self.image = image or get_image_name(capabilities) self.port = port self.vnc_port = vnc_port + self.video = None + self.__video_network = None super().__init__(image=self.image, **kwargs) self.with_exposed_ports(self.port, self.vnc_port) @@ -72,3 +77,44 @@ def get_connection_url(self) -> str: ip = self.get_container_host_ip() port = self.get_exposed_port(self.port) return f"http://{ip}:{port}/wd/hub" + + def with_video(self, image: Optional[str] = None, video_path: Optional[Path] = None) -> Self: + video_path = video_path or Path.cwd() + + self.video = SeleniumVideoContainer(image) + + video_folder_path = video_path.parent if video_path.suffix else video_path + self.video.set_videos_host_path(str(video_folder_path.resolve())) + + if video_path.name: + self.video.set_video_name(video_path.name) + + return self + + def start(self) -> "DockerContainer": + if not self.video: + super().start() + return self + + self.__video_network = Network().__enter__() + + self.with_kwargs(network=self.__video_network.name) + super().start() + + self.video.with_kwargs(network=self.__video_network.name).set_selenium_container_host( + self.get_wrapped_container().short_id + ).start() + + return self + + def stop(self, force=True, delete_volume=True) -> None: + if self.video: + # get_wrapped_container().stop -> stop the container + # video.stop -> remove the container + self.video.get_wrapped_container().stop() + self.video.stop(force, delete_volume) + + super().stop(force, delete_volume) + + if self.__video_network: + self.__video_network.remove() diff --git a/modules/selenium/testcontainers/selenium/video.py b/modules/selenium/testcontainers/selenium/video.py new file mode 100644 index 000000000..debf098db --- /dev/null +++ b/modules/selenium/testcontainers/selenium/video.py @@ -0,0 +1,39 @@ +# +# 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. +from typing import Optional + +from testcontainers.core.container import DockerContainer + +VIDEO_DEFAULT_IMAGE = "selenium/video:ffmpeg-6.1-20240402" + + +class SeleniumVideoContainer(DockerContainer): + """ + Selenium video container. + """ + + def __init__(self, image: Optional[str] = None, **kwargs) -> None: + self.image = image or VIDEO_DEFAULT_IMAGE + super().__init__(image=self.image, **kwargs) + + def set_video_name(self, video_name: str) -> "DockerContainer": + self.with_env("FILE_NAME", video_name) + return self + + def set_videos_host_path(self, host_path: str) -> "DockerContainer": + self.with_volume_mapping(host_path, "/videos", "rw") + return self + + def set_selenium_container_host(self, host: str) -> "DockerContainer": + self.with_env("DISPLAY_CONTAINER_NAME", host) + return self diff --git a/modules/selenium/tests/test_selenium.py b/modules/selenium/tests/test_selenium.py index 61c1bb326..ac243c27b 100644 --- a/modules/selenium/tests/test_selenium.py +++ b/modules/selenium/tests/test_selenium.py @@ -1,3 +1,7 @@ +import os +import tempfile +from pathlib import Path + import pytest from selenium.webdriver import DesiredCapabilities from selenium.webdriver.common.by import By @@ -23,3 +27,21 @@ def test_selenium_custom_image(): chrome = BrowserWebDriverContainer(DesiredCapabilities.CHROME, image=image) assert "image" in dir(chrome), "`image` attribute was not instantialized." assert chrome.image == image, "`image` attribute was not set to the user provided value" + + +@pytest.mark.parametrize("caps", [DesiredCapabilities.CHROME, DesiredCapabilities.FIREFOX]) +def test_selenium_video(caps, workdir): + video_path = workdir / Path("video.mp4") + with BrowserWebDriverContainer(caps).with_video(video_path=video_path) as chrome: + chrome.get_driver().get("https://google.com") + + assert video_path.exists(), "Selenium video file does not exist" + + +@pytest.fixture +def workdir() -> Path: + tmpdir = tempfile.TemporaryDirectory() + # Enable write permissions for the Docker user container. + os.chmod(tmpdir.name, 0o777) + yield Path(tmpdir.name) + tmpdir.cleanup() From 08916c8fa29c835bc5c62fdbdd26ac1546c0c061 Mon Sep 17 00:00:00 2001 From: Christian Schroeder <43764673+christianaaronschroeder@users.noreply.github.com> Date: Tue, 14 May 2024 03:40:04 -0400 Subject: [PATCH 366/425] fix(core): add empty _configure to DockerContainer (#556) I've used DbContainer in the past as a parent to a class where I redefined `_configure()` and did not redefine `start()`. I'm now using DockerContainer and wanted to follow the same pattern but it doesn't have a `_configure()` function called in `start()`. I figure this gives the easy option without being invasive? tests pass --------- Co-authored-by: David Ankin --- core/testcontainers/core/container.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 73e3287d2..4fa651e4c 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -87,6 +87,7 @@ def start(self) -> Self: Reaper.get_instance() logger.info("Pulling image %s", self.image) docker_client = self.get_docker_client() + self._configure() self._container = docker_client.run( self.image, command=self._command, @@ -176,6 +177,10 @@ def exec(self, command) -> tuple[int, str]: raise ContainerStartException("Container should be started before executing a command") return self._container.exec_run(command) + def _configure(self) -> None: + # placeholder if subclasses want to define this and use the default start method + pass + class Reaper: _instance: "Optional[Reaper]" = None From 49b261e0224296680d7ef2faee00e0166178a35e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 03:48:17 -0400 Subject: [PATCH 367/425] chore(main): release testcontainers 4.4.1 (#551) :robot: I have created a release *beep* *boop* --- ## [4.4.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.4.0...testcontainers-v4.4.1) (2024-05-14) ### Bug Fixes * Add memcached container ([#322](https://github.com/testcontainers/testcontainers-python/issues/322)) ([690b9b4](https://github.com/testcontainers/testcontainers-python/commit/690b9b4526dcdf930c0733c227009af208f47cda)) * Add selenium video support [#6](https://github.com/testcontainers/testcontainers-python/issues/6) ([#364](https://github.com/testcontainers/testcontainers-python/issues/364)) ([3c8006c](https://github.com/testcontainers/testcontainers-python/commit/3c8006cb6b94d074d2e33d27e972409886bcc7f3)) * **core:** add empty _configure to DockerContainer ([#556](https://github.com/testcontainers/testcontainers-python/issues/556)) ([08916c8](https://github.com/testcontainers/testcontainers-python/commit/08916c8fa29c835bc5c62fdbdd26ac1546c0c061)) * **core:** remove version from compose tests ([#571](https://github.com/testcontainers/testcontainers-python/issues/571)) ([38946d4](https://github.com/testcontainers/testcontainers-python/commit/38946d41dacdc4985fc696a5d58cf7d97e367a1c)) * **keycloak:** add realm imports ([#565](https://github.com/testcontainers/testcontainers-python/issues/565)) ([f761b98](https://github.com/testcontainers/testcontainers-python/commit/f761b983613e16dc56e560a947247c01052c19f6)) * **mysql:** Add seed support in MySQL ([#552](https://github.com/testcontainers/testcontainers-python/issues/552)) ([396079a](https://github.com/testcontainers/testcontainers-python/commit/396079a5af4c550084df2be5037a0ff52cd9fb5a)) * url quote passwords ([#549](https://github.com/testcontainers/testcontainers-python/issues/549)) ([6c5d227](https://github.com/testcontainers/testcontainers-python/commit/6c5d227730d415111c54e7ea3cb5d86b549cc901)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index b7c720789..cc2772216 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.4.0" + ".": "4.4.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 56c4fe365..ecc280b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [4.4.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.4.0...testcontainers-v4.4.1) (2024-05-14) + + +### Bug Fixes + +* Add memcached container ([#322](https://github.com/testcontainers/testcontainers-python/issues/322)) ([690b9b4](https://github.com/testcontainers/testcontainers-python/commit/690b9b4526dcdf930c0733c227009af208f47cda)) +* Add selenium video support [#6](https://github.com/testcontainers/testcontainers-python/issues/6) ([#364](https://github.com/testcontainers/testcontainers-python/issues/364)) ([3c8006c](https://github.com/testcontainers/testcontainers-python/commit/3c8006cb6b94d074d2e33d27e972409886bcc7f3)) +* **core:** add empty _configure to DockerContainer ([#556](https://github.com/testcontainers/testcontainers-python/issues/556)) ([08916c8](https://github.com/testcontainers/testcontainers-python/commit/08916c8fa29c835bc5c62fdbdd26ac1546c0c061)) +* **core:** remove version from compose tests ([#571](https://github.com/testcontainers/testcontainers-python/issues/571)) ([38946d4](https://github.com/testcontainers/testcontainers-python/commit/38946d41dacdc4985fc696a5d58cf7d97e367a1c)) +* **keycloak:** add realm imports ([#565](https://github.com/testcontainers/testcontainers-python/issues/565)) ([f761b98](https://github.com/testcontainers/testcontainers-python/commit/f761b983613e16dc56e560a947247c01052c19f6)) +* **mysql:** Add seed support in MySQL ([#552](https://github.com/testcontainers/testcontainers-python/issues/552)) ([396079a](https://github.com/testcontainers/testcontainers-python/commit/396079a5af4c550084df2be5037a0ff52cd9fb5a)) +* url quote passwords ([#549](https://github.com/testcontainers/testcontainers-python/issues/549)) ([6c5d227](https://github.com/testcontainers/testcontainers-python/commit/6c5d227730d415111c54e7ea3cb5d86b549cc901)) + ## [4.4.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.3.3...testcontainers-v4.4.0) (2024-04-17) diff --git a/pyproject.toml b/pyproject.toml index d658aab1b..2594f3e44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.4.0" # auto-incremented by release-please +version = "4.4.1" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 6f9376c9864fa2fdec49fa9f11acf2e44cb7d8ba Mon Sep 17 00:00:00 2001 From: David Ankin Date: Tue, 14 May 2024 06:00:51 -0400 Subject: [PATCH 368/425] test(postgres): add example of initdb.d usage for postgres (#572) --- .../postgres_create_example_table.sql | 6 ++++++ modules/postgres/tests/test_postgres.py | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 modules/postgres/tests/fixtures/postgres_create_example_table.sql diff --git a/modules/postgres/tests/fixtures/postgres_create_example_table.sql b/modules/postgres/tests/fixtures/postgres_create_example_table.sql new file mode 100644 index 000000000..4d7994ead --- /dev/null +++ b/modules/postgres/tests/fixtures/postgres_create_example_table.sql @@ -0,0 +1,6 @@ +create table example +( + id serial not null primary key, + name varchar(255) not null unique, + description text null +); diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index fbba6932d..528403617 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from testcontainers.postgres import PostgresContainer @@ -77,3 +79,21 @@ def test_quoted_password(): # it raises ValueError, but auth (OperationalError) = more interesting with sqlalchemy.create_engine(raw_pass_url).begin() as connection: connection.execute(sqlalchemy.text("select 1=1")) + + +def test_show_how_to_initialize_db_via_initdb_dir(): + postgres_container = PostgresContainer("postgres:16-alpine") + script = Path(__file__).parent / "fixtures" / "postgres_create_example_table.sql" + postgres_container.with_volume_mapping(host=str(script), container=f"/docker-entrypoint-initdb.d/{script.name}") + + insert_query = "insert into example(name, description) VALUES ('sally', 'sells seashells');" + select_query = "select id, name, description from example;" + + with postgres_container as postgres: + engine = sqlalchemy.create_engine(postgres.get_connection_url()) + with engine.begin() as connection: + connection.execute(sqlalchemy.text(insert_query)) + result = connection.execute(sqlalchemy.text(select_query)) + result = result.fetchall() + assert len(result) == 1 + assert result[0] == (1, "sally", "sells seashells") From 9eabb79f213cfb6d8e60173ff4c40f580ae0972a Mon Sep 17 00:00:00 2001 From: Daniel Adekugbe <78769670+Dandiggas@users.noreply.github.com> Date: Fri, 17 May 2024 11:13:51 +0100 Subject: [PATCH 369/425] fix: added types to exec & tc_properties_get_tc_host (#561) https://github.com/testcontainers/testcontainers-python/issues/557 - trying to solve this issue by adding types. --------- Co-authored-by: Dandiggas --- core/testcontainers/core/config.py | 3 ++- core/testcontainers/core/container.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 391c88bfa..1b3719e78 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -2,6 +2,7 @@ from os import environ from os.path import exists from pathlib import Path +from typing import Union MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120)) SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) @@ -47,7 +48,7 @@ class TestcontainersConfiguration: ryuk_reconnection_timeout: str = RYUK_RECONNECTION_TIMEOUT tc_properties: dict[str, str] = field(default_factory=read_tc_properties) - def tc_properties_get_tc_host(self): + def tc_properties_get_tc_host(self) -> Union[str, None]: return self.tc_properties.get("tc.host") @property diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 4fa651e4c..085fc58e1 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -172,7 +172,7 @@ def get_logs(self) -> tuple[bytes, bytes]: raise ContainerStartException("Container should be started before getting logs") return self._container.logs(stderr=False), self._container.logs(stdout=False) - def exec(self, command) -> tuple[int, str]: + def exec(self, command) -> tuple[int, bytes]: if not self._container: raise ContainerStartException("Container should be started before executing a command") return self._container.exec_run(command) From 9d2ceb6a80782085ae20511343577333452bd6d4 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Fri, 24 May 2024 18:52:36 -0400 Subject: [PATCH 370/425] typos (#580) --- core/testcontainers/core/labels.py | 2 +- modules/arangodb/tests/test_arangodb.py | 10 +++++----- modules/rabbitmq/tests/test_rabbitmq.py | 2 +- modules/weaviate/testcontainers/weaviate/__init__.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/testcontainers/core/labels.py b/core/testcontainers/core/labels.py index df9c617b1..0570b22cb 100644 --- a/core/testcontainers/core/labels.py +++ b/core/testcontainers/core/labels.py @@ -19,7 +19,7 @@ def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str else: for k in labels: if k.startswith(TESTCONTAINERS_NAMESPACE): - raise ValueError("The org.testcontainers namespace is reserved for interal use") + raise ValueError("The org.testcontainers namespace is reserved for internal use") labels[LABEL_LANG] = "python" labels[LABEL_TESTCONTAINERS] = "true" diff --git a/modules/arangodb/tests/test_arangodb.py b/modules/arangodb/tests/test_arangodb.py index f526bf383..01e4643a7 100644 --- a/modules/arangodb/tests/test_arangodb.py +++ b/modules/arangodb/tests/test_arangodb.py @@ -13,7 +13,7 @@ IMAGE_VERSION = "3.11.8" -def arango_test_ops(arango_client, expeced_version, username="root", password=""): +def arango_test_ops(arango_client, expected_version, username="root", password=""): """ Basic ArangoDB operations to test DB really up and running. """ @@ -22,7 +22,7 @@ def arango_test_ops(arango_client, expeced_version, username="root", password="" # Taken from https://github.com/ArangoDB-Community/python-arango/blob/main/README.md # Connect to "_system" database as root user. sys_db = arango_client.db("_system", username=username, password=password) - assert sys_db.version() == expeced_version + assert sys_db.version() == expected_version # Create a new database named "test". sys_db.create_database("test") @@ -63,7 +63,7 @@ def test_docker_run_arango(): with pytest.raises(DatabaseCreateError): sys_db.create_database("test") - arango_test_ops(arango_client=client, expeced_version=IMAGE_VERSION, password=arango_root_password) + arango_test_ops(arango_client=client, expected_version=IMAGE_VERSION, password=arango_root_password) def test_docker_run_arango_without_auth(): @@ -75,7 +75,7 @@ def test_docker_run_arango_without_auth(): with ArangoDbContainer(image, arango_no_auth=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) - arango_test_ops(arango_client=client, expeced_version=IMAGE_VERSION, password="") + arango_test_ops(arango_client=client, expected_version=IMAGE_VERSION, password="") @pytest.mark.skipif(platform.processor() == "arm", reason="Test does not run on machines with ARM CPU") @@ -94,7 +94,7 @@ def test_docker_run_arango_older_version(): with ArangoDbContainer(image, arango_no_auth=True) as arango: client = ArangoClient(hosts=arango.get_connection_url()) - arango_test_ops(arango_client=client, expeced_version=image_version, password="") + arango_test_ops(arango_client=client, expected_version=image_version, password="") def test_docker_run_arango_random_root_password(): diff --git a/modules/rabbitmq/tests/test_rabbitmq.py b/modules/rabbitmq/tests/test_rabbitmq.py index 25c0fbbb9..98fb7e6d3 100644 --- a/modules/rabbitmq/tests/test_rabbitmq.py +++ b/modules/rabbitmq/tests/test_rabbitmq.py @@ -42,7 +42,7 @@ def test_docker_run_rabbitmq(port: Optional[int], username: Optional[str], passw channel.queue_declare(QUEUE, arguments={}) channel.queue_bind(QUEUE, EXCHANGE, ROUTING_KEY) - # pulish message: + # publish message: encoded_message = json.dumps(MESSAGE) channel.basic_publish(EXCHANGE, ROUTING_KEY, body=encoded_message) diff --git a/modules/weaviate/testcontainers/weaviate/__init__.py b/modules/weaviate/testcontainers/weaviate/__init__.py index e59e251ec..c33983121 100644 --- a/modules/weaviate/testcontainers/weaviate/__init__.py +++ b/modules/weaviate/testcontainers/weaviate/__init__.py @@ -32,7 +32,7 @@ class WeaviateContainer(DbContainer): Additional environment variables to include with the container, e.g. ENABLE_MODULES list, QUERY_DEFAULTS_LIMIT setting. Example: - This example shows how to start Weaviate container with defualt settings. + This example shows how to start Weaviate container with default settings. .. doctest:: @@ -43,7 +43,7 @@ class WeaviateContainer(DbContainer): ... client.is_live() True - This example shows how to start Weaviate container with additinal settings. + This example shows how to start Weaviate container with additional settings. .. doctest:: From 59fbcfaf512d1f094e6d8346d45766e810ee2d44 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Sat, 25 May 2024 22:32:26 +0300 Subject: [PATCH 371/425] feat(core): Private registry (#566) Ref #562 This enhancement adds capability to utilize the env var `DOCKER_AUTH_CONFIG` in-order to login to a private docker registry. --------- Co-authored-by: David Ankin --- core/testcontainers/core/config.py | 19 +++++++++- core/testcontainers/core/docker_client.py | 17 ++++++++- core/testcontainers/core/utils.py | 31 +++++++++++++++++ core/tests/test_docker_client.py | 32 +++++++++++++++++ core/tests/test_utils.py | 42 +++++++++++++++++++++++ 5 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 core/tests/test_utils.py diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 1b3719e78..5e038b451 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -1,8 +1,9 @@ from dataclasses import dataclass, field +from logging import warning from os import environ from os.path import exists from pathlib import Path -from typing import Union +from typing import Optional, Union MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120)) SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) @@ -37,6 +38,9 @@ def read_tc_properties() -> dict[str, str]: return settings +_WARNINGS = {"DOCKER_AUTH_CONFIG": "DOCKER_AUTH_CONFIG is experimental, see testcontainers/testcontainers-python#566"} + + @dataclass class TestcontainersConfiguration: max_tries: int = MAX_TRIES @@ -47,6 +51,19 @@ class TestcontainersConfiguration: ryuk_docker_socket: str = RYUK_DOCKER_SOCKET ryuk_reconnection_timeout: str = RYUK_RECONNECTION_TIMEOUT tc_properties: dict[str, str] = field(default_factory=read_tc_properties) + _docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG")) + + @property + def docker_auth_config(self): + if "DOCKER_AUTH_CONFIG" in _WARNINGS: + warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) + return self._docker_auth_config + + @docker_auth_config.setter + def docker_auth_config(self, value: str): + if "DOCKER_AUTH_CONFIG" in _WARNINGS: + warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) + self._docker_auth_config = value def tc_properties_get_tc_host(self) -> Union[str, None]: return self.tc_properties.get("tc.host") diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index e43dddb41..485adb594 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -24,7 +24,7 @@ from testcontainers.core.config import testcontainers_config as c from testcontainers.core.labels import SESSION_ID, create_labels -from testcontainers.core.utils import default_gateway_ip, inside_container, setup_logger +from testcontainers.core.utils import default_gateway_ip, inside_container, parse_docker_auth_config, setup_logger LOGGER = setup_logger(__name__) @@ -57,6 +57,9 @@ def __init__(self, **kwargs) -> None: self.client.api.headers["x-tc-sid"] = SESSION_ID self.client.api.headers["User-Agent"] = "tc-python/" + importlib.metadata.version("testcontainers") + if docker_auth_config := get_docker_auth_config(): + self.login(docker_auth_config) + @_wrapped_container_collection def run( self, @@ -183,6 +186,18 @@ def host(self) -> str: return ip_address return "localhost" + def login(self, docker_auth_config: str) -> None: + """ + Login to a docker registry using the given auth config. + """ + auth_config = parse_docker_auth_config(docker_auth_config)[0] # Only using the first auth config + login_info = self.client.login(**auth_config._asdict()) + LOGGER.debug(f"logged in using {login_info}") + def get_docker_host() -> Optional[str]: return c.tc_properties_get_tc_host() or os.getenv("DOCKER_HOST") + + +def get_docker_auth_config() -> Optional[str]: + return c.docker_auth_config diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index 5ca1c2f7d..0061e8329 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -1,13 +1,18 @@ +import base64 +import json import logging import os import platform import subprocess import sys +from collections import namedtuple LINUX = "linux" MAC = "mac" WIN = "win" +DockerAuthInfo = namedtuple("DockerAuthInfo", ["registry", "username", "password"]) + def setup_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) @@ -77,3 +82,29 @@ def raise_for_deprecated_parameter(kwargs: dict, name: str, replacement: str) -> if kwargs.pop(name, None): raise ValueError(f"Use `{replacement}` instead of `{name}`") return kwargs + + +def parse_docker_auth_config(auth_config: str) -> list[DockerAuthInfo]: + """ + Parse the docker auth config from a string. + + Example: + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=" + } + } + } + """ + auth_info: list[DockerAuthInfo] = [] + try: + auth_config_dict: dict = json.loads(auth_config).get("auths") + for registry, auth in auth_config_dict.items(): + auth_str = auth.get("auth") + auth_str = base64.b64decode(auth_str).decode("utf-8") + username, password = auth_str.split(":") + auth_info.append(DockerAuthInfo(registry, username, password)) + return auth_info + except (json.JSONDecodeError, KeyError, ValueError) as exp: + raise ValueError("Could not parse docker auth config") from exp diff --git a/core/tests/test_docker_client.py b/core/tests/test_docker_client.py index 23f92e9e5..cfd95be91 100644 --- a/core/tests/test_docker_client.py +++ b/core/tests/test_docker_client.py @@ -1,9 +1,14 @@ +import os +from collections import namedtuple +from unittest import mock from unittest.mock import MagicMock, patch import docker +from testcontainers.core.config import testcontainers_config as c from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient +from testcontainers.core.utils import parse_docker_auth_config def test_docker_client_from_env(): @@ -15,6 +20,33 @@ def test_docker_client_from_env(): mock_docker.from_env.assert_called_with(**test_kwargs) +def test_docker_client_login_no_login(): + with patch.dict(os.environ, {}, clear=True): + mock_docker = MagicMock(spec=docker) + with patch("testcontainers.core.docker_client.docker", mock_docker): + DockerClient() + + mock_docker.from_env.return_value.login.assert_not_called() + + +def test_docker_client_login(): + mock_docker = MagicMock(spec=docker) + mock_parse_docker_auth_config = MagicMock(spec=parse_docker_auth_config) + mock_utils = MagicMock() + mock_utils.parse_docker_auth_config = mock_parse_docker_auth_config + TestAuth = namedtuple("Auth", "value") + mock_parse_docker_auth_config.return_value = [TestAuth("test")] + + with ( + mock.patch.object(c, "_docker_auth_config", "test"), + patch("testcontainers.core.docker_client.docker", mock_docker), + patch("testcontainers.core.docker_client.parse_docker_auth_config", mock_parse_docker_auth_config), + ): + DockerClient() + + mock_docker.from_env.return_value.login.assert_called_with(**{"value": "test"}) + + def test_container_docker_client_kw(): test_kwargs = {"test_kw": "test_value"} mock_docker = MagicMock(spec=docker) diff --git a/core/tests/test_utils.py b/core/tests/test_utils.py new file mode 100644 index 000000000..56f96fbf0 --- /dev/null +++ b/core/tests/test_utils.py @@ -0,0 +1,42 @@ +import json + +from testcontainers.core.utils import parse_docker_auth_config, DockerAuthInfo + + +def test_parse_docker_auth_config(): + auth_config_json = '{"auths":{"https://index.docker.io/v1/":{"auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}' + auth_info = parse_docker_auth_config(auth_config_json) + assert len(auth_info) == 1 + assert auth_info[0] == DockerAuthInfo( + registry="https://index.docker.io/v1/", + username="username", + password="password", + ) + + +def test_parse_docker_auth_config_multiple(): + auth_dict = { + "auths": { + "localhost:5000": {"auth": "dXNlcjE6cGFzczE=="}, + "https://example.com": {"auth": "dXNlcl9uZXc6cGFzc19uZXc=="}, + "example2.com": {"auth": "YWJjOjEyMw==="}, + } + } + auth_config_json = json.dumps(auth_dict) + auth_info = parse_docker_auth_config(auth_config_json) + assert len(auth_info) == 3 + assert auth_info[0] == DockerAuthInfo( + registry="localhost:5000", + username="user1", + password="pass1", + ) + assert auth_info[1] == DockerAuthInfo( + registry="https://example.com", + username="user_new", + password="pass_new", + ) + assert auth_info[2] == DockerAuthInfo( + registry="example2.com", + username="abc", + password="123", + ) From 2aa3d371647877db45eac1663814dcc99de0f6af Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 25 May 2024 15:32:38 -0400 Subject: [PATCH 372/425] fix: on windows, DockerCompose.get_service_host returns an unusable "0.0.0.0" - adjust to 127.0.0.1 (#457) #358 not sure if this is the right solution --- core/testcontainers/compose/compose.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index 5931b35a6..951aee6d3 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -1,7 +1,8 @@ -from dataclasses import dataclass, field, fields +from dataclasses import asdict, dataclass, field, fields from functools import cached_property from json import loads from os import PathLike +from platform import system from re import split from subprocess import CompletedProcess from subprocess import run as subprocess_run @@ -38,6 +39,14 @@ class PublishedPort: PublishedPort: Optional[str] = None Protocol: Optional[str] = None + def normalize(self): + url_not_usable = system() == "Windows" and self.URL == "0.0.0.0" + if url_not_usable: + self_dict = asdict(self) + self_dict.update({"URL": "127.0.0.1"}) + return PublishedPort(**self_dict) + return self + OT = TypeVar("OT") @@ -357,7 +366,7 @@ def get_service_port( str: The mapped port on the host """ - return self.get_container(service_name).get_publisher(by_port=port).PublishedPort + return self.get_container(service_name).get_publisher(by_port=port).normalize().PublishedPort def get_service_host( self, @@ -379,14 +388,14 @@ def get_service_host( str: The hostname for the service """ - return self.get_container(service_name).get_publisher(by_port=port).URL + return self.get_container(service_name).get_publisher(by_port=port).normalize().URL def get_service_host_and_port( self, service_name: Optional[str] = None, port: Optional[int] = None, ): - publisher = self.get_container(service_name).get_publisher(by_port=port) + publisher = self.get_container(service_name).get_publisher(by_port=port).normalize() return publisher.URL, publisher.PublishedPort @wait_container_is_ready(HTTPError, URLError) From 70414dbc536b1c301e61efb5a588e14549a59cf0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 25 May 2024 15:36:23 -0400 Subject: [PATCH 373/425] chore(main): release testcontainers 4.5.0 (#575) :robot: I have created a release *beep* *boop* --- ## [4.5.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.4.1...testcontainers-v4.5.0) (2024-05-25) ### Features * **core:** Private registry ([#566](https://github.com/testcontainers/testcontainers-python/issues/566)) ([59fbcfa](https://github.com/testcontainers/testcontainers-python/commit/59fbcfaf512d1f094e6d8346d45766e810ee2d44)) ### Bug Fixes * added types to exec & tc_properties_get_tc_host ([#561](https://github.com/testcontainers/testcontainers-python/issues/561)) ([9eabb79](https://github.com/testcontainers/testcontainers-python/commit/9eabb79f213cfb6d8e60173ff4c40f580ae0972a)) * on windows, DockerCompose.get_service_host returns an unusable "0.0.0.0" - adjust to 127.0.0.1 ([#457](https://github.com/testcontainers/testcontainers-python/issues/457)) ([2aa3d37](https://github.com/testcontainers/testcontainers-python/commit/2aa3d371647877db45eac1663814dcc99de0f6af)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index cc2772216..49bcca2a8 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.4.1" + ".": "4.5.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc280b2c..1f0f003de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [4.5.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.4.1...testcontainers-v4.5.0) (2024-05-25) + + +### Features + +* **core:** Private registry ([#566](https://github.com/testcontainers/testcontainers-python/issues/566)) ([59fbcfa](https://github.com/testcontainers/testcontainers-python/commit/59fbcfaf512d1f094e6d8346d45766e810ee2d44)) + + +### Bug Fixes + +* added types to exec & tc_properties_get_tc_host ([#561](https://github.com/testcontainers/testcontainers-python/issues/561)) ([9eabb79](https://github.com/testcontainers/testcontainers-python/commit/9eabb79f213cfb6d8e60173ff4c40f580ae0972a)) +* on windows, DockerCompose.get_service_host returns an unusable "0.0.0.0" - adjust to 127.0.0.1 ([#457](https://github.com/testcontainers/testcontainers-python/issues/457)) ([2aa3d37](https://github.com/testcontainers/testcontainers-python/commit/2aa3d371647877db45eac1663814dcc99de0f6af)) + ## [4.4.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.4.0...testcontainers-v4.4.1) (2024-05-14) diff --git a/pyproject.toml b/pyproject.toml index 2594f3e44..cbf650bea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.4.1" # auto-incremented by release-please +version = "4.5.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 8fe2d6dd1829daedd85f848e5b17e30b941efec6 Mon Sep 17 00:00:00 2001 From: Andrzej Wasowski Date: Mon, 27 May 2024 06:08:48 +0200 Subject: [PATCH 374/425] chore: add .gitattributes file for shell scripts (#581) Just a .gitattributes file to checkout .sh files with LF line ending for people coming from Windows world. --------- Co-authored-by: David Ankin --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a9a66543c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Shell scripts +*.sh text eol=lf From 78b6f0ecb15e8cba687eb4588c5ce19ca32208bc Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Mon, 27 May 2024 08:07:35 +0300 Subject: [PATCH 375/425] chore(core): Adds integration testing to the private registry auth feature (DOCKER_AUTH_CONFIG) (#582) Follow up on #566 - Testing using the registry module --- core/tests/test_registry.py | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 core/tests/test_registry.py diff --git a/core/tests/test_registry.py b/core/tests/test_registry.py new file mode 100644 index 000000000..384b06693 --- /dev/null +++ b/core/tests/test_registry.py @@ -0,0 +1,83 @@ +"""Integration test using login to a private registry. + +Note: Using the testcontainers-python library to test the Docker registry. +This could be considered a bad practice as it is not recommended to use the same library to test itself. +However, it is a very good use case for DockerRegistryContainer and allows us to test it thoroughly. +""" + +import json +import os +import base64 +import pytest + +from docker.errors import NotFound + +from testcontainers.core.config import testcontainers_config +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.waiting_utils import wait_container_is_ready + +from testcontainers.registry import DockerRegistryContainer + + +def test_missing_on_private_registry(monkeypatch): + username = "user" + password = "pass" + image = "hello-world" + tag = "test" + + with DockerRegistryContainer(username=username, password=password) as registry: + registry_url = registry.get_registry() + + # prepare auth config + creds: bytes = base64.b64encode(f"{username}:{password}".encode("utf-8")) + config = {"auths": {f"{registry_url}": {"auth": creds.decode("utf-8")}}} + monkeypatch.setattr(testcontainers_config, name="docker_auth_config", value=json.dumps(config)) + assert testcontainers_config.docker_auth_config, "docker_auth_config not set" + + with pytest.raises(NotFound): + # Test a container with image from private registry + with DockerContainer(f"{registry_url}/{image}:{tag}") as test_container: + wait_container_is_ready(test_container) + + +@pytest.mark.parametrize( + "image,tag,username,password", + [ + ("nginx", "test", "user", "pass"), + ("hello-world", "latest", "new_user", "new_pass"), + ("alpine", "3.12", None, None), + ], +) +def test_with_private_registry(image, tag, username, password, monkeypatch): + client = DockerClient().client + + with DockerRegistryContainer(username=username, password=password) as registry: + registry_url = registry.get_registry() + + # prepare image + _image = client.images.pull(image) + assert _image.tag(repository=f"{registry_url}/{image}", tag=tag), "Image not tagged" + + # login to private registry + client.login(registry=registry_url, username=username, password=password) + + # push image to private registry + client.images.push(f"{registry_url}/{image}") + + # clear local image so we will pull from private registry + client.images.remove(f"{registry_url}/{image}:{tag}") + + # prepare auth config + creds: bytes = base64.b64encode(f"{username}:{password}".encode("utf-8")) + config = {"auths": {f"{registry_url}": {"auth": creds.decode("utf-8")}}} + monkeypatch.setattr(testcontainers_config, name="docker_auth_config", value=json.dumps(config)) + assert testcontainers_config.docker_auth_config, "docker_auth_config not set" + + # Test a container with image from private registry + with DockerContainer(f"{registry_url}/{image}:{tag}") as test_container: + wait_container_is_ready(test_container) + + # cleanup + client.images.remove(f"{registry_url}/{image}:{tag}") + client.close() From 111bd094428b83233d7eca693d94e10b34ee8ae8 Mon Sep 17 00:00:00 2001 From: Sebastian Scholz <328418+sebassz@users.noreply.github.com> Date: Mon, 27 May 2024 22:50:31 +0200 Subject: [PATCH 376/425] fix(keycloak): realm import (#584) With the option to import keycloak realms, introduced with #565, the[_configure()](https://github.com/testcontainers/testcontainers-python/blob/78b6f0ecb15e8cba687eb4588c5ce19ca32208bc/modules/keycloak/testcontainers/keycloak/__init__.py#L57) method is called twice. Once it is called in the [start()](https://github.com/testcontainers/testcontainers-python/blob/78b6f0ecb15e8cba687eb4588c5ce19ca32208bc/modules/keycloak/testcontainers/keycloak/__init__.py#L83) method of keycloak itself and then it is called a second time in the [start()](https://github.com/testcontainers/testcontainers-python/blob/78b6f0ecb15e8cba687eb4588c5ce19ca32208bc/core/testcontainers/core/container.py#L90) method of DockerContainer. This wasn't an issue so far. But if a realm shall be imported (self.has_realm_import in keycloak is True), then every time the string " --import-realm" is added to the start command in the _configure() method. The keycloak container won't start if "--import-realm" is specified multiple times. This is probably the easiest solution to solve the issue. If wished, I can also work on a more robust solution, e.g. by storing the start command in a list and checking that "--import-realm" is only added once. Co-authored-by: Sebastian Scholz --- modules/keycloak/testcontainers/keycloak/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index 27b6b20d1..6addf09ab 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -80,7 +80,6 @@ def _readiness_probe(self) -> None: wait_for_logs(self, "Added user .* to realm .*") def start(self) -> "KeycloakContainer": - self._configure() super().start() self._readiness_probe() return self From 8917772d8c90d26086af3b9606657c95928e2b9d Mon Sep 17 00:00:00 2001 From: David Ankin Date: Fri, 31 May 2024 05:18:35 -0400 Subject: [PATCH 377/425] fix(k3s): add configuration parameter for disabling cgroup mount to avoid "unable to apply cgroup configuration" (#592) relates to #591 --- modules/k3s/testcontainers/k3s/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/k3s/testcontainers/k3s/__init__.py b/modules/k3s/testcontainers/k3s/__init__.py index 2682df356..6e5354175 100644 --- a/modules/k3s/testcontainers/k3s/__init__.py +++ b/modules/k3s/testcontainers/k3s/__init__.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from testcontainers.core.config import testcontainers_config from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -37,13 +39,16 @@ class K3SContainer(DockerContainer): KUBE_SECURE_PORT = 6443 RANCHER_WEBHOOK_PORT = 8443 - def __init__(self, image="rancher/k3s:latest", **kwargs) -> None: + def __init__(self, image="rancher/k3s:latest", enable_cgroup_mount=True, **kwargs) -> None: super().__init__(image, **kwargs) self.with_exposed_ports(self.KUBE_SECURE_PORT, self.RANCHER_WEBHOOK_PORT) self.with_env("K3S_URL", f"https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}") self.with_command("server --disable traefik --tls-san=" + self.get_container_host_ip()) self.with_kwargs(privileged=True, tmpfs={"/run": "", "/var/run": ""}) - self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw") + if enable_cgroup_mount: + self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw") + else: + logging.warning("'enable_cgroup_mount' is experimental, see testcontainers/testcontainers-python#591)") def _connect(self) -> None: wait_for_logs(self, predicate="Node controller sync successful", timeout=testcontainers_config.timeout) From a95af7ddab9de5d375031a9071d6a680e47ba981 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 05:19:09 -0400 Subject: [PATCH 378/425] chore(main): release testcontainers 4.5.1 (#586) :robot: I have created a release *beep* *boop* --- ## [4.5.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.5.0...testcontainers-v4.5.1) (2024-05-31) ### Bug Fixes * **k3s:** add configuration parameter for disabling cgroup mount to avoid "unable to apply cgroup configuration" ([#592](https://github.com/testcontainers/testcontainers-python/issues/592)) ([8917772](https://github.com/testcontainers/testcontainers-python/commit/8917772d8c90d26086af3b9606657c95928e2b9d)) * **keycloak:** realm import ([#584](https://github.com/testcontainers/testcontainers-python/issues/584)) ([111bd09](https://github.com/testcontainers/testcontainers-python/commit/111bd094428b83233d7eca693d94e10b34ee8ae8)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 49bcca2a8..616660195 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.5.0" + ".": "4.5.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0f003de..6d50cb9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [4.5.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.5.0...testcontainers-v4.5.1) (2024-05-31) + + +### Bug Fixes + +* **k3s:** add configuration parameter for disabling cgroup mount to avoid "unable to apply cgroup configuration" ([#592](https://github.com/testcontainers/testcontainers-python/issues/592)) ([8917772](https://github.com/testcontainers/testcontainers-python/commit/8917772d8c90d26086af3b9606657c95928e2b9d)) +* **keycloak:** realm import ([#584](https://github.com/testcontainers/testcontainers-python/issues/584)) ([111bd09](https://github.com/testcontainers/testcontainers-python/commit/111bd094428b83233d7eca693d94e10b34ee8ae8)) + ## [4.5.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.4.1...testcontainers-v4.5.0) (2024-05-25) diff --git a/pyproject.toml b/pyproject.toml index cbf650bea..6bc84171e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.5.0" # auto-incremented by release-please +version = "4.5.1" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 54c88cf00ad7bb08eb7894c52bed7a9010fd7786 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Fri, 31 May 2024 21:51:57 +0300 Subject: [PATCH 379/425] feat(core): Image build (Dockerfile support) (#585) As part of the effort described, detailed and presented on #559 (Providing the implementation for #83 - Docker file support and more) This is the first PR (out of 4) that should provide all the groundwork to support image build. This would allow users to use custom images: ```python with DockerImage(path=".") as image: with DockerContainer(str(image)) as container: # Test something with/on custom image ``` Next in line is: `feat(core): Added SrvContainer` And later on: `feat(core): Added FastAPI module` `feat(core): Added AWS Lambda module` (all of the above can be overviewed on #559) --- core/README.rst | 14 +++ core/testcontainers/core/docker_client.py | 21 +++++ core/testcontainers/core/image.py | 88 +++++++++++++++++++ core/tests/conftest.py | 22 +++++ .../{ => image_fixtures/busybox}/Dockerfile | 0 core/tests/image_fixtures/sample/Dockerfile | 2 + core/tests/test_core.py | 33 +++++++ core/tests/test_docker_client.py | 10 +++ 8 files changed, 190 insertions(+) create mode 100644 core/testcontainers/core/image.py create mode 100644 core/tests/conftest.py rename core/tests/{ => image_fixtures/busybox}/Dockerfile (100%) create mode 100644 core/tests/image_fixtures/sample/Dockerfile diff --git a/core/README.rst b/core/README.rst index 2256bd204..bdc46db6d 100644 --- a/core/README.rst +++ b/core/README.rst @@ -4,3 +4,17 @@ testcontainers-core :code:`testcontainers-core` is the core functionality for spinning up Docker containers in test environments. .. autoclass:: testcontainers.core.container.DockerContainer + +.. autoclass:: testcontainers.core.image.DockerImage + +Using `DockerContainer` and `DockerImage` directly: + +.. doctest:: + + >>> from testcontainers.core.container import DockerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path="./core/tests/image_fixtures/sample/", tag="test-sample:latest") as image: + ... with DockerContainer(str(image)) as container: + ... delay = wait_for_logs(container, "Test Sample Image") diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 485adb594..00534c3e9 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -16,10 +16,12 @@ import os import urllib import urllib.parse +from collections.abc import Iterable from typing import Callable, Optional, TypeVar, Union import docker from docker.models.containers import Container, ContainerCollection +from docker.models.images import Image, ImageCollection from typing_extensions import ParamSpec from testcontainers.core.config import testcontainers_config as c @@ -40,6 +42,14 @@ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: return wrapper +def _wrapped_image_collection(function: Callable[_P, _T]) -> Callable[_P, _T]: + @ft.wraps(ImageCollection.build) + def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: + return function(*args, **kwargs) + + return wrapper + + class DockerClient: """ Thin wrapper around :class:`docker.DockerClient` for a more functional interface. @@ -94,6 +104,17 @@ def run( ) return container + @_wrapped_image_collection + def build(self, path: str, tag: str, rm: bool = True, **kwargs) -> tuple[Image, Iterable[dict]]: + """ + Build a Docker image from a directory containing the Dockerfile. + + :return: A tuple containing the image object and the build logs. + """ + image_object, image_logs = self.client.images.build(path=path, tag=tag, rm=rm, **kwargs) + + return image_object, image_logs + def find_host_network(self) -> Optional[str]: """ Try to find the docker host network. diff --git a/core/testcontainers/core/image.py b/core/testcontainers/core/image.py new file mode 100644 index 000000000..399200bf8 --- /dev/null +++ b/core/testcontainers/core/image.py @@ -0,0 +1,88 @@ +from typing import TYPE_CHECKING, Optional + +from typing_extensions import Self + +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.utils import setup_logger + +if TYPE_CHECKING: + from docker.models.containers import Image + +logger = setup_logger(__name__) + + +class DockerImage: + """ + Basic image object to build Docker images. + + .. doctest:: + + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path="./core/tests/image_fixtures/sample/", tag="test-image") as image: + ... logs = image.get_logs() + + :param tag: Tag for the image to be built (default: None) + :param path: Path to the Dockerfile to build the image + """ + + def __init__( + self, + path: str, + docker_client_kw: Optional[dict] = None, + tag: Optional[str] = None, + clean_up: bool = True, + **kwargs, + ) -> None: + self.tag = tag + self.path = path + self.id = None + self._docker = DockerClient(**(docker_client_kw or {})) + self.clean_up = clean_up + self._kwargs = kwargs + + def build(self, **kwargs) -> Self: + logger.info(f"Building image from {self.path}") + docker_client = self.get_docker_client() + self._image, self._logs = docker_client.build(path=self.path, tag=self.tag, **kwargs) + logger.info(f"Built image {self.short_id} with tag {self.tag}") + return self + + @property + def short_id(self) -> str: + """ + The ID of the image truncated to 12 characters, without the ``sha256:`` prefix. + """ + if self._image.id.startswith("sha256:"): + return self._image.id.split(":")[1][:12] + return self._image.id[:12] + + def remove(self, force=True, noprune=False) -> None: + """ + Remove the image. + + :param force: Remove the image even if it is in use + :param noprune: Do not delete untagged parent images + """ + if self._image and self.clean_up: + logger.info(f"Removing image {self.short_id}") + self._image.remove(force=force, noprune=noprune) + self.get_docker_client().client.close() + + def __str__(self) -> str: + return f"{self.tag if self.tag else self.short_id}" + + def __enter__(self) -> Self: + return self.build() + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.remove() + + def get_wrapped_image(self) -> "Image": + return self._image + + def get_docker_client(self) -> DockerClient: + return self._docker + + def get_logs(self) -> list[dict]: + return list(self._logs) diff --git a/core/tests/conftest.py b/core/tests/conftest.py new file mode 100644 index 000000000..4f69565f4 --- /dev/null +++ b/core/tests/conftest.py @@ -0,0 +1,22 @@ +import pytest +from typing import Callable +from testcontainers.core.container import DockerClient + + +@pytest.fixture +def check_for_image() -> Callable[[str, bool], None]: + """Warp the check_for_image function in a fixture""" + + def _check_for_image(image_short_id: str, cleaned: bool) -> None: + """ + Validates if the image is present or not. + + :param image_short_id: The short id of the image + :param cleaned: True if the image should not be present, False otherwise + """ + client = DockerClient() + images = client.client.images.list() + found = any(image.short_id.endswith(image_short_id) for image in images) + assert found is not cleaned, f'Image {image_short_id} was {"found" if cleaned else "not found"}' + + return _check_for_image diff --git a/core/tests/Dockerfile b/core/tests/image_fixtures/busybox/Dockerfile similarity index 100% rename from core/tests/Dockerfile rename to core/tests/image_fixtures/busybox/Dockerfile diff --git a/core/tests/image_fixtures/sample/Dockerfile b/core/tests/image_fixtures/sample/Dockerfile new file mode 100644 index 000000000..d7d786035 --- /dev/null +++ b/core/tests/image_fixtures/sample/Dockerfile @@ -0,0 +1,2 @@ +FROM alpine:latest +CMD echo "Test Sample Image" diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 4ebe90409..efac8262e 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -1,6 +1,11 @@ import pytest +import tempfile +import random + +from typing import Optional from testcontainers.core.container import DockerContainer +from testcontainers.core.image import DockerImage from testcontainers.core.waiting_utils import wait_for_logs @@ -31,3 +36,31 @@ def test_can_get_logs(): assert isinstance(stdout, bytes) assert isinstance(stderr, bytes) assert stdout, "There should be something on stdout" + + +@pytest.mark.parametrize("test_cleanup", [True, False]) +@pytest.mark.parametrize("test_image_tag", [None, "test-image:latest"]) +def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_for_image): + with tempfile.TemporaryDirectory() as temp_directory: + # It's important to use a random string to avoid image caching + random_string = "Hello from Docker Image! " + str(random.randint(0, 1000)) + with open(f"{temp_directory}/Dockerfile", "w") as f: + f.write( + f""" + FROM alpine:latest + CMD echo "{random_string}" + """ + ) + with DockerImage(path=temp_directory, tag=test_image_tag, clean_up=test_cleanup) as image: + image_short_id = image.short_id + assert image.tag is test_image_tag, f"Expected {test_image_tag}, got {image.tag}" + assert image.short_id is not None, "Short ID should not be None" + logs = image.get_logs() + assert isinstance(logs, list), "Logs should be a list" + assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"} + assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'} + with DockerContainer(str(image)) as container: + assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch" + assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch" + + check_for_image(image_short_id, test_cleanup) diff --git a/core/tests/test_docker_client.py b/core/tests/test_docker_client.py index cfd95be91..9234d3062 100644 --- a/core/tests/test_docker_client.py +++ b/core/tests/test_docker_client.py @@ -9,6 +9,7 @@ from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient from testcontainers.core.utils import parse_docker_auth_config +from testcontainers.core.image import DockerImage def test_docker_client_from_env(): @@ -54,3 +55,12 @@ def test_container_docker_client_kw(): DockerContainer(image="", docker_client_kw=test_kwargs) mock_docker.from_env.assert_called_with(**test_kwargs) + + +def test_image_docker_client_kw(): + test_kwargs = {"test_kw": "test_value"} + mock_docker = MagicMock(spec=docker) + with patch("testcontainers.core.docker_client.docker", mock_docker): + DockerImage(name="", path="", docker_client_kw=test_kwargs) + + mock_docker.from_env.assert_called_with(**test_kwargs) From 9045c0aea6029283490c89aea985e625dcdfc7b9 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Thu, 6 Jun 2024 00:18:21 +0300 Subject: [PATCH 380/425] docs(main): Private registry (#598) Following #566 - Private registry, adding the relevant doc so the usage will be clear and (hopefully) reachable. --- index.rst | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/index.rst b/index.rst index 3c7fcc140..a9dca6df8 100644 --- a/index.rst +++ b/index.rst @@ -103,20 +103,43 @@ When trying to launch a testcontainer from within a Docker container, e.g., in c 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. +Private Docker registry +----------------------- + +Using a private docker registry requires the `DOCKER_AUTH_CONFIG` environment variable to be set. `official documentation ` + +The value of this variable should be a JSON string containing the authentication information for the registry. + +In order to generate the JSON string, you can use the following command: +``echo -n '{"auths": {"": {"auth": "'$(echo -n ":" | base64 -w 0)'"}}}'`` + +Example: +``DOCKER_AUTH_CONFIG='{"auths": {"https://myregistry.com": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="}}}'`` + +Fetching passwords from cloud providers: + +* ``ECR_PASSWORD = $(aws ecr get-login-password --region eu-west-1)`` +* ``GCP_PASSWORD = $(gcloud auth print-access-token)`` +* ``AZURE_PASSWORD = $(az acr login --name --expose-token --output tsv)`` + + + Configuration ------------- -+-------------------------------------------+-------------------------------+------------------------------------------+ -| Env Variable | Example | Description | -+===========================================+===============================+==========================================+ -| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | -+-------------------------------------------+-------------------------------+------------------------------------------+ -| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | -+-------------------------------------------+-------------------------------+------------------------------------------+ -| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | -+-------------------------------------------+-------------------------------+------------------------------------------+ -| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | -+-------------------------------------------+-------------------------------+------------------------------------------+ ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| Env Variable | Example | Description | ++===========================================+===================================================+==========================================+ +| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``DOCKER_AUTH_CONFIG`` | ``{"auths": {"": {"auth": ""}}}`` | Custom registry auth config | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ Development and Contributing ---------------------------- From 54822de4f918db0b6857af28f324ff91bc45e8e1 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Thu, 6 Jun 2024 00:23:15 +0300 Subject: [PATCH 381/425] chore(core): Display status on readme (#589) Make the Readme display some more details regarding the project: - using ruff (just a nice reference) - pypi version (good visibility) - license (good visibility) - supported python versions (good visibility) - code coverage reporting (main driver for this PR) ![image](https://github.com/testcontainers/testcontainers-python/assets/7189138/1e8897b6-15eb-47eb-a1e7-42caa7db1eca) Relates to #544 - export code coverage (e.g. to codecov) --- .github/workflows/ci-core.yml | 31 ++++++++++++++++++++++++++++++- Makefile | 7 +++++++ README.md | 7 +++++++ pyproject.toml | 2 +- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index f794f98e3..0f6a5e4e2 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -9,7 +9,7 @@ on: branches: [main] jobs: - test: + run-tests-and-coverage: runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -27,5 +27,34 @@ jobs: run: poetry build && poetry run twine check dist/*.tar.gz - name: Run tests run: make core/tests + - name: Rename coverage file + run: mv .coverage .coverage.${{ matrix.python-version}} + - name: "Save coverage artifact" + uses: actions/upload-artifact@v4 + with: + name: "coverage-artifact-${{ matrix.python-version}}" + path: ".coverage.*" + retention-days: 1 - name: Run doctests run: make core/doctests + + coverage-compile: + needs: "run-tests-and-coverage" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: ./.github/actions/setup-env + - name: Install Python dependencies + run: poetry install --all-extras + - name: "Download coverage artifacts" + uses: actions/download-artifact@v4 + with: + pattern: "coverage-artifact-*" + merge-multiple: true + - name: Compile coverage + run: make coverage + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/Makefile b/Makefile index 1816f64b9..b7bf2826b 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,13 @@ tests : ${TESTS} ${TESTS} : %/tests : poetry run pytest -v --cov=testcontainers.$* $*/tests +# Target to combine and report coverage. +coverage: + poetry run coverage combine + poetry run coverage report + poetry run coverage xml + poetry run coverage html + # Target to lint the code. lint: pre-commit run -a diff --git a/README.md b/README.md index 036723d61..434d0698a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![image](https://img.shields.io/pypi/v/testcontainers.svg)](https://pypi.python.org/pypi/testcontainers) +[![image](https://img.shields.io/pypi/l/testcontainers.svg)](https://github.com/testcontainers/testcontainers-python/blob/main/LICENSE) +[![image](https://img.shields.io/pypi/pyversions/testcontainers.svg)](https://pypi.python.org/pypi/testcontainers) +[![codecov](https://codecov.io/gh/testcontainers/testcontainers-python/branch/master/graph/badge.svg)](https://codecov.io/gh/testcontainers/testcontainers-python) + + # Testcontainers Python `testcontainers-python` facilitates the use of Docker containers for functional and integration testing. diff --git a/pyproject.toml b/pyproject.toml index 6bc84171e..181a12a64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,7 +159,7 @@ priority = "primary" line-length = 120 [tool.pytest.ini_options] -addopts = "--cov-report=term --cov-report=html --tb=short --strict-markers" +addopts = "--tb=short --strict-markers" log_cli = true log_cli_level = "INFO" From 2a5a1904391020a9da4be17b32f23b36d9385c29 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Fri, 7 Jun 2024 10:46:23 -0400 Subject: [PATCH 382/425] fix: move TESTCONTAINERS_HOST_OVERRIDE to config.py (#603) fix #602 --- core/testcontainers/core/config.py | 6 ++++++ core/testcontainers/core/docker_client.py | 8 ++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 5e038b451..34b8177a2 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -14,6 +14,7 @@ RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true" RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock") RYUK_RECONNECTION_TIMEOUT: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s") +TC_HOST_OVERRIDE: Optional[str] = environ.get("TC_HOST", environ.get("TESTCONTAINERS_HOST_OVERRIDE")) TC_FILE = ".testcontainers.properties" TC_GLOBAL = Path.home() / TC_FILE @@ -52,6 +53,11 @@ class TestcontainersConfiguration: ryuk_reconnection_timeout: str = RYUK_RECONNECTION_TIMEOUT tc_properties: dict[str, str] = field(default_factory=read_tc_properties) _docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG")) + tc_host_override: Optional[str] = TC_HOST_OVERRIDE + """ + https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 + if os env TC_HOST is set, use it + """ @property def docker_auth_config(self): diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 00534c3e9..9b7fe7479 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -187,18 +187,14 @@ def host(self) -> str: """ Get the hostname or ip address of the docker host. """ - # https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 - # if os env TC_HOST is set, use it - host = os.environ.get("TC_HOST") - if not host: - host = os.environ.get("TESTCONTAINERS_HOST_OVERRIDE") + host = c.tc_host_override if host: return host try: url = urllib.parse.urlparse(self.client.api.base_url) except ValueError: - return None + return "localhost" if "http" in url.scheme or "tcp" in url.scheme: return url.hostname if inside_container() and ("unix" in url.scheme or "npipe" in url.scheme): From f5a019b6d2552788478e4a10cd17f7a2b453abb9 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Sat, 8 Jun 2024 03:40:21 +0300 Subject: [PATCH 383/425] docs: Update private registry instructions (#604) Fix some issues with the private registry instructions: - issue with the link to official documentation - convert all relevant blocks to code-block - fix some typos --- index.rst | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/index.rst b/index.rst index a9dca6df8..9eb58cf48 100644 --- a/index.rst +++ b/index.rst @@ -106,22 +106,30 @@ When trying to launch a testcontainer from within a Docker container, e.g., in c Private Docker registry ----------------------- -Using a private docker registry requires the `DOCKER_AUTH_CONFIG` environment variable to be set. `official documentation ` +Using a private docker registry requires the `DOCKER_AUTH_CONFIG` environment variable to be set. +`official documentation `_ The value of this variable should be a JSON string containing the authentication information for the registry. +Example: + +.. code-block:: bash + + DOCKER_AUTH_CONFIG='{"auths": {"https://myregistry.com": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="}}}' + In order to generate the JSON string, you can use the following command: -``echo -n '{"auths": {"": {"auth": "'$(echo -n ":" | base64 -w 0)'"}}}'`` -Example: -``DOCKER_AUTH_CONFIG='{"auths": {"https://myregistry.com": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="}}}'`` +.. code-block:: bash + + echo -n '{"auths": {"": {"auth": "'$(echo -n ":" | base64 -w 0)'"}}}' Fetching passwords from cloud providers: -* ``ECR_PASSWORD = $(aws ecr get-login-password --region eu-west-1)`` -* ``GCP_PASSWORD = $(gcloud auth print-access-token)`` -* ``AZURE_PASSWORD = $(az acr login --name --expose-token --output tsv)`` +.. code-block:: bash + ECR_PASSWORD = $(aws ecr get-login-password --region eu-west-1) + GCP_PASSWORD = $(gcloud auth print-access-token) + AZURE_PASSWORD = $(az acr login --name --expose-token --output tsv) Configuration From ec76df27c3d95ac1b79df3a049b4e2c12539081d Mon Sep 17 00:00:00 2001 From: Ivan Belyaev Date: Thu, 13 Jun 2024 23:15:33 +0300 Subject: [PATCH 384/425] fix: Container for Milvus database (#606) I use this wonderful package for writing tests, but I did not find a container for [Milvus vector database](https://milvus.io/docs) Please check, I'm ready to correct comments --------- Co-authored-by: ivan Co-authored-by: David Ankin --- index.rst | 1 + modules/milvus/README.rst | 2 + .../milvus/testcontainers/milvus/__init__.py | 85 ++++++ modules/milvus/tests/test_milvus.py | 39 +++ poetry.lock | 264 +++++++++++++++++- pyproject.toml | 3 + 6 files changed, 388 insertions(+), 6 deletions(-) create mode 100644 modules/milvus/README.rst create mode 100644 modules/milvus/testcontainers/milvus/__init__.py create mode 100644 modules/milvus/tests/test_milvus.py diff --git a/index.rst b/index.rst index 9eb58cf48..45bc33806 100644 --- a/index.rst +++ b/index.rst @@ -28,6 +28,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/keycloak/README modules/localstack/README modules/memcached/README + modules/milvus/README modules/minio/README modules/mongodb/README modules/mssql/README diff --git a/modules/milvus/README.rst b/modules/milvus/README.rst new file mode 100644 index 000000000..f823d7fe9 --- /dev/null +++ b/modules/milvus/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.milvus.MilvusContainer +.. title:: testcontainers.milvus.MilvusContainer diff --git a/modules/milvus/testcontainers/milvus/__init__.py b/modules/milvus/testcontainers/milvus/__init__.py new file mode 100644 index 000000000..39a1403e9 --- /dev/null +++ b/modules/milvus/testcontainers/milvus/__init__.py @@ -0,0 +1,85 @@ +# +# 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 requests + +from testcontainers.core.config import testcontainers_config as c +from testcontainers.core.generic import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class MilvusContainer(DockerContainer): + """ + Milvus database container. + + Read mode about Milvus: https://milvus.io/docs + + Example: + + The example spins up a Milvus database and connects to it client using MilvisClient. + + .. doctest:: + + >>> from testcontainers.milvus import MilvusContainer + >>> with MilvusContainer("milvusdb/milvus:v2.4.4") as milvus_container: + ... milvus_container.get_exposed_port(milvus_container.port) in milvus_container.get_connection_url() + True + """ + + def __init__( + self, + image: str = "milvusdb/milvus:latest", + port: int = 19530, + **kwargs, + ) -> None: + super().__init__(image=image, **kwargs) + self.port = port + self.healthcheck_port = 9091 + self.with_exposed_ports(self.port, self.healthcheck_port) + self.cmd = "milvus run standalone" + + envs = {"ETCD_USE_EMBED": "true", "ETCD_DATA_DIR": "/var/lib/milvus/etcd", "COMMON_STORAGETYPE": "local"} + + for env, value in envs.items(): + self.with_env(env, value) + + def get_connection_url(self) -> str: + ip = self.get_container_host_ip() + port = self.get_exposed_port(self.port) + return f"http://{ip}:{port}" + + @wait_container_is_ready() + def _connect(self) -> None: + msg = "Welcome to use Milvus!" + wait_for_logs(self, f".*{msg}.*", c.max_tries, c.sleep_time) + self._healthcheck() + + def _get_healthcheck_url(self) -> str: + ip = self.get_container_host_ip() + port = self.get_exposed_port(self.healthcheck_port) + return f"http://{ip}:{port}" + + @wait_container_is_ready(requests.exceptions.HTTPError) + def _healthcheck(self) -> None: + healthcheck_url = self._get_healthcheck_url() + response = requests.get(f"{healthcheck_url}/healthz", timeout=1) + response.raise_for_status() + + def start(self) -> "MilvusContainer": + """This method starts the Milvus container and runs the healthcheck + to verify that the container is ready to use.""" + self.with_command(self.cmd) + super().start() + self._connect() + self._healthcheck() + return self diff --git a/modules/milvus/tests/test_milvus.py b/modules/milvus/tests/test_milvus.py new file mode 100644 index 000000000..12887a49b --- /dev/null +++ b/modules/milvus/tests/test_milvus.py @@ -0,0 +1,39 @@ +import pytest +from pymilvus import MilvusClient + +from testcontainers.milvus import MilvusContainer + +VERSIONS = ["v2.4.0", "v2.4.4"] + + +class ClientMilvusContainer(MilvusContainer): + def get_client(self, *, dbname: str = "default", token: str = "root:Milvus") -> MilvusClient: + connection_url = self.get_connection_url() + client = MilvusClient(uri=connection_url, dbname=dbname, token=token) + return client + + +@pytest.mark.parametrize("version", VERSIONS) +def test_run_milvus_success(version: str): + image = f"milvusdb/milvus:{version}" + + with MilvusContainer(image=image) as milvus_container: + exposed_port = milvus_container.get_exposed_port(milvus_container.port) + url = milvus_container.get_connection_url() + + assert url and exposed_port in url + + +@pytest.mark.parametrize("version", VERSIONS) +def test_milvus_client_success(version: str): + image = f"milvusdb/milvus:{version}" + test_collection = "test_collection" + + with ClientMilvusContainer(image=image) as milvus_container: + client = milvus_container.get_client() + client.create_collection(test_collection, dimension=2) + collections = client.list_collections() + assert test_collection in collections + + client.drop_collection(test_collection) + assert not client.has_collection(test_collection) diff --git a/poetry.lock b/poetry.lock index 272b0b238..4f07c50b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -931,6 +931,27 @@ files = [ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] +[[package]] +name = "environs" +version = "9.5.0" +description = "simplified environment variable parsing" +optional = false +python-versions = ">=3.6" +files = [ + {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, + {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, +] + +[package.dependencies] +marshmallow = ">=3.0.0" +python-dotenv = "*" + +[package.extras] +dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] +django = ["dj-database-url", "dj-email-url", "django-cache-url"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] +tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -1205,7 +1226,7 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 name = "grpcio" version = "1.62.1" description = "HTTP/2-based RPC framework" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "grpcio-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:179bee6f5ed7b5f618844f760b6acf7e910988de77a4f75b95bbfaa8106f3c1e"}, @@ -1838,6 +1859,25 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "marshmallow" +version = "3.21.3" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mdurl" version = "0.1.2" @@ -1849,6 +1889,18 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "milvus-lite" +version = "2.4.7" +description = "A lightweight version of Milvus wrapped with Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, + {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, + {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, +] + [[package]] name = "minio" version = "7.2.5" @@ -2088,7 +2140,7 @@ setuptools = "*" name = "numpy" version = "1.26.4" description = "Fundamental package for array computing in Python" -optional = true +optional = false python-versions = ">=3.9" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, @@ -2387,6 +2439,79 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pandas" +version = "2.2.2" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pg8000" version = "1.30.5" @@ -2543,7 +2668,7 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "4.25.3" description = "" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, @@ -2882,6 +3007,31 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pymilvus" +version = "2.4.3" +description = "Python Sdk for Milvus" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pymilvus-2.4.3-py3-none-any.whl", hash = "sha256:38239e89f8d739f665141d0b80908990b5f59681e889e135c234a4a45669a5c8"}, + {file = "pymilvus-2.4.3.tar.gz", hash = "sha256:703ac29296cdce03d6dc2aaebbe959e57745c141a94150e371dc36c61c226cc1"}, +] + +[package.dependencies] +environs = "<=9.5.0" +grpcio = ">=1.49.1,<=1.63.0" +milvus-lite = ">=2.4.0,<2.5.0" +pandas = ">=1.2.4" +protobuf = ">=3.20.0" +setuptools = ">=67" +ujson = ">=2.0.0" + +[package.extras] +bulk-writer = ["azure-storage-blob", "minio (>=7.0.0)", "pyarrow (>=12.0.0)", "requests"] +dev = ["black", "grpcio (==1.62.2)", "grpcio-testing (==1.62.2)", "grpcio-tools (==1.62.2)", "pytest (>=5.3.4)", "pytest-cov (>=2.8.1)", "pytest-timeout (>=1.3.4)", "ruff (>0.4.0)"] +model = ["milvus-model (>=0.1.0)"] + [[package]] name = "pymongo" version = "4.6.2" @@ -3187,6 +3337,20 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "python-keycloak" version = "3.9.1" @@ -3211,7 +3375,7 @@ docs = ["Sphinx (>=6.1.0,<7.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (> name = "pytz" version = "2024.1" description = "World timezone definitions, modern and historical" -optional = true +optional = false python-versions = "*" files = [ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, @@ -3950,6 +4114,93 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +[[package]] +name = "ujson" +version = "5.10.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, + {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, + {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"}, + {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"}, + {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"}, + {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"}, + {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"}, + {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"}, + {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"}, + {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"}, + {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"}, + {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"}, + {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"}, + {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"}, + {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"}, + {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"}, + {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"}, + {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"}, + {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"}, + {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"}, + {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"}, + {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"}, + {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"}, + {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"}, + {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"}, + {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"}, + {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"}, + {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"}, + {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"}, + {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"}, + {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"}, + {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"}, + {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"}, + {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"}, + {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"}, + {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"}, + {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"}, + {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"}, + {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"}, + {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"}, + {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"}, + {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"}, + {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"}, + {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"}, + {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"}, + {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"}, + {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"}, + {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"}, + {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"}, + {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"}, + {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"}, + {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"}, + {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"}, + {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"}, + {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"}, + {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"}, + {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"}, + {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"}, + {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"}, + {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"}, + {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"}, +] + [[package]] name = "urllib3" version = "1.26.18" @@ -4190,6 +4441,7 @@ kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] memcached = [] +milvus = [] minio = ["minio"] mongodb = ["pymongo"] mssql = ["pymssql", "sqlalchemy"] @@ -4212,4 +4464,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "95a2e0ef23d8dfb1cbc74d72f534028aeff5da8bc26cc194f464f6fe282ba38f" +content-hash = "fd8fb814c5b61f11a31e35b34030bb23fb19b935fe49f4941fbd58c7c8859cb1" diff --git a/pyproject.toml b/pyproject.toml index 181a12a64..baaeb24dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ packages = [ { include = "testcontainers", from = "modules/localstack" }, { include = "testcontainers", from = "modules/memcached" }, { include = "testcontainers", from = "modules/minio" }, + { include = "testcontainers", from = "modules/milvus" }, { include = "testcontainers", from = "modules/mongodb" }, { include = "testcontainers", from = "modules/mssql" }, { include = "testcontainers", from = "modules/mysql" }, @@ -114,6 +115,7 @@ keycloak = ["python-keycloak"] localstack = ["boto3"] memcached = [] minio = ["minio"] +milvus = [] mongodb = ["pymongo"] mssql = ["sqlalchemy", "pymssql"] mysql = ["sqlalchemy", "pymysql"] @@ -150,6 +152,7 @@ cassandra-driver = "*" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" hvac = "*" +pymilvus = "2.4.3" [[tool.poetry.source]] name = "PyPI" From 59cb6fc4e7d93870ff2d0d961d14ccd5142a8a05 Mon Sep 17 00:00:00 2001 From: Francesco Montorsi Date: Tue, 18 Jun 2024 10:57:32 +0200 Subject: [PATCH 385/425] fix(mqtt): Add mqtt.MosquittoContainer (#568) (#599) This PR is adding a new MosquittoContainer class that helps creating integration tests for MQTT clients. The MosquittoContainer class contains a bunch of methods to help with testing: * checking number of messages received * watching topics * check last payload published on a particular topic * etc This PR lacks tests. I can add them if there is interest in this PR... --------- Co-authored-by: Dave Ankin --- index.rst | 1 + modules/mqtt/README.rst | 2 + modules/mqtt/testcontainers/mqtt/__init__.py | 155 ++++++++++++++++++ ...iners-mosquitto-default-configuration.conf | 20 +++ modules/mqtt/tests/test_mosquitto.py | 18 ++ poetry.lock | 17 +- pyproject.toml | 3 + 7 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 modules/mqtt/README.rst create mode 100644 modules/mqtt/testcontainers/mqtt/__init__.py create mode 100644 modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf create mode 100644 modules/mqtt/tests/test_mosquitto.py diff --git a/index.rst b/index.rst index 45bc33806..7bf056799 100644 --- a/index.rst +++ b/index.rst @@ -31,6 +31,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/milvus/README modules/minio/README modules/mongodb/README + modules/mqtt/README modules/mssql/README modules/mysql/README modules/nats/README diff --git a/modules/mqtt/README.rst b/modules/mqtt/README.rst new file mode 100644 index 000000000..2e088cbbb --- /dev/null +++ b/modules/mqtt/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.mqtt.MosquittoContainer +.. title:: testcontainers.mqtt.MosquittoContainer diff --git a/modules/mqtt/testcontainers/mqtt/__init__.py b/modules/mqtt/testcontainers/mqtt/__init__.py new file mode 100644 index 000000000..1382762ae --- /dev/null +++ b/modules/mqtt/testcontainers/mqtt/__init__.py @@ -0,0 +1,155 @@ +# +# 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. + +from pathlib import Path +from typing import TYPE_CHECKING, Optional + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + +if TYPE_CHECKING: + from paho.mqtt.client import Client + from paho.mqtt.enums import MQTTErrorCode + + +class MosquittoContainer(DockerContainer): + """ + Specialization of DockerContainer for MQTT broker Mosquitto. + Example: + + .. doctest:: + + >>> from testcontainers.mqtt import MosquittoContainer + + >>> with MosquittoContainer() as mosquitto_broker: + ... mqtt_client = mosquitto_broker.get_client() + """ + + TESTCONTAINERS_CLIENT_ID = "TESTCONTAINERS-CLIENT" + MQTT_PORT = 1883 + CONFIG_FILE = "testcontainers-mosquitto-default-configuration.conf" + + def __init__( + self, + image: str = "eclipse-mosquitto:latest", + # password: Optional[str] = None, + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + # self.password = password + # reusable client context: + self.client: Optional["Client"] = None + + @wait_container_is_ready() + def get_client(self) -> "Client": + """ + Creates and connects a client, caching the result in `self.client` + returning that if it exists. + + Connection attempts are retried using `@wait_container_is_ready`. + + Returns: + a client from the paho library + """ + if self.client: + return self.client + client, err = self.new_client() + # 0 is a conventional "success" value in C, which is falsy in python + if err: + # retry, maybe it is not available yet + raise ConnectionError(f"Failed to establish a connection: {err}") + if not client.is_connected(): + raise TimeoutError("The Paho MQTT secondary thread has not connected yet!") + self.client = client + return client + + def new_client(self, **kwargs) -> tuple["Client", "MQTTErrorCode"]: + """ + Get a paho.mqtt client connected to this container. + Check the returned object is_connected() method before use + + Usage of this method is required for versions <2; + versions >=2 will wait for log messages to determine container readiness. + There is no way to pass arguments to new_client in versions <2, + please use an up-to-date version. + + Args: + **kwargs: Keyword arguments passed to `paho.mqtt.client`. + + Returns: + client: MQTT client to connect to the container. + error: an error code or MQTT_ERR_SUCCESS. + """ + try: + from paho.mqtt.client import CallbackAPIVersion, Client + from paho.mqtt.enums import MQTTErrorCode + except ImportError as i: + raise ImportError("'pip install paho-mqtt' required for MosquittoContainer.new_client") from i + + err = MQTTErrorCode.MQTT_ERR_SUCCESS + if self.client is None: + self.client = Client( + client_id=MosquittoContainer.TESTCONTAINERS_CLIENT_ID, + callback_api_version=CallbackAPIVersion.VERSION2, + userdata=self, + **kwargs, + ) + self.client._connect_timeout = 1.0 + + # connect() is a blocking call: + err = self.client.connect(self.get_container_host_ip(), int(self.get_exposed_port(self.MQTT_PORT))) + self.client.loop_start() # launch a thread to call loop() and dequeue the message + + return self.client, err + + def start(self, configfile: Optional[str] = None) -> Self: + # setup container: + self.with_exposed_ports(self.MQTT_PORT) + if configfile is None: + # default config file + configfile = Path(__file__).parent / MosquittoContainer.CONFIG_FILE + self.with_volume_mapping(configfile, "/mosquitto/config/mosquitto.conf") + # if self.password: + # # TODO: add authentication + # pass + + # do container start + super().start() + + self._wait() + return self + + def _wait(self): + if self.image.split(":")[-1].startswith("1"): + import logging + + logging.warning( + "You are using version 1 of eclipse-mosquitto which is not supported for use by this module without paho-mqtt also installed" + ) + self.get_client() + else: + wait_for_logs(self, r"mosquitto version \d+.\d+.\d+ running", timeout=30) + + def stop(self, force=True, delete_volume=True) -> None: + if self.client is not None: + self.client.disconnect() + self.client = None # force recreation of the client object at next start() + super().stop(force, delete_volume) + + def publish_message(self, topic: str, payload: str, timeout: int = 2) -> None: + ret = self.get_client().publish(topic, payload) + ret.wait_for_publish(timeout=timeout) + if not ret.is_published(): + raise RuntimeError(f"Could not publish a message on topic {topic} to Mosquitto broker: {ret}") diff --git a/modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf b/modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf new file mode 100644 index 000000000..13728cec0 --- /dev/null +++ b/modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf @@ -0,0 +1,20 @@ +# see https://mosquitto.org/man/mosquitto-conf-5.html + +protocol mqtt +user root +log_dest stdout +allow_anonymous true + +log_type error +log_type warning +log_type notice +log_type information + +log_timestamp_format %Y-%m-%d %H:%M:%S +persistence true +persistence_location /data/ + +listener 1883 +protocol mqtt + +sys_interval 1 diff --git a/modules/mqtt/tests/test_mosquitto.py b/modules/mqtt/tests/test_mosquitto.py new file mode 100644 index 000000000..63ce7fcd9 --- /dev/null +++ b/modules/mqtt/tests/test_mosquitto.py @@ -0,0 +1,18 @@ +import pytest + +from testcontainers.mqtt import MosquittoContainer + +VERSIONS = ["1.6.15", "2.0.18"] + + +@pytest.mark.parametrize("version", VERSIONS) +def test_mosquitto(version): + with MosquittoContainer(image=f"eclipse-mosquitto:{version}") as container: + external_port = int(container.get_exposed_port(container.MQTT_PORT)) + print(f"listening on port: {external_port}") + + +@pytest.mark.parametrize("version", VERSIONS) +def test_mosquitto_client(version): + with MosquittoContainer(image=f"eclipse-mosquitto:{version}") as container: + container.get_client() diff --git a/poetry.lock b/poetry.lock index 4f07c50b4..8d7db3214 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2439,6 +2439,20 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "paho-mqtt" +version = "2.1.0" +description = "MQTT version 5.0/3.1.1 client class" +optional = false +python-versions = ">=3.7" +files = [ + {file = "paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"}, + {file = "paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834"}, +] + +[package.extras] +proxy = ["pysocks"] + [[package]] name = "pandas" version = "2.2.2" @@ -4444,6 +4458,7 @@ memcached = [] milvus = [] minio = ["minio"] mongodb = ["pymongo"] +mqtt = [] mssql = ["pymssql", "sqlalchemy"] mysql = ["pymysql", "sqlalchemy"] nats = ["nats-py"] @@ -4464,4 +4479,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "fd8fb814c5b61f11a31e35b34030bb23fb19b935fe49f4941fbd58c7c8859cb1" +content-hash = "f91d5824e3c430ff0ff0256fe7b32b8c57fe2437a5dbd098f75a5d4e960f3209" diff --git a/pyproject.toml b/pyproject.toml index baaeb24dd..fe0db3320 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ packages = [ { include = "testcontainers", from = "modules/minio" }, { include = "testcontainers", from = "modules/milvus" }, { include = "testcontainers", from = "modules/mongodb" }, + { include = "testcontainers", from = "modules/mqtt" }, { include = "testcontainers", from = "modules/mssql" }, { include = "testcontainers", from = "modules/mysql" }, { include = "testcontainers", from = "modules/nats" }, @@ -117,6 +118,7 @@ memcached = [] minio = ["minio"] milvus = [] mongodb = ["pymongo"] +mqtt = [] mssql = ["sqlalchemy", "pymssql"] mysql = ["sqlalchemy", "pymysql"] nats = ["nats-py"] @@ -153,6 +155,7 @@ pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" hvac = "*" pymilvus = "2.4.3" +paho-mqtt = "2.1.0" [[tool.poetry.source]] name = "PyPI" From 076849015ad3542384ecf8cf6c205d5d498e4986 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Tue, 18 Jun 2024 12:29:55 +0300 Subject: [PATCH 386/425] feat(core): Added ServerContainer (#595) As part of the effort described, detailed and presented on #559 This is the seconds PR (out of 4) that should provide all the groundwork to support containers running a server. This would allow users to use custom images: ```python with DockerImage(path=".", tag="test:latest") as image: with ServerContainer(port=9000, image=image) as srv: # Test something with/on the server using port 9000 ``` Next in line are: `feat(core): Added FastAPI module` `feat(core): Added AWS Lambda module` --- Based on the work done on #585 Expended from issue #83 --------- Co-authored-by: David Ankin --- Makefile | 4 + core/README.rst | 16 +++- core/testcontainers/core/config.py | 5 +- core/testcontainers/core/generic.py | 73 ++++++++++++++++++- core/testcontainers/core/image.py | 10 ++- .../image_fixtures/python_server/Dockerfile | 3 + core/tests/test_generics.py | 47 ++++++++++++ index.rst | 38 ++-------- modules/index.rst | 11 +++ poetry.lock | 8 +- pyproject.toml | 19 ++--- 11 files changed, 178 insertions(+), 56 deletions(-) create mode 100644 core/tests/image_fixtures/python_server/Dockerfile create mode 100644 core/tests/test_generics.py create mode 100644 modules/index.rst diff --git a/Makefile b/Makefile index b7bf2826b..4a0594095 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,10 @@ ${TESTS_DIND} : %/tests-dind : image docs : poetry run sphinx-build -nW . docs/_build +# Target to build docs watching for changes as per https://stackoverflow.com/a/21389615 +docs-watch : + poetry run sphinx-autobuild . docs/_build # requires 'pip install sphinx-autobuild' + doctests : ${DOCTESTS} poetry run sphinx-build -b doctest . docs/_build diff --git a/core/README.rst b/core/README.rst index bdc46db6d..8479efac8 100644 --- a/core/README.rst +++ b/core/README.rst @@ -1,12 +1,10 @@ -testcontainers-core +Testcontainers Core =================== :code:`testcontainers-core` is the core functionality for spinning up Docker containers in test environments. .. autoclass:: testcontainers.core.container.DockerContainer -.. autoclass:: testcontainers.core.image.DockerImage - Using `DockerContainer` and `DockerImage` directly: .. doctest:: @@ -18,3 +16,15 @@ Using `DockerContainer` and `DockerImage` directly: >>> with DockerImage(path="./core/tests/image_fixtures/sample/", tag="test-sample:latest") as image: ... with DockerContainer(str(image)) as container: ... delay = wait_for_logs(container, "Test Sample Image") + +--- + +.. autoclass:: testcontainers.core.image.DockerImage + +--- + +.. autoclass:: testcontainers.core.generic.ServerContainer + +--- + +.. autoclass:: testcontainers.core.generic.DbContainer diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 34b8177a2..3522b91f0 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -61,9 +61,10 @@ class TestcontainersConfiguration: @property def docker_auth_config(self): - if "DOCKER_AUTH_CONFIG" in _WARNINGS: + config = self._docker_auth_config + if config and "DOCKER_AUTH_CONFIG" in _WARNINGS: warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) - return self._docker_auth_config + return config @docker_auth_config.setter def docker_auth_config(self, value: str): diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 6dd635e69..11456a515 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -10,11 +10,14 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Optional, Union +from urllib.error import HTTPError from urllib.parse import quote +from urllib.request import urlopen from testcontainers.core.container import DockerContainer from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.image import DockerImage from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -29,6 +32,8 @@ class DbContainer(DockerContainer): """ + **DEPRECATED (for removal)** + Generic database container. """ @@ -79,3 +84,69 @@ def _configure(self) -> None: def _transfer_seed(self) -> None: pass + + +class ServerContainer(DockerContainer): + """ + **DEPRECATED - will be moved from core to a module (stay tuned for a final/stable import location)** + + Container for a generic server that is based on a custom image. + + Example: + + .. doctest:: + + >>> import httpx + >>> from testcontainers.core.generic import ServerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path="./core/tests/image_fixtures/python_server", tag="test-srv:latest") as image: + ... with ServerContainer(port=9000, image=image) as srv: + ... url = srv._create_connection_url() + ... response = httpx.get(f"{url}", timeout=5) + ... assert response.status_code == 200, "Response status code is not 200" + ... delay = wait_for_logs(srv, "GET / HTTP/1.1") + + + :param path: Path to the Dockerfile to build the image + :param tag: Tag for the image to be built (default: None) + """ + + def __init__(self, port: int, image: Union[str, DockerImage]) -> None: + super().__init__(str(image)) + self.internal_port = port + self.with_exposed_ports(self.internal_port) + + @wait_container_is_ready(HTTPError) + def _connect(self) -> None: + # noinspection HttpUrlsUsage + url = self._create_connection_url() + try: + with urlopen(url) as r: + assert b"" in r.read() + except HTTPError as e: + # 404 is expected, as the server may not have the specific endpoint we are looking for + if e.code == 404: + pass + else: + raise + + def get_api_url(self) -> str: + raise NotImplementedError + + def _create_connection_url(self) -> str: + if self._container is None: + raise ContainerStartException("container has not been started") + host = self.get_container_host_ip() + exposed_port = self.get_exposed_port(self.internal_port) + url = f"http://{host}:{exposed_port}" + return url + + def start(self) -> "ServerContainer": + super().start() + self._connect() + return self + + def stop(self, force=True, delete_volume=True) -> None: + super().stop(force, delete_volume) diff --git a/core/testcontainers/core/image.py b/core/testcontainers/core/image.py index 399200bf8..4004e9e44 100644 --- a/core/testcontainers/core/image.py +++ b/core/testcontainers/core/image.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Optional +from os import PathLike +from typing import TYPE_CHECKING, Optional, Union from typing_extensions import Self @@ -28,7 +29,7 @@ class DockerImage: def __init__( self, - path: str, + path: Union[str, PathLike], docker_client_kw: Optional[dict] = None, tag: Optional[str] = None, clean_up: bool = True, @@ -36,15 +37,16 @@ def __init__( ) -> None: self.tag = tag self.path = path - self.id = None self._docker = DockerClient(**(docker_client_kw or {})) self.clean_up = clean_up self._kwargs = kwargs + self._image = None + self._logs = None def build(self, **kwargs) -> Self: logger.info(f"Building image from {self.path}") docker_client = self.get_docker_client() - self._image, self._logs = docker_client.build(path=self.path, tag=self.tag, **kwargs) + self._image, self._logs = docker_client.build(path=str(self.path), tag=self.tag, **kwargs) logger.info(f"Built image {self.short_id} with tag {self.tag}") return self diff --git a/core/tests/image_fixtures/python_server/Dockerfile b/core/tests/image_fixtures/python_server/Dockerfile new file mode 100644 index 000000000..844acf2b3 --- /dev/null +++ b/core/tests/image_fixtures/python_server/Dockerfile @@ -0,0 +1,3 @@ +FROM python:3-alpine +EXPOSE 9000 +CMD ["python", "-m", "http.server", "9000"] diff --git a/core/tests/test_generics.py b/core/tests/test_generics.py new file mode 100644 index 000000000..340ac6655 --- /dev/null +++ b/core/tests/test_generics.py @@ -0,0 +1,47 @@ +import re +from pathlib import Path +from typing import Optional + +import pytest +from httpx import get + +from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.image import DockerImage +from testcontainers.core.generic import ServerContainer + +TEST_DIR = Path(__file__).parent + + +@pytest.mark.parametrize("test_image_cleanup", [True, False]) +@pytest.mark.parametrize("test_image_tag", [None, "custom-image:test"]) +def test_srv_container(test_image_tag: Optional[str], test_image_cleanup: bool, check_for_image, port=9000): + with ( + DockerImage( + path=TEST_DIR / "image_fixtures/python_server", + tag=test_image_tag, + clean_up=test_image_cleanup, + # + ) as docker_image, + ServerContainer(port=port, image=docker_image) as srv, + ): + image_short_id = docker_image.short_id + image_build_logs = docker_image.get_logs() + # check if dict is in any of the logs + assert {"stream": f"Step 2/3 : EXPOSE {port}"} in image_build_logs, "Image logs mismatch" + assert (port, None) in srv.ports.items(), "Port mismatch" + with pytest.raises(NotImplementedError): + srv.get_api_url() + test_url = srv._create_connection_url() + assert re.match(r"http://localhost:\d+", test_url), "Connection URL mismatch" + + check_for_image(image_short_id, test_image_cleanup) + + +def test_like_doctest(): + with DockerImage(path=TEST_DIR / "image_fixtures/python_server", tag="test-srv:latest") as image: + with ServerContainer(port=9000, image=image) as srv: + url = srv._create_connection_url() + response = get(f"{url}", timeout=5) + assert response.status_code == 200, "Response status code is not 200" + delay = wait_for_logs(srv, "GET / HTTP/1.1") + print(delay) diff --git a/index.rst b/index.rst index 7bf056799..6e7ed596c 100644 --- a/index.rst +++ b/index.rst @@ -13,40 +13,10 @@ testcontainers-python testcontainers-python facilitates the use of Docker containers for functional and integration testing. The collection of packages currently supports the following features. .. toctree:: + :maxdepth: 1 core/README - modules/arangodb/README - modules/azurite/README - modules/cassandra/README - modules/chroma/README - modules/clickhouse/README - modules/elasticsearch/README - modules/google/README - modules/influxdb/README - modules/k3s/README - modules/kafka/README - modules/keycloak/README - modules/localstack/README - modules/memcached/README - modules/milvus/README - modules/minio/README - modules/mongodb/README - modules/mqtt/README - modules/mssql/README - modules/mysql/README - modules/nats/README - modules/neo4j/README - modules/nginx/README - modules/opensearch/README - modules/oracle-free/README - modules/postgres/README - modules/qdrant/README - modules/rabbitmq/README - modules/redis/README - modules/registry/README - modules/selenium/README - modules/vault/README - modules/weaviate/README + modules/index Getting Started --------------- @@ -190,4 +160,6 @@ Testcontainers is a collection of `implicit namespace packages __`. +You want to contribute a new feature or container? +Great! You can do that in six steps as outlined +`here `_. diff --git a/modules/index.rst b/modules/index.rst new file mode 100644 index 000000000..d2a67a3d4 --- /dev/null +++ b/modules/index.rst @@ -0,0 +1,11 @@ +Community Modules +================= + +.. + glob: + https://stackoverflow.com/a/44572883/4971476 + +.. toctree:: + :glob: + + */README diff --git a/poetry.lock b/poetry.lock index 8d7db3214..891c7bd75 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1391,7 +1391,7 @@ setuptools = "*" name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, @@ -1428,7 +1428,7 @@ files = [ name = "httpcore" version = "1.0.5" description = "A minimal low-level HTTP client." -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, @@ -1449,7 +1449,7 @@ trio = ["trio (>=0.22.0,<0.26.0)"] name = "httpx" version = "0.27.0" description = "The next generation HTTP client." -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, @@ -4479,4 +4479,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "f91d5824e3c430ff0ff0256fe7b32b8c57fe2437a5dbd098f75a5d4e960f3209" +content-hash = "043c7eea4ca72646a19a705891b26577a27149673ba38c8a6dd4732d30ce081c" diff --git a/pyproject.toml b/pyproject.toml index fe0db3320..afe841c82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -142,19 +142,20 @@ mypy = "1.7.1" pre-commit = "^3.6" pytest = "7.4.3" pytest-cov = "4.1.0" -sphinx = "^7.2.6" -twine = "^4.0.2" -anyio = "^4.3.0" +sphinx = "7.2.6" +twine = "4.0.2" +anyio = "4.3.0" # for tests only -psycopg2-binary = "*" -pg8000 = "*" -sqlalchemy = "*" -psycopg = "*" -cassandra-driver = "*" +psycopg2-binary = "2.9.9" +pg8000 = "1.30.5" +sqlalchemy = "2.0.28" +psycopg = "3.1.18" +cassandra-driver = "3.29.1" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" -hvac = "*" +hvac = "2.1.0" pymilvus = "2.4.3" +httpx = "0.27.0" paho-mqtt = "2.1.0" [[tool.poetry.source]] From 4aff6793f28fbeb8358adcc728283ea9a7b94e5f Mon Sep 17 00:00:00 2001 From: Joel Hess Date: Tue, 18 Jun 2024 04:36:31 -0500 Subject: [PATCH 387/425] fix: Add Cockroach DB Module to Testcontainers (#608) Adds [Cockroach DB] (https://www.cockroachlabs.com/) module to use with Test containers I had done this previously under https://github.com/testcontainers/testcontainers-python/pull/281, but opted to just redo it rather than try to rebase all the things. - [x] Create a new feature directory and populate it with the package structure [described in the documentation](https://testcontainers-python.readthedocs.io/en/latest/#package-structure). Copying one of the existing features is likely the best way to get started. - [x] Implement the new feature (typically in `__init__.py`) and corresponding tests. - [x] Update the feature `README.rst` and add it to the table of contents (`toctree` directive) in the top-level `README.rst`. - [] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `main` branch. - [x] Rebase your development branch on `main` (or merge `main` into your development branch). - [x] Add Package to pyproject.toml - [ ] Add a line `-e file:[feature name]` to `requirements.in` and open a pull request. Opening a pull request will automatically generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Finally, run `python get_requirements.py --pr=[your PR number]` to fetch the updated requirement files (the build needs to have succeeded). --------- Co-authored-by: joelhess Co-authored-by: David Ankin --- modules/cockroachdb/README.rst | 2 + .../testcontainers/cockroachdb/__init__.py | 100 ++++++++++++++++++ modules/cockroachdb/tests/test_cockroachdb.py | 14 +++ poetry.lock | 17 ++- pyproject.toml | 3 + 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 modules/cockroachdb/README.rst create mode 100644 modules/cockroachdb/testcontainers/cockroachdb/__init__.py create mode 100644 modules/cockroachdb/tests/test_cockroachdb.py diff --git a/modules/cockroachdb/README.rst b/modules/cockroachdb/README.rst new file mode 100644 index 000000000..7b53fc336 --- /dev/null +++ b/modules/cockroachdb/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.cockroachdb.CockroachDBContainer +.. title:: testcontainers.cockroachdb.CockroachDBContainer diff --git a/modules/cockroachdb/testcontainers/cockroachdb/__init__.py b/modules/cockroachdb/testcontainers/cockroachdb/__init__.py new file mode 100644 index 000000000..13a17ed5c --- /dev/null +++ b/modules/cockroachdb/testcontainers/cockroachdb/__init__.py @@ -0,0 +1,100 @@ +# +# 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. +from os import environ +from typing import Optional +from urllib.error import HTTPError, URLError +from urllib.request import urlopen + +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class CockroachDBContainer(DbContainer): + """ + CockroachDB database container. + + Example: + + The example will spin up a CockroachDB database to which you can connect with the credentials + passed in the constructor. Alternatively, you may use the :code:`get_connection_url()` + method which returns a sqlalchemy-compatible url in format + :code:`dialect+driver://username:password@host:port/database`. + + .. doctest:: + + >>> import sqlalchemy + >>> from testcontainers.cockroachdb import CockroachDBContainer + + >>> with CockroachDBContainer('cockroachdb/cockroach:v24.1.1') as crdb: + ... engine = sqlalchemy.create_engine(crdb.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute(sqlalchemy.text("select version()")) + ... version, = result.fetchone() + + """ + + COCKROACH_DB_PORT: int = 26257 + COCKROACH_API_PORT: int = 8080 + + def __init__( + self, + image: str = "cockroachdb/cockroach:v24.1.1", + username: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + dialect="cockroachdb+psycopg2", + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + + self.with_exposed_ports(self.COCKROACH_DB_PORT, self.COCKROACH_API_PORT) + self.username = username or environ.get("COCKROACH_USER", "cockroach") + self.password = password or environ.get("COCKROACH_PASSWORD", "arthropod") + self.dbname = dbname or environ.get("COCKROACH_DATABASE", "roach") + self.dialect = dialect + + def _configure(self) -> None: + self.with_env("COCKROACH_DATABASE", self.dbname) + self.with_env("COCKROACH_USER", self.username) + self.with_env("COCKROACH_PASSWORD", self.password) + + cmd = "start-single-node" + if not self.password: + cmd += " --insecure" + self.with_command(cmd) + + @wait_container_is_ready(HTTPError, URLError) + def _connect(self) -> None: + host = self.get_container_host_ip() + url = f"http://{host}:{self.get_exposed_port(self.COCKROACH_API_PORT)}/health" + self._wait_for_health(url) + wait_for_logs(self, "finished creating default user*") + + @staticmethod + def _wait_for_health(url): + with urlopen(url) as response: + response.read() + + def get_connection_url(self) -> str: + conn_str = super()._create_connection_url( + dialect=self.dialect, + username=self.username, + password=self.password, + dbname=self.dbname, + port=self.COCKROACH_DB_PORT, + ) + + if self.password: + conn_str += "?sslmode=require" + + return conn_str diff --git a/modules/cockroachdb/tests/test_cockroachdb.py b/modules/cockroachdb/tests/test_cockroachdb.py new file mode 100644 index 000000000..af20fd580 --- /dev/null +++ b/modules/cockroachdb/tests/test_cockroachdb.py @@ -0,0 +1,14 @@ +import sqlalchemy + +from testcontainers.cockroachdb import CockroachDBContainer + + +def test_docker_run_mysql(): + config = CockroachDBContainer("cockroachdb/cockroach:v24.1.1") + with config as crdb: + engine = sqlalchemy.create_engine(crdb.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("select version()")) + for row in result: + assert "CockroachDB" in row[0] + assert "v24.1.1" in row[0] diff --git a/poetry.lock b/poetry.lock index 891c7bd75..d70ba7e37 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4006,6 +4006,20 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] +[[package]] +name = "sqlalchemy-cockroachdb" +version = "2.0.2" +description = "CockroachDB dialect for SQLAlchemy" +optional = false +python-versions = "*" +files = [ + {file = "sqlalchemy-cockroachdb-2.0.2.tar.gz", hash = "sha256:119756eb905855d6a11345b99cfe853031a3fe598a9c4bf35a8ddac9f89fe8cc"}, + {file = "sqlalchemy_cockroachdb-2.0.2-py3-none-any.whl", hash = "sha256:0d5d50e805b024cb2ccd85423a5c1a367d1a56a5cd0ea47765233fd47665070d"}, +] + +[package.dependencies] +SQLAlchemy = "*" + [[package]] name = "tenacity" version = "8.2.3" @@ -4447,6 +4461,7 @@ azurite = ["azure-storage-blob"] cassandra = [] chroma = ["chromadb-client"] clickhouse = ["clickhouse-driver"] +cockroachdb = [] elasticsearch = [] google = ["google-cloud-datastore", "google-cloud-pubsub"] influxdb = ["influxdb", "influxdb-client"] @@ -4479,4 +4494,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "043c7eea4ca72646a19a705891b26577a27149673ba38c8a6dd4732d30ce081c" +content-hash = "040fa3576807a8bd7b129b889c934d5c4bca9e95376c50e15fa870f4d8336fdb" diff --git a/pyproject.toml b/pyproject.toml index afe841c82..4967a28eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ packages = [ { include = "testcontainers", from = "modules/cassandra" }, { include = "testcontainers", from = "modules/chroma" }, { include = "testcontainers", from = "modules/clickhouse" }, + { include = "testcontainers", from = "modules/cockroachdb" }, { include = "testcontainers", from = "modules/elasticsearch" }, { include = "testcontainers", from = "modules/google" }, { include = "testcontainers", from = "modules/influxdb" }, @@ -107,6 +108,7 @@ arangodb = ["python-arango"] azurite = ["azure-storage-blob"] cassandra = [] clickhouse = ["clickhouse-driver"] +cockroachdb = [] elasticsearch = [] google = ["google-cloud-pubsub", "google-cloud-datastore"] influxdb = ["influxdb", "influxdb-client"] @@ -157,6 +159,7 @@ hvac = "2.1.0" pymilvus = "2.4.3" httpx = "0.27.0" paho-mqtt = "2.1.0" +sqlalchemy-cockroachdb = "2.0.2" [[tool.poetry.source]] name = "PyPI" From 090bd0d3f09414decc481d9a06d8a62f045d8819 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 06:23:05 -0400 Subject: [PATCH 388/425] chore(main): release testcontainers 4.6.0 (#594) :robot: I have created a release *beep* *boop* --- ## [4.6.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.5.1...testcontainers-v4.6.0) (2024-06-18) ### Features * **core:** Added ServerContainer ([#595](https://github.com/testcontainers/testcontainers-python/issues/595)) ([0768490](https://github.com/testcontainers/testcontainers-python/commit/076849015ad3542384ecf8cf6c205d5d498e4986)) * **core:** Image build (Dockerfile support) ([#585](https://github.com/testcontainers/testcontainers-python/issues/585)) ([54c88cf](https://github.com/testcontainers/testcontainers-python/commit/54c88cf00ad7bb08eb7894c52bed7a9010fd7786)) ### Bug Fixes * Add Cockroach DB Module to Testcontainers ([#608](https://github.com/testcontainers/testcontainers-python/issues/608)) ([4aff679](https://github.com/testcontainers/testcontainers-python/commit/4aff6793f28fbeb8358adcc728283ea9a7b94e5f)) * Container for Milvus database ([#606](https://github.com/testcontainers/testcontainers-python/issues/606)) ([ec76df2](https://github.com/testcontainers/testcontainers-python/commit/ec76df27c3d95ac1b79df3a049b4e2c12539081d)) * move TESTCONTAINERS_HOST_OVERRIDE to config.py ([#603](https://github.com/testcontainers/testcontainers-python/issues/603)) ([2a5a190](https://github.com/testcontainers/testcontainers-python/commit/2a5a1904391020a9da4be17b32f23b36d9385c29)), closes [#602](https://github.com/testcontainers/testcontainers-python/issues/602) * **mqtt:** Add mqtt.MosquittoContainer ([#568](https://github.com/testcontainers/testcontainers-python/issues/568)) ([#599](https://github.com/testcontainers/testcontainers-python/issues/599)) ([59cb6fc](https://github.com/testcontainers/testcontainers-python/commit/59cb6fc4e7d93870ff2d0d961d14ccd5142a8a05)) ### Documentation * **main:** Private registry ([#598](https://github.com/testcontainers/testcontainers-python/issues/598)) ([9045c0a](https://github.com/testcontainers/testcontainers-python/commit/9045c0aea6029283490c89aea985e625dcdfc7b9)) * Update private registry instructions ([#604](https://github.com/testcontainers/testcontainers-python/issues/604)) ([f5a019b](https://github.com/testcontainers/testcontainers-python/commit/f5a019b6d2552788478e4a10cd17f7a2b453abb9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 22 ++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 616660195..c69cd293a 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.5.1" + ".": "4.6.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d50cb9fd..120adbeee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [4.6.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.5.1...testcontainers-v4.6.0) (2024-06-18) + + +### Features + +* **core:** Added ServerContainer ([#595](https://github.com/testcontainers/testcontainers-python/issues/595)) ([0768490](https://github.com/testcontainers/testcontainers-python/commit/076849015ad3542384ecf8cf6c205d5d498e4986)) +* **core:** Image build (Dockerfile support) ([#585](https://github.com/testcontainers/testcontainers-python/issues/585)) ([54c88cf](https://github.com/testcontainers/testcontainers-python/commit/54c88cf00ad7bb08eb7894c52bed7a9010fd7786)) + + +### Bug Fixes + +* Add Cockroach DB Module to Testcontainers ([#608](https://github.com/testcontainers/testcontainers-python/issues/608)) ([4aff679](https://github.com/testcontainers/testcontainers-python/commit/4aff6793f28fbeb8358adcc728283ea9a7b94e5f)) +* Container for Milvus database ([#606](https://github.com/testcontainers/testcontainers-python/issues/606)) ([ec76df2](https://github.com/testcontainers/testcontainers-python/commit/ec76df27c3d95ac1b79df3a049b4e2c12539081d)) +* move TESTCONTAINERS_HOST_OVERRIDE to config.py ([#603](https://github.com/testcontainers/testcontainers-python/issues/603)) ([2a5a190](https://github.com/testcontainers/testcontainers-python/commit/2a5a1904391020a9da4be17b32f23b36d9385c29)), closes [#602](https://github.com/testcontainers/testcontainers-python/issues/602) +* **mqtt:** Add mqtt.MosquittoContainer ([#568](https://github.com/testcontainers/testcontainers-python/issues/568)) ([#599](https://github.com/testcontainers/testcontainers-python/issues/599)) ([59cb6fc](https://github.com/testcontainers/testcontainers-python/commit/59cb6fc4e7d93870ff2d0d961d14ccd5142a8a05)) + + +### Documentation + +* **main:** Private registry ([#598](https://github.com/testcontainers/testcontainers-python/issues/598)) ([9045c0a](https://github.com/testcontainers/testcontainers-python/commit/9045c0aea6029283490c89aea985e625dcdfc7b9)) +* Update private registry instructions ([#604](https://github.com/testcontainers/testcontainers-python/issues/604)) ([f5a019b](https://github.com/testcontainers/testcontainers-python/commit/f5a019b6d2552788478e4a10cd17f7a2b453abb9)) + ## [4.5.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.5.0...testcontainers-v4.5.1) (2024-05-31) diff --git a/pyproject.toml b/pyproject.toml index 4967a28eb..3cd4518bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.5.1" # auto-incremented by release-please +version = "4.6.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 762d2a2130f7ce17dacaed5a96a6898a08cf2bc5 Mon Sep 17 00:00:00 2001 From: Jorge Lima <5619521+jfmlima@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:33:51 +0100 Subject: [PATCH 389/425] fix(kafka): Add Kraft to Kafka containers (#611) Following a similar strategy as several other testcontainers implementations, this PR introduces the possibility to run Kafka in KRAft mode. ```py with KafkaContainer().with_kraft() as container: # Test something with/on KRaft mode ``` --- core/testcontainers/core/version.py | 30 ++++++ core/tests/test_version.py | 78 +++++++++++++++ .../kafka/testcontainers/kafka/__init__.py | 97 +++++++++++++++++-- modules/kafka/tests/test_kafka.py | 6 ++ 4 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 core/testcontainers/core/version.py create mode 100644 core/tests/test_version.py diff --git a/core/testcontainers/core/version.py b/core/testcontainers/core/version.py new file mode 100644 index 000000000..cac51fc18 --- /dev/null +++ b/core/testcontainers/core/version.py @@ -0,0 +1,30 @@ +from typing import Callable + +from packaging.version import Version + + +class ComparableVersion: + def __init__(self, version): + self.version = Version(version) + + def __lt__(self, other: str): + return self._apply_op(other, lambda x, y: x < y) + + def __le__(self, other: str): + return self._apply_op(other, lambda x, y: x <= y) + + def __eq__(self, other: str): + return self._apply_op(other, lambda x, y: x == y) + + def __ne__(self, other: str): + return self._apply_op(other, lambda x, y: x != y) + + def __gt__(self, other: str): + return self._apply_op(other, lambda x, y: x > y) + + def __ge__(self, other: str): + return self._apply_op(other, lambda x, y: x >= y) + + def _apply_op(self, other: str, op: Callable[[Version, Version], bool]): + other = Version(other) + return op(self.version, other) diff --git a/core/tests/test_version.py b/core/tests/test_version.py new file mode 100644 index 000000000..397cd0523 --- /dev/null +++ b/core/tests/test_version.py @@ -0,0 +1,78 @@ +import pytest +from packaging.version import InvalidVersion + +from testcontainers.core.version import ComparableVersion + + +@pytest.fixture +def version(): + return ComparableVersion("1.0.0") + + +@pytest.mark.parametrize("other_version, expected", [("0.9.0", False), ("1.0.0", False), ("1.1.0", True)]) +def test_lt(version, other_version, expected): + assert (version < other_version) == expected + + +@pytest.mark.parametrize("other_version, expected", [("0.9.0", False), ("1.0.0", True), ("1.1.0", True)]) +def test_le(version, other_version, expected): + assert (version <= other_version) == expected + + +@pytest.mark.parametrize("other_version, expected", [("0.9.0", False), ("1.0.0", True), ("1.1.0", False)]) +def test_eq(version, other_version, expected): + assert (version == other_version) == expected + + +@pytest.mark.parametrize("other_version, expected", [("0.9.0", True), ("1.0.0", False), ("1.1.0", True)]) +def test_ne(version, other_version, expected): + assert (version != other_version) == expected + + +@pytest.mark.parametrize("other_version, expected", [("0.9.0", True), ("1.0.0", False), ("1.1.0", False)]) +def test_gt(version, other_version, expected): + assert (version > other_version) == expected + + +@pytest.mark.parametrize("other_version, expected", [("0.9.0", True), ("1.0.0", True), ("1.1.0", False)]) +def test_ge(version, other_version, expected): + assert (version >= other_version) == expected + + +@pytest.mark.parametrize( + "invalid_version", + [ + "invalid", + "1..0", + ], +) +def test_invalid_version_raises_error(invalid_version): + with pytest.raises(InvalidVersion): + ComparableVersion(invalid_version) + + +@pytest.mark.parametrize( + "invalid_version", + [ + "invalid", + "1..0", + ], +) +def test_comparison_with_invalid_version_raises_error(version, invalid_version): + with pytest.raises(InvalidVersion): + assert version < invalid_version + + with pytest.raises(InvalidVersion): + assert version <= invalid_version + + with pytest.raises(InvalidVersion): + assert version == invalid_version + + with pytest.raises(InvalidVersion): + assert version != invalid_version + + with pytest.raises(InvalidVersion): + assert version > invalid_version + + with pytest.raises(InvalidVersion): + assert version >= invalid_version diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index 7dd71b633..ea837be37 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -3,8 +3,11 @@ from io import BytesIO from textwrap import dedent +from typing_extensions import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.version import ComparableVersion from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.kafka._redpanda import RedpandaContainer @@ -26,18 +29,29 @@ class KafkaContainer(DockerContainer): >>> with KafkaContainer() as kafka: ... connection = kafka.get_bootstrap_server() + + # Using KRaft protocol + >>> with KafkaContainer().with_kraft() as kafka: + ... connection = kafka.get_bootstrap_server() """ TC_START_SCRIPT = "/tc-start.sh" + MIN_KRAFT_TAG = "7.0.0" def __init__(self, image: str = "confluentinc/cp-kafka:7.6.0", port: int = 9093, **kwargs) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image, **kwargs) self.port = port + self.kraft_enabled = False + self.wait_for = r".*\[KafkaServer id=\d+\] started.*" + self.boot_command = "" + self.cluster_id = "MkU3OEVBNTcwNTJENDM2Qk" + self.listeners = f"PLAINTEXT://0.0.0.0:{self.port},BROKER://0.0.0.0:9092" + self.security_protocol_map = "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT" + self.with_exposed_ports(self.port) - listeners = f"PLAINTEXT://0.0.0.0:{self.port},BROKER://0.0.0.0:9092" - self.with_env("KAFKA_LISTENERS", listeners) - self.with_env("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT") + self.with_env("KAFKA_LISTENERS", self.listeners) + self.with_env("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", self.security_protocol_map) self.with_env("KAFKA_INTER_BROKER_LISTENER_NAME", "BROKER") self.with_env("KAFKA_BROKER_ID", "1") @@ -46,6 +60,74 @@ def __init__(self, image: str = "confluentinc/cp-kafka:7.6.0", port: int = 9093, self.with_env("KAFKA_LOG_FLUSH_INTERVAL_MESSAGES", "10000000") self.with_env("KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS", "0") + def with_kraft(self) -> Self: + self._verify_min_kraft_version() + self.kraft_enabled = True + return self + + def _verify_min_kraft_version(self): + actual_version = self.image.split(":")[-1] + + if ComparableVersion(actual_version) < self.MIN_KRAFT_TAG: + raise ValueError( + f"Provided Confluent Platform's version {actual_version} " + f"is not supported in Kraft mode" + f" (must be {self.MIN_KRAFT_TAG} or above)" + ) + + def with_cluster_id(self, cluster_id: str) -> Self: + self.cluster_id = cluster_id + return self + + def configure(self): + if self.kraft_enabled: + self._configure_kraft() + else: + self._configure_zookeeper() + + def _configure_kraft(self) -> None: + self.wait_for = r".*Kafka Server started.*" + + self.with_env("CLUSTER_ID", self.cluster_id) + self.with_env("KAFKA_NODE_ID", 1) + self.with_env( + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", + f"{self.security_protocol_map},CONTROLLER:PLAINTEXT", + ) + self.with_env( + "KAFKA_LISTENERS", + f"{self.listeners},CONTROLLER://0.0.0.0:9094", + ) + self.with_env("KAFKA_PROCESS_ROLES", "broker,controller") + + network_alias = self._get_network_alias() + controller_quorum_voters = f"1@{network_alias}:9094" + self.with_env("KAFKA_CONTROLLER_QUORUM_VOTERS", controller_quorum_voters) + self.with_env("KAFKA_CONTROLLER_LISTENER_NAMES", "CONTROLLER") + + self.boot_command = f""" + sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure + echo 'kafka-storage format --ignore-formatted -t {self.cluster_id} -c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure + """ + + def _get_network_alias(self): + if self._network: + return next( + iter(self._network_aliases or [self._network.name or self._kwargs.get("network", [])]), + None, + ) + + return "localhost" + + def _configure_zookeeper(self) -> None: + self.boot_command = """ + echo 'clientPort=2181' > zookeeper.properties + echo 'dataDir=/var/lib/zookeeper/data' >> zookeeper.properties + echo 'dataLogDir=/var/lib/zookeeper/log' >> zookeeper.properties + zookeeper-server-start zookeeper.properties & + export KAFKA_ZOOKEEPER_CONNECT='localhost:2181' + """ + def get_bootstrap_server(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) @@ -59,11 +141,7 @@ def tc_start(self) -> None: dedent( f""" #!/bin/bash - echo 'clientPort=2181' > zookeeper.properties - echo 'dataDir=/var/lib/zookeeper/data' >> zookeeper.properties - echo 'dataLogDir=/var/lib/zookeeper/log' >> zookeeper.properties - zookeeper-server-start zookeeper.properties & - export KAFKA_ZOOKEEPER_CONNECT='localhost:2181' + {self.boot_command} export KAFKA_ADVERTISED_LISTENERS={listeners} . /etc/confluent/docker/bash-config /etc/confluent/docker/configure @@ -78,10 +156,11 @@ def tc_start(self) -> None: def start(self, timeout=30) -> "KafkaContainer": script = KafkaContainer.TC_START_SCRIPT command = f'sh -c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' + self.configure() self.with_command(command) super().start() self.tc_start() - wait_for_logs(self, r".*\[KafkaServer id=\d+\] started.*", timeout=timeout) + wait_for_logs(self, self.wait_for, timeout=timeout) return self def create_file(self, content: bytes, path: str) -> None: diff --git a/modules/kafka/tests/test_kafka.py b/modules/kafka/tests/test_kafka.py index 1f3826adf..eb1a48127 100644 --- a/modules/kafka/tests/test_kafka.py +++ b/modules/kafka/tests/test_kafka.py @@ -8,6 +8,12 @@ def test_kafka_producer_consumer(): produce_and_consume_kafka_message(container) +def test_kafka_with_kraft_producer_consumer(): + with KafkaContainer().with_kraft() as container: + assert container.kraft_enabled + produce_and_consume_kafka_message(container) + + def test_kafka_producer_consumer_custom_port(): with KafkaContainer(port=9888) as container: assert container.port == 9888 From ead0f797902a94d3b2558e489fe2a0a55c3bb7ad Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 22 Jun 2024 07:08:52 +0200 Subject: [PATCH 390/425] feat(core): allow custom dockerfile path for image build and bypassing build cache (#615) fix #610 --- core/testcontainers/core/image.py | 12 ++++++++++-- core/tests/test_core.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/image.py b/core/testcontainers/core/image.py index 4004e9e44..6d793f83e 100644 --- a/core/testcontainers/core/image.py +++ b/core/testcontainers/core/image.py @@ -24,7 +24,9 @@ class DockerImage: ... logs = image.get_logs() :param tag: Tag for the image to be built (default: None) - :param path: Path to the Dockerfile to build the image + :param path: Path to the build context + :param dockerfile_path: Path to the Dockerfile within the build context path (default: Dockerfile) + :param no_cache: Bypass build cache; CLI's --no-cache """ def __init__( @@ -33,6 +35,8 @@ def __init__( docker_client_kw: Optional[dict] = None, tag: Optional[str] = None, clean_up: bool = True, + dockerfile_path: Union[str, PathLike] = "Dockerfile", + no_cache: bool = False, **kwargs, ) -> None: self.tag = tag @@ -42,11 +46,15 @@ def __init__( self._kwargs = kwargs self._image = None self._logs = None + self._dockerfile_path = dockerfile_path + self._no_cache = no_cache def build(self, **kwargs) -> Self: logger.info(f"Building image from {self.path}") docker_client = self.get_docker_client() - self._image, self._logs = docker_client.build(path=str(self.path), tag=self.tag, **kwargs) + self._image, self._logs = docker_client.build( + path=str(self.path), tag=self.tag, dockerfile=self._dockerfile_path, nocache=self._no_cache, **kwargs + ) logger.info(f"Built image {self.short_id} with tag {self.tag}") return self diff --git a/core/tests/test_core.py b/core/tests/test_core.py index efac8262e..8d0c77944 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -1,7 +1,9 @@ import pytest import tempfile import random +import os +from pathlib import Path from typing import Optional from testcontainers.core.container import DockerContainer @@ -64,3 +66,29 @@ def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_f assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch" check_for_image(image_short_id, test_cleanup) + + +@pytest.mark.parametrize("dockerfile_path", [None, Path("subdir/my.Dockerfile")]) +def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]): + with tempfile.TemporaryDirectory() as temp_directory: + temp_dir_path = Path(temp_directory) + if dockerfile_path: + os.makedirs(temp_dir_path / dockerfile_path.parent, exist_ok=True) + dockerfile_rel_path = dockerfile_path + dockerfile_kwargs = {"dockerfile_path": dockerfile_path} + else: + dockerfile_rel_path = Path("Dockerfile") # default + dockerfile_kwargs = {} + + with open(temp_dir_path / dockerfile_rel_path, "x") as f: + f.write( + f""" + FROM alpine:latest + CMD echo "Hello world!" + """ + ) + with DockerImage(path=temp_directory, tag="test", clean_up=True, no_cache=True, **dockerfile_kwargs) as image: + image_short_id = image.short_id + with DockerContainer(str(image)) as container: + assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch" + assert container.get_logs() == (("Hello world!\n").encode(), b""), "Container logs mismatch" From 5442d054cb8bc11887e09d24e29d9f91dd943307 Mon Sep 17 00:00:00 2001 From: Brice Fotzo <44189336+bricefotzo@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:11:25 +0200 Subject: [PATCH 391/425] feat(core): Add support for ollama module (#618) - Added a new class OllamaContainer with few methods to handle the Ollama container. - The `_check_and_add_gpu_capabilities` method checks if the host has GPUs and adds the necessary capabilities to the container. - The `commit_to_image` allows to save somehow the state of a container into an image so that we can reuse it, especially for the ones having some models pulled. - Added tests to check the functionality of the new class. > Note: I inspired myself from the java implementation of the Ollama module. Fixes #617 --------- Co-authored-by: David Ankin --- modules/ollama/README.rst | 2 + .../ollama/testcontainers/ollama/__init__.py | 120 ++++++++++++++++++ modules/ollama/tests/test_ollama.py | 60 +++++++++ poetry.lock | 4 +- pyproject.toml | 3 + 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 modules/ollama/README.rst create mode 100644 modules/ollama/testcontainers/ollama/__init__.py create mode 100644 modules/ollama/tests/test_ollama.py diff --git a/modules/ollama/README.rst b/modules/ollama/README.rst new file mode 100644 index 000000000..dc18fe265 --- /dev/null +++ b/modules/ollama/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.ollama.OllamaContainer +.. title:: testcontainers.ollama.OllamaContainer diff --git a/modules/ollama/testcontainers/ollama/__init__.py b/modules/ollama/testcontainers/ollama/__init__.py new file mode 100644 index 000000000..286aabd56 --- /dev/null +++ b/modules/ollama/testcontainers/ollama/__init__.py @@ -0,0 +1,120 @@ +# +# 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. + +from os import PathLike +from typing import Any, Optional, TypedDict, Union + +from docker.types.containers import DeviceRequest +from requests import get + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + + +class OllamaModel(TypedDict): + name: str + model: str + modified_at: str + size: int + digest: str + details: dict[str, Any] + + +class OllamaContainer(DockerContainer): + """ + Ollama Container + + Example: + + .. doctest:: + + >>> from testcontainers.ollama import OllamaContainer + >>> with OllamaContainer() as ollama: + ... ollama.list_models() + [] + """ + + OLLAMA_PORT = 11434 + + def __init__( + self, + image: str = "ollama/ollama:0.1.44", + ollama_dir: Optional[Union[str, PathLike]] = None, + **kwargs, + # + ): + super().__init__(image=image, **kwargs) + self.ollama_dir = ollama_dir + self.with_exposed_ports(OllamaContainer.OLLAMA_PORT) + self._check_and_add_gpu_capabilities() + + def _check_and_add_gpu_capabilities(self): + info = self.get_docker_client().client.info() + if "nvidia" in info["Runtimes"]: + self._kwargs = {**self._kwargs, "device_requests": DeviceRequest(count=-1, capabilities=[["gpu"]])} + + def start(self) -> "OllamaContainer": + """ + Start the Ollama server + """ + if self.ollama_dir: + self.with_volume_mapping(self.ollama_dir, "/root/.ollama", "rw") + super().start() + wait_for_logs(self, "Listening on ", timeout=30) + + return self + + def get_endpoint(self): + """ + Return the endpoint of the Ollama server + """ + host = self.get_container_host_ip() + exposed_port = self.get_exposed_port(OllamaContainer.OLLAMA_PORT) + url = f"http://{host}:{exposed_port}" + return url + + @property + def id(self) -> str: + """ + Return the container object + """ + return self._container.id + + def pull_model(self, model_name: str) -> None: + """ + Pull a model from the Ollama server + + Args: + model_name (str): Name of the model + """ + self.exec(f"ollama pull {model_name}") + + def list_models(self) -> list[OllamaModel]: + endpoint = self.get_endpoint() + response = get(url=f"{endpoint}/api/tags") + response.raise_for_status() + return response.json().get("models", []) + + def commit_to_image(self, image_name: str) -> None: + """ + Commit the current container to a new image + + Args: + image_name (str): Name of the new image + """ + docker_client = self.get_docker_client() + existing_images = docker_client.client.images.list(name=image_name) + if not existing_images and self.id: + docker_client.client.containers.get(self.id).commit( + repository=image_name, conf={"Labels": {"org.testcontainers.session-id": ""}} + ) diff --git a/modules/ollama/tests/test_ollama.py b/modules/ollama/tests/test_ollama.py new file mode 100644 index 000000000..80b22a462 --- /dev/null +++ b/modules/ollama/tests/test_ollama.py @@ -0,0 +1,60 @@ +import random +import string +from pathlib import Path + +import requests +from testcontainers.ollama import OllamaContainer + + +def random_string(length=6): + return "".join(random.choices(string.ascii_lowercase, k=length)) + + +def test_ollama_container(): + with OllamaContainer() as ollama: + url = ollama.get_endpoint() + response = requests.get(url) + assert response.status_code == 200 + assert response.text == "Ollama is running" + + +def test_with_default_config(): + with OllamaContainer("ollama/ollama:0.1.26") as ollama: + ollama.start() + response = requests.get(f"{ollama.get_endpoint()}/api/version") + version = response.json().get("version") + assert version == "0.1.26" + + +def test_download_model_and_commit_to_image(): + new_image_name = f"tc-ollama-allminilm-{random_string(length=4).lower()}" + with OllamaContainer("ollama/ollama:0.1.26") as ollama: + ollama.start() + # Pull the model + ollama.pull_model("all-minilm") + + response = requests.get(f"{ollama.get_endpoint()}/api/tags") + model_name = ollama.list_models()[0].get("name") + assert "all-minilm" in model_name + + # Commit the container state to a new image + ollama.commit_to_image(new_image_name) + + # Verify the new image + with OllamaContainer(new_image_name) as ollama: + ollama.start() + response = requests.get(f"{ollama.get_endpoint()}/api/tags") + model_name = response.json().get("models", [])[0].get("name") + assert "all-minilm" in model_name + + +def test_models_saved_in_folder(tmp_path: Path): + with OllamaContainer("ollama/ollama:0.1.26", ollama_dir=tmp_path) as ollama: + assert len(ollama.list_models()) == 0 + ollama.pull_model("all-minilm") + assert len(ollama.list_models()) == 1 + assert "all-minilm" in ollama.list_models()[0].get("name") + + with OllamaContainer("ollama/ollama:0.1.26", ollama_dir=tmp_path) as ollama: + assert len(ollama.list_models()) == 1 + assert "all-minilm" in ollama.list_models()[0].get("name") diff --git a/poetry.lock b/poetry.lock index d70ba7e37..b2cb7e81f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1898,6 +1898,7 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, + {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -4479,6 +4480,7 @@ mysql = ["pymysql", "sqlalchemy"] nats = ["nats-py"] neo4j = ["neo4j"] nginx = [] +ollama = [] opensearch = ["opensearch-py"] oracle = ["oracledb", "sqlalchemy"] oracle-free = ["oracledb", "sqlalchemy"] @@ -4494,4 +4496,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "040fa3576807a8bd7b129b889c934d5c4bca9e95376c50e15fa870f4d8336fdb" +content-hash = "6f7697a84a674802e30ceea61276d800b6b98224863a0c512138447d9b4af524" diff --git a/pyproject.toml b/pyproject.toml index 3cd4518bf..cf9a3710c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ packages = [ { include = "testcontainers", from = "modules/nats" }, { include = "testcontainers", from = "modules/neo4j" }, { include = "testcontainers", from = "modules/nginx" }, + { include = "testcontainers", from = "modules/ollama" }, { include = "testcontainers", from = "modules/opensearch" }, { include = "testcontainers", from = "modules/oracle-free" }, { include = "testcontainers", from = "modules/postgres" }, @@ -127,6 +128,7 @@ nats = ["nats-py"] neo4j = ["neo4j"] nginx = [] opensearch = ["opensearch-py"] +ollama = [] oracle = ["sqlalchemy", "oracledb"] oracle-free = ["sqlalchemy", "oracledb"] postgres = [] @@ -272,6 +274,7 @@ mypy_path = [ # "modules/mysql", # "modules/neo4j", # "modules/nginx", +# "modules/ollama", # "modules/opensearch", # "modules/oracle", # "modules/postgres", From 27f2a6bdca8b9c860a96920eebc96f53682ea750 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Thu, 27 Jun 2024 06:38:07 -0400 Subject: [PATCH 392/425] fix: improve ollama docs, s/ollama_dir/ollama_home/g (#619) --- .../ollama/testcontainers/ollama/__init__.py | 50 +++++++++++++++++-- modules/ollama/tests/test_ollama.py | 4 +- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/modules/ollama/testcontainers/ollama/__init__.py b/modules/ollama/testcontainers/ollama/__init__.py index 286aabd56..ea089f149 100644 --- a/modules/ollama/testcontainers/ollama/__init__.py +++ b/modules/ollama/testcontainers/ollama/__init__.py @@ -34,7 +34,13 @@ class OllamaContainer(DockerContainer): """ Ollama Container - Example: + :param: image - the ollama image to use (default: :code:`ollama/ollama:0.1.44`) + :param: ollama_home - the directory to mount for model data (default: None) + + you may pass :code:`pathlib.Path.home() / ".ollama"` to re-use models + that have already been pulled with ollama running on this host outside the container. + + Examples: .. doctest:: @@ -42,6 +48,40 @@ class OllamaContainer(DockerContainer): >>> with OllamaContainer() as ollama: ... ollama.list_models() [] + + .. code-block:: python + + >>> from json import loads + >>> from pathlib import Path + >>> from requests import post + >>> from testcontainers.ollama import OllamaContainer + >>> def split_by_line(generator): + ... data = b'' + ... for each_item in generator: + ... for line in each_item.splitlines(True): + ... data += line + ... if data.endswith((b'\\r\\r', b'\\n\\n', b'\\r\\n\\r\\n', b'\\n')): + ... yield from data.splitlines() + ... data = b'' + ... if data: + ... yield from data.splitlines() + + >>> with OllamaContainer(ollama_home=Path.home() / ".ollama") as ollama: + ... if "llama3:latest" not in [e["name"] for e in ollama.list_models()]: + ... print("did not find 'llama3:latest', pulling") + ... ollama.pull_model("llama3:latest") + ... endpoint = ollama.get_endpoint() + ... for chunk in split_by_line( + ... post(url=f"{endpoint}/api/chat", stream=True, json={ + ... "model": "llama3:latest", + ... "messages": [{ + ... "role": "user", + ... "content": "what color is the sky? MAX ONE WORD" + ... }] + ... }) + ... ): + ... print(loads(chunk)["message"]["content"], end="") + Blue. """ OLLAMA_PORT = 11434 @@ -49,12 +89,12 @@ class OllamaContainer(DockerContainer): def __init__( self, image: str = "ollama/ollama:0.1.44", - ollama_dir: Optional[Union[str, PathLike]] = None, + ollama_home: Optional[Union[str, PathLike]] = None, **kwargs, # ): super().__init__(image=image, **kwargs) - self.ollama_dir = ollama_dir + self.ollama_home = ollama_home self.with_exposed_ports(OllamaContainer.OLLAMA_PORT) self._check_and_add_gpu_capabilities() @@ -67,8 +107,8 @@ def start(self) -> "OllamaContainer": """ Start the Ollama server """ - if self.ollama_dir: - self.with_volume_mapping(self.ollama_dir, "/root/.ollama", "rw") + if self.ollama_home: + self.with_volume_mapping(self.ollama_home, "/root/.ollama", "rw") super().start() wait_for_logs(self, "Listening on ", timeout=30) diff --git a/modules/ollama/tests/test_ollama.py b/modules/ollama/tests/test_ollama.py index 80b22a462..980dac00b 100644 --- a/modules/ollama/tests/test_ollama.py +++ b/modules/ollama/tests/test_ollama.py @@ -49,12 +49,12 @@ def test_download_model_and_commit_to_image(): def test_models_saved_in_folder(tmp_path: Path): - with OllamaContainer("ollama/ollama:0.1.26", ollama_dir=tmp_path) as ollama: + with OllamaContainer("ollama/ollama:0.1.26", ollama_home=tmp_path) as ollama: assert len(ollama.list_models()) == 0 ollama.pull_model("all-minilm") assert len(ollama.list_models()) == 1 assert "all-minilm" in ollama.list_models()[0].get("name") - with OllamaContainer("ollama/ollama:0.1.26", ollama_dir=tmp_path) as ollama: + with OllamaContainer("ollama/ollama:0.1.26", ollama_home=tmp_path) as ollama: assert len(ollama.list_models()) == 1 assert "all-minilm" in ollama.list_models()[0].get("name") From e71180039441e3c7d49467298ef0f498fe786149 Mon Sep 17 00:00:00 2001 From: aksel Date: Thu, 27 Jun 2024 15:01:51 +0100 Subject: [PATCH 393/425] feat(core): DockerCompose.stop now stops only services that it starts (does not stop the other services) (#620) The command would otherwise stop/down all services, not just the services the instance itself started. Useful for e.g. one fixture per service, and you want different scopes for the services. --- core/testcontainers/compose/compose.py | 4 ++ .../basic_multiple/docker-compose.yaml | 15 ++++++ core/tests/test_compose.py | 49 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 core/tests/compose_fixtures/basic_multiple/docker-compose.yaml diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index 951aee6d3..08dd313a4 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -236,6 +236,10 @@ def stop(self, down=True) -> None: down_cmd += ["down", "--volumes"] else: down_cmd += ["stop"] + + if self.services: + down_cmd.extend(self.services) + self._run_command(cmd=down_cmd) def get_logs(self, *services: str) -> tuple[str, str]: diff --git a/core/tests/compose_fixtures/basic_multiple/docker-compose.yaml b/core/tests/compose_fixtures/basic_multiple/docker-compose.yaml new file mode 100644 index 000000000..38bd92b94 --- /dev/null +++ b/core/tests/compose_fixtures/basic_multiple/docker-compose.yaml @@ -0,0 +1,15 @@ +services: + alpine1: + image: alpine:latest + init: true + command: + - sh + - -c + - 'while true; do sleep 0.1 ; date -Ins; done' + alpine2: + image: alpine:latest + init: true + command: + - sh + - -c + - 'while true; do sleep 0.1 ; date -Ins; done' diff --git a/core/tests/test_compose.py b/core/tests/test_compose.py index 0a244220b..e1a42655e 100644 --- a/core/tests/test_compose.py +++ b/core/tests/test_compose.py @@ -37,6 +37,55 @@ def test_compose_start_stop(): basic.stop() +def test_start_stop_multiple(): + """Start and stop multiple containers individually.""" + + # Create two DockerCompose instances from the same file, one service each. + dc_a = DockerCompose(context=FIXTURES / "basic_multiple", services=["alpine1"]) + dc_b = DockerCompose(context=FIXTURES / "basic_multiple", services=["alpine2"]) + + # After starting the first instance, alpine1 should be running + dc_a.start() + dc_a.get_container("alpine1") # Raises if it isn't running + dc_b.get_container("alpine1") # Raises if it isn't running + + # Both instances report the same number of containers + assert len(dc_a.get_containers()) == 1 + assert len(dc_b.get_containers()) == 1 + + # Although alpine1 is running, alpine2 has not started yet. + with pytest.raises(ContainerIsNotRunning): + dc_a.get_container("alpine2") + with pytest.raises(ContainerIsNotRunning): + dc_b.get_container("alpine2") + + # After starting the second instance, alpine2 should also be running + dc_b.start() + dc_a.get_container("alpine2") # No longer raises + dc_b.get_container("alpine2") # No longer raises + assert len(dc_a.get_containers()) == 2 + assert len(dc_b.get_containers()) == 2 + + # After stopping the first instance, alpine1 should no longer be running + dc_a.stop() + dc_a.get_container("alpine2") + dc_b.get_container("alpine2") + assert len(dc_a.get_containers()) == 1 + assert len(dc_b.get_containers()) == 1 + + # alpine1 no longer running + with pytest.raises(ContainerIsNotRunning): + dc_a.get_container("alpine1") + with pytest.raises(ContainerIsNotRunning): + dc_b.get_container("alpine1") + + # Stop the second instance + dc_b.stop() + + assert len(dc_a.get_containers()) == 0 + assert len(dc_b.get_containers()) == 0 + + def test_compose(): """stream-of-consciousness e2e test""" basic = DockerCompose(context=FIXTURES / "basic") From 3519f4bdad6eac6c172977303b51cf52b4fa4c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:42:58 +0200 Subject: [PATCH 394/425] docs(contributing): add contribution and new-container guide (#460) # change Document how to contribute, with initial focus on making local development smooth. # Tasks - [x] Finish the `new-container` guide - [x] Remove any old docs referring to - [x] Update `README.md` to point at the contribution guide - [x] Update `README.md` to add badges (supported python versions, etc) and to give kudos to current and past maintainers and contributors --------- Co-authored-by: Dave Ankin Co-authored-by: Jan Katins Co-authored-by: Max Pfeiffer --- .github/CONTRIBUTING.md | 82 ++++++++++++++++ .github/ISSUE_TEMPLATE/new-container.md | 26 +++++ .github/ISSUE_TEMPLATE/question.md | 30 +++--- .../PULL_REQUEST_TEMPLATE/new_container.md | 48 ++++++++-- Dockerfile | 9 +- Makefile | 96 +++++++++---------- README.md | 15 ++- index.rst | 30 +++--- 8 files changed, 247 insertions(+), 89 deletions(-) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/new-container.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..12c8d19fa --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,82 @@ +# Contributing to `testcontainers-python` + +Welcome to the `testcontainers-python` community! +This should give you an idea about how we build, test and release `testcontainers-python`! + +Highly recommended to read this document thoroughly to understand what we're working on right now +and what our priorities are before you are trying to contribute something. + +This will greatly increase your chances of getting prompt replies as the maintainers are volunteers themselves. + +## Before you Begin + +We recommend following these steps: + +1. Finish reading this document. +2. Read the [recently updated issues][1] +3. Look for existing issues on the subject you are interested in - we do our best to label everything correctly + + +## Local Development + +### Pre-Requisites + +You need to have the following tools available to you: +- `make` - You'll need a GNU Make for common developer activities +- `poetry` - This is the primary package manager for the project +- `pyenv` **Recommended**: For installing python versions for your system. + Poetry infers the current latest version from what it can find on the `PATH` so you are still fine if you don't use `pyenv`. + +### Build and test + + +- Run `make install` to get `poetry` to install all dependencies and set up `pre-commit` + - **Recommended**: Run `make` or `make help` to see other commands available to you. +- After this, you should have a working virtual environment and proceed with writing code with your favourite IDE +- **TIP**: You can run `make core/tests` or `make module//tests` to run the tests specifically for that to speed up feedback cycles +- You can also run `make lint` to run the `pre-commit` for the entire codebase. + + +## Adding new containers + +We have an [issue template](.github/ISSUE_TEMPLATE/new-container.md) for adding new containers, please refer to that for more information. +Once you've talked to the maintainers (we do our best to reply!) then you can proceed with contributing the new container. + +> [!WARNING] +> PLease raise an issue before you try to contribute a new container! It helps maintainers understand your use-case and motivation. +> This way we can keep pull requests foruced on the "how", not the "why"! :pray: +> It also gives maintainers a chance to give you last-minute guidance on caveats or expectations, particularly with +> new extra dependencies and how to manage them. + + +## Raising Issues + +We have [Issue Templates][2] to cover most cases, please try to adhere to them, they will guide you through the process. +Try to look through the existing issues before you raise a new one. + + +## Releasing Versions + +We have automated Semantic Versioning and release via [release-please](workflows/release-please.yml). +This takes care of: +- Detecting the next version, based on the commits that landed on `main` +- When a Release PR has been merged + - Create a GitHub Release with the CHANGELOG included + - Update the [CHANGELOG](../CHANGELOG.md), similar to the GitHub Release + - Release to PyPI via a [trusted publisher](https://docs.pypi.org/trusted-publishers/using-a-publisher/) + - Automatically script updates in files where it's needed instead of hand-crafting it (i.e. in `pyproject.toml`) + +> [!CRITICAL] +> Community modules are supported on a best-effort basis and for maintenance reasons, any change to them +> is only covered under minor and patch changes. +> +> Community modules changes DO NOT contribute to major version changes! +> +> If your community module container was broken by a minor or patch version change, check out the change logs! + +# Thank you! + +Thanks for reading, feedback on documentation is always welcome! + +[1]: https://github.com/testcontainers/testcontainers-python/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc "Recently Updated Issues showing you what we're focusing on" +[2]: https://github.com/testcontainers/testcontainers-python/issues/new/choose "List of current issue templates, please use them" diff --git a/.github/ISSUE_TEMPLATE/new-container.md b/.github/ISSUE_TEMPLATE/new-container.md new file mode 100644 index 000000000..b089c2e18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-container.md @@ -0,0 +1,26 @@ +--- +name: New Container +about: Tell the Testcontainers-Python team about a container you'd like to have support for. +title: 'New Container: ' +labels: '🚀 enhancement' +assignees: '' + +--- + + + +**What is the new container you'd like to have?** + +Please link some docker containers as well as documentation/arguments to the benefits of having this container. + +**Why not just use a generic container for this?** + +Please describe why the `DockerContainer("my-image:latest")` approach is not useful enough. + +Having a dedicated `TestContainer` usually means the need for some or all of these: +- complicated setup/configuration +- the wait strategy is complex for the container, usually more than just an http wait + +**Other references:** + +Include any other relevant reading material about the enhancement. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 9a7af6ead..b05282d90 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -9,27 +9,33 @@ assignees: '' -**What are you trying to do?** +## What are you trying to do? Ask your question here -**Where are you trying to do it?** +## Where are you trying to do it? Provide a self-contained code snippet that illustrates the bug or unexpected behavior. Ideally, include a link to a public repository with a minimal project where someone from the testcontainers-python can submit a PR with a solution to the problem you are facing with the library. -**Runtime environment** +## Runtime environment -Provide a summary of your runtime environment. Which operating system, python version, and docker version are you using? What is the version of `testcontainers-python` you are using? You can run the following commands to get the relevant information. +Provide a summary of your runtime environment. Which operating system, python version, and docker version are you using? +What is the version of `testcontainers-python` you are using? You can run the following commands to get the relevant information. + +Paste the results of the bash below + +```bash +uname -a +echo "------" +docker info +echo "------" +poetry run python --version +echo "------" +poetry show --tree +``` ```bash -# Get the operating system information (on a unix os). -$ uname -a -# Get the python version. -$ python --version -# Get the docker version and other docker information. -$ docker info -# Get all python packages. -$ pip freeze +paste-me-here ``` diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index 29b8190d4..27057310d 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -1,8 +1,40 @@ -You have implemented a new container and would like to contribute it? Great! Here are the necessary steps. - -- [ ] Create a new feature directory and populate it with the package structure [described in the documentation](https://testcontainers-python.readthedocs.io/en/latest/#package-structure). Copying one of the existing features is likely the best way to get started. -- [ ] Implement the new feature (typically in `__init__.py`) and corresponding tests. -- [ ] Update the feature `README.rst` and add it to the table of contents (`toctree` directive) in the top-level `README.rst`. -- [ ] Add a line `[feature name]` to the list of components in the GitHub Action workflow in `.github/workflows/main.yml` to run tests, build, and publish your package when pushed to the `main` branch. -- [ ] Rebase your development branch on `main` (or merge `main` into your development branch). -- [ ] Add a line `-e file:[feature name]` to `requirements.in` and open a pull request. Opening a pull request will automatically generate lock files to ensure reproducible builds (see the [pip-tools documentation](https://pip-tools.readthedocs.io/en/latest/) for details). Finally, run `python get_requirements.py --pr=[your PR number]` to fetch the updated requirement files (the build needs to have succeeded). +# New Container + + + +Fixes ... + + + + +# PR Checklist + +- [ ] Your PR title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) syntax + as we make use of this for detecting Semantic Versioning changes. +- [ ] Your PR allows maintainers to edit your branch, this will speed up resolving minor issues! +- [ ] The new container is implemented under `modules/*` + - Your module follows [PEP 420](https://peps.python.org/pep-0420/) with implicit namespace packages + (if unsure, look at other existing community modules) + - Your package namespacing follows `testcontainers..*` + and you DO NOT have an `__init__.py` above your module's level. + - Your module has it's own tests under `modules/*/tests` + - Your module has a `README.rst` and hooks in the `.. auto-class` and `.. title` of your container + - Implement the new feature (typically in `__init__.py`) and corresponding tests. +- [ ] Your module is added in `pyproject.toml` + - it is declared under `tool.poetry.packages` - see other community modules + - it is declared under `tool.poetry.extras` with the same name as your module name, + we still prefer adding _NO EXTRA DEPENDENCIES_, meaning `mymodule = []` is the preferred addition + (see the notes at the bottom) +- [ ] The `INDEX.rst` at the project root includes your module under the `.. toctree` directive +- [ ] Your branch is up to date (or we'll use GH's "update branch" function through the UI) + +# Preferred implementation + +- The current consensus among maintainers is to try to avoid enforcing the client library + for the given tools you are triyng to implement. +- This means we want you to avoid adding specific libraries as dependencies to `testcontainers`. +- Therefore, you should implement the configuration and the waiting with as little extra as possible +- You may still find it useful to add your preferred client library as a dev dependency diff --git a/Dockerfile b/Dockerfile index 4172f86fe..c86c9e2df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -ARG version=3.8 -FROM python:${version} +ARG PYTHON_VERSION +FROM python:${version}-slim-bookworm WORKDIR /workspace RUN pip install --upgrade pip \ @@ -7,7 +7,10 @@ RUN pip install --upgrade pip \ && apt-get install -y \ freetds-dev \ && rm -rf /var/lib/apt/lists/* + +# install requirements we exported from poetry COPY build/requirements.txt requirements.txt -COPY setup.py README.rst ./ RUN pip install -r requirements.txt + +# copy project source COPY . . diff --git a/Makefile b/Makefile index 4a0594095..9a4fd6f94 100644 --- a/Makefile +++ b/Makefile @@ -1,84 +1,78 @@ -PYTHON_VERSIONS = 3.9 3.10 3.11 +.DEFAULT_GOAL := help + + PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} -RUN = docker run --rm -it -# Get all directories that contain a setup.py and get the directory name. PACKAGES = core $(addprefix modules/,$(notdir $(wildcard modules/*))) -# All */dist folders for each of the packages. -DISTRIBUTIONS = $(addsuffix /dist,${PACKAGES}) UPLOAD = $(addsuffix /upload,${PACKAGES}) -# All */tests folders for each of the test suites. TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES})) TESTS_DIND = $(addsuffix -dind,${TESTS}) DOCTESTS = $(addsuffix /doctests,$(filter-out modules/README.md,${PACKAGES})) -# All linting targets. -LINT = $(addsuffix /lint,${PACKAGES}) - -# Targets to build a distribution for each package. -dist: ${DISTRIBUTIONS} -${DISTRIBUTIONS} : %/dist : %/setup.py - cd $* \ - && python setup.py bdist_wheel \ - && twine check dist/* - -# Targets to run the test suite for each package. -tests : ${TESTS} -${TESTS} : %/tests : + + +install: ## Set up the project for development + poetry install --all-extras + poetry run pre-commit install + +build: ## Build the python package + poetry build && poetry run twine check dist/* + +tests: ${TESTS} ## Run tests for each package +${TESTS}: %/tests: poetry run pytest -v --cov=testcontainers.$* $*/tests -# Target to combine and report coverage. -coverage: +coverage: ## Target to combine and report coverage. poetry run coverage combine poetry run coverage report poetry run coverage xml poetry run coverage html -# Target to lint the code. -lint: - pre-commit run -a - -# Targets to publish packages. -upload : ${UPLOAD} -${UPLOAD} : %/upload : - if [ ${TWINE_REPOSITORY}-$* = testpypi-meta ]; then \ - echo "Cannot upload meta package to testpypi because of missing permissions."; \ - else \ - twine upload --non-interactive --skip-existing $*/dist/*; \ - fi - -# Targets to build docker images -image: +lint: ## Lint all files in the project, which we also run in pre-commit + poetry run pre-commit run -a + +image: ## Make the docker image for dind tests poetry export -f requirements.txt -o build/requirements.txt - docker build --build-arg version=${PYTHON_VERSION} -t ${IMAGE} . + docker build --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t ${IMAGE} . -# Targets to run tests in docker containers -tests-dind : ${TESTS_DIND} +DOCKER_RUN = docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -${TESTS_DIND} : %/tests-dind : image - ${RUN} -v /var/run/docker.sock:/var/run/docker.sock ${IMAGE} \ - bash -c "make $*/lint $*/tests" +tests-dind: ${TESTS_DIND} ## Run the tests in docker containers to test `dind` +${TESTS_DIND}: %/tests-dind: image + ${DOCKER_RUN} ${IMAGE} \ + bash -c "make $*/tests" -# Target to build the documentation -docs : +docs: ## Build the docs for the project poetry run sphinx-build -nW . docs/_build # Target to build docs watching for changes as per https://stackoverflow.com/a/21389615 docs-watch : poetry run sphinx-autobuild . docs/_build # requires 'pip install sphinx-autobuild' -doctests : ${DOCTESTS} +doctests: ${DOCTESTS} ## Run doctests found across the documentation. poetry run sphinx-build -b doctest . docs/_build -${DOCTESTS} : %/doctests : +${DOCTESTS}: %/doctests: ## Run doctests found for a module. poetry run sphinx-build -b doctest -c doctests $* docs/_build -# Remove any generated files. -clean : + +clean: ## Remove generated files. rm -rf docs/_build - rm -rf */build - rm -rf */dist + rm -rf build + rm -rf dist rm -rf */*.egg-info +clean-all: clean ## Remove all generated files and reset the local virtual environment + rm -rf .venv + # Targets that do not generate file-level artifacts. -.PHONY : clean dists ${DISTRIBUTIONS} docs doctests image tests ${TESTS} +.PHONY: clean docs doctests image tests ${TESTS} + + +# Implements this pattern for autodocumenting Makefiles: +# https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +# +# Picks up all comments that start with a ## and are at the end of a target definition line. +.PHONY: help +help: ## Display command usage + @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index 434d0698a..cec096a47 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ +[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![image](https://img.shields.io/pypi/v/testcontainers.svg)](https://pypi.python.org/pypi/testcontainers) -[![image](https://img.shields.io/pypi/l/testcontainers.svg)](https://github.com/testcontainers/testcontainers-python/blob/main/LICENSE) -[![image](https://img.shields.io/pypi/pyversions/testcontainers.svg)](https://pypi.python.org/pypi/testcontainers) +![PyPI - Version](https://img.shields.io/pypi/v/testcontainers) +[![PyPI - License](https://img.shields.io/pypi/l/testcontainers.svg)](https://github.com/testcontainers/testcontainers-python/blob/main/LICENSE) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/testcontainers.svg)](https://pypi.python.org/pypi/testcontainers) [![codecov](https://codecov.io/gh/testcontainers/testcontainers-python/branch/master/graph/badge.svg)](https://codecov.io/gh/testcontainers/testcontainers-python) +![Core Tests](https://github.com/testcontainers/testcontainers-python/actions/workflows/ci-core.yml/badge.svg) +![Community Tests](https://github.com/testcontainers/testcontainers-python/actions/workflows/ci-community.yml/badge.svg) +[![Docs](https://readthedocs.org/projects/testcontainers-python/badge/?version=latest)](http://testcontainers-python.readthedocs.io/en/latest/?badge=latest) +[![Codespace](https://github.com/codespaces/badge.svg)](https://codespaces.new/testcontainers/testcontainers-python) # Testcontainers Python @@ -30,6 +35,10 @@ For more information, see [the docs][readthedocs]. The snippet above will spin up a postgres database in a container. The `get_connection_url()` convenience method returns a `sqlalchemy` compatible url we use to connect to the database and retrieve the database version. +## Contributing / Development / Release + +See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more details. + ## Configuration | Env Variable | Example | Description | diff --git a/index.rst b/index.rst index 6e7ed596c..af3142831 100644 --- a/index.rst +++ b/index.rst @@ -60,17 +60,20 @@ Installation ------------ The suite of testcontainers packages is available on `PyPI `_, -and individual packages can be installed using :code:`pip`. +and the package can be installed using :code:`pip`. -Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsutainable to maintain ownership. +Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsustainable to maintain ownership. Instead packages can be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[postgres]`. +Please note, that community modules are supported on a best-effort basis and breaking changes DO NOT create major versions in the package. +Therefore, only the package core is strictly following SemVer. If your workflow is broken by a minor update, please look at the changelogs for guidance. + Docker in Docker (DinD) ----------------------- -When trying to launch a testcontainer from within a Docker container, e.g., in continuous integration testing, two things have to be provided: +When trying to launch Testcontainers from within a Docker container, e.g., in continuous integration testing, two things have to be provided: 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. @@ -124,17 +127,21 @@ Configuration Development and Contributing ---------------------------- -We recommend you use a `virtual environment `_ for development (:code:`python>=3.7` is required). After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. +We recommend you use a `Poetry `_ for development. +After having installed `poetry`, you can run the following snippet to set up your local dev environment. .. code-block:: bash - poetry install --all-extras - make /tests + make install Package Structure ^^^^^^^^^^^^^^^^^ -Testcontainers is a collection of `implicit namespace packages `__ to decouple the development of different extensions, e.g., :code:`testcontainers-mysql` and :code:`testcontainers-postgres` for MySQL and PostgreSQL database containers, respectively. The folder structure is as follows. +Testcontainers is a collection of `implicit namespace packages `__ +to decouple the development of different extensions, +e.g., :code:`testcontainers[mysql]` and :code:`testcontainers[postgres]` for MySQL and PostgreSQL database containers, respectively. + +The folder structure is as follows: .. code-block:: bash @@ -154,12 +161,11 @@ Testcontainers is a collection of `implicit namespace packages `_. +You want to contribute a new feature or container? Great! +- We recommend you first `open an issue `_ +- Then follow the suggestions from the team +- We also have a Pull Request `template `_ for new containers! From e575b28da912147c5b806abab40a0c92329e2eb7 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Fri, 28 Jun 2024 11:34:41 +0300 Subject: [PATCH 395/425] feat(core): Added Generic module (#612) As part of the effort described, detailed and presented on https://github.com/testcontainers/testcontainers-python/pull/559 This is the third PR (out of 4) that should provide all the groundwork to support containers running a server. As discussed on #595 this PR aims to refactor the `ServerContainer` under a new dedicated module called "generic". ![image](https://github.com/testcontainers/testcontainers-python/assets/7189138/b7a3395b-ce3c-40ef-8baa-dfa3eff1b056) The idea is that this module could include multiple generic implementations such as ```server.py``` with the proper documentation and examples to allow users simpler usage and QOL. This PR adds the original FastAPI implementation as a simple doc example, I think this aligns better following #595 Next in line is ```feat(core): Added AWS Lambda module``` Based on the work done on https://github.com/testcontainers/testcontainers-python/pull/585 and #595 Expended from issue https://github.com/testcontainers/testcontainers-python/issues/83 --- Please note an extra commit is included to simulate the relations when importing between and with other modules. --- core/README.rst | 26 +++--- core/testcontainers/core/generic.py | 71 +--------------- index.rst | 11 +++ modules/generic/README.rst | 20 +++++ .../testcontainers/generic/__init__.py | 1 + .../generic/testcontainers/generic/server.py | 80 +++++++++++++++++++ modules/generic/tests/conftest.py | 22 +++++ .../generic/tests/samples/fastapi/Dockerfile | 11 +++ .../tests/samples/fastapi/app/__init__.py | 0 .../generic/tests/samples/fastapi/app/main.py | 8 ++ .../tests/samples}/python_server/Dockerfile | 0 .../generic/tests/test_generic.py | 14 +++- modules/testmoduleimport/README.rst | 2 + .../testmoduleimport/__init__.py | 1 + .../testmoduleimport/new_sub_module.py | 27 +++++++ .../testmoduleimport/tests/test_mock_one.py | 15 ++++ poetry.lock | 5 +- pyproject.toml | 7 +- 18 files changed, 232 insertions(+), 89 deletions(-) create mode 100644 modules/generic/README.rst create mode 100644 modules/generic/testcontainers/generic/__init__.py create mode 100644 modules/generic/testcontainers/generic/server.py create mode 100644 modules/generic/tests/conftest.py create mode 100644 modules/generic/tests/samples/fastapi/Dockerfile create mode 100644 modules/generic/tests/samples/fastapi/app/__init__.py create mode 100644 modules/generic/tests/samples/fastapi/app/main.py rename {core/tests/image_fixtures => modules/generic/tests/samples}/python_server/Dockerfile (100%) rename core/tests/test_generics.py => modules/generic/tests/test_generic.py (74%) create mode 100644 modules/testmoduleimport/README.rst create mode 100644 modules/testmoduleimport/testcontainers/testmoduleimport/__init__.py create mode 100644 modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py create mode 100644 modules/testmoduleimport/tests/test_mock_one.py diff --git a/core/README.rst b/core/README.rst index 8479efac8..8cc9a2780 100644 --- a/core/README.rst +++ b/core/README.rst @@ -5,7 +5,18 @@ Testcontainers Core .. autoclass:: testcontainers.core.container.DockerContainer -Using `DockerContainer` and `DockerImage` directly: +.. autoclass:: testcontainers.core.image.DockerImage + +.. autoclass:: testcontainers.core.generic.DbContainer + +.. raw:: html + +
+ +Examples +-------- + +Using `DockerContainer` and `DockerImage` to create a container: .. doctest:: @@ -17,14 +28,5 @@ Using `DockerContainer` and `DockerImage` directly: ... with DockerContainer(str(image)) as container: ... delay = wait_for_logs(container, "Test Sample Image") ---- - -.. autoclass:: testcontainers.core.image.DockerImage - ---- - -.. autoclass:: testcontainers.core.generic.ServerContainer - ---- - -.. autoclass:: testcontainers.core.generic.DbContainer +The `DockerImage` class is used to build the image from the specified path and tag. +The `DockerContainer` class is then used to create a container from the image. diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 11456a515..b2cd3010d 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -10,14 +10,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from typing import Optional, Union -from urllib.error import HTTPError +from typing import Optional from urllib.parse import quote -from urllib.request import urlopen from testcontainers.core.container import DockerContainer from testcontainers.core.exceptions import ContainerStartException -from testcontainers.core.image import DockerImage from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready @@ -84,69 +81,3 @@ def _configure(self) -> None: def _transfer_seed(self) -> None: pass - - -class ServerContainer(DockerContainer): - """ - **DEPRECATED - will be moved from core to a module (stay tuned for a final/stable import location)** - - Container for a generic server that is based on a custom image. - - Example: - - .. doctest:: - - >>> import httpx - >>> from testcontainers.core.generic import ServerContainer - >>> from testcontainers.core.waiting_utils import wait_for_logs - >>> from testcontainers.core.image import DockerImage - - >>> with DockerImage(path="./core/tests/image_fixtures/python_server", tag="test-srv:latest") as image: - ... with ServerContainer(port=9000, image=image) as srv: - ... url = srv._create_connection_url() - ... response = httpx.get(f"{url}", timeout=5) - ... assert response.status_code == 200, "Response status code is not 200" - ... delay = wait_for_logs(srv, "GET / HTTP/1.1") - - - :param path: Path to the Dockerfile to build the image - :param tag: Tag for the image to be built (default: None) - """ - - def __init__(self, port: int, image: Union[str, DockerImage]) -> None: - super().__init__(str(image)) - self.internal_port = port - self.with_exposed_ports(self.internal_port) - - @wait_container_is_ready(HTTPError) - def _connect(self) -> None: - # noinspection HttpUrlsUsage - url = self._create_connection_url() - try: - with urlopen(url) as r: - assert b"" in r.read() - except HTTPError as e: - # 404 is expected, as the server may not have the specific endpoint we are looking for - if e.code == 404: - pass - else: - raise - - def get_api_url(self) -> str: - raise NotImplementedError - - def _create_connection_url(self) -> str: - if self._container is None: - raise ContainerStartException("container has not been started") - host = self.get_container_host_ip() - exposed_port = self.get_exposed_port(self.internal_port) - url = f"http://{host}:{exposed_port}" - return url - - def start(self) -> "ServerContainer": - super().start() - self._connect() - return self - - def stop(self, force=True, delete_volume=True) -> None: - super().stop(force, delete_volume) diff --git a/index.rst b/index.rst index af3142831..ead699b2a 100644 --- a/index.rst +++ b/index.rst @@ -70,6 +70,17 @@ Please note, that community modules are supported on a best-effort basis and bre Therefore, only the package core is strictly following SemVer. If your workflow is broken by a minor update, please look at the changelogs for guidance. +Custom Containers +----------------- + +Crafting containers that are based on custom images is supported by the `core` module. Please check the `core documentation `_ for more information. + +This allows you to create containers from images that are not part of the modules provided by testcontainers-python. + +For common use cases, you can also use the generic containers provided by the `testcontainers-generic` module. Please check the `generic documentation `_ for more information. +(example: `ServerContainer` for running a FastAPI server) + + Docker in Docker (DinD) ----------------------- diff --git a/modules/generic/README.rst b/modules/generic/README.rst new file mode 100644 index 000000000..7e12da700 --- /dev/null +++ b/modules/generic/README.rst @@ -0,0 +1,20 @@ +:code:`testcontainers-generic` is a set of generic containers modules that can be used to creat containers. + +.. autoclass:: testcontainers.generic.ServerContainer +.. title:: testcontainers.generic.ServerContainer + +FastAPI container that is using :code:`ServerContainer` + +.. doctest:: + + >>> from testcontainers.generic import ServerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + + >>> with DockerImage(path="./modules/generic/tests/samples/fastapi", tag="fastapi-test:latest") as image: + ... with ServerContainer(port=80, image=image) as fastapi_server: + ... delay = wait_for_logs(fastapi_server, "Uvicorn running on http://0.0.0.0:80") + ... fastapi_server.get_api_url = lambda: fastapi_server._create_connection_url() + "/api/v1/" + ... client = fastapi_server.get_client() + ... response = client.get("/") + ... assert response.status_code == 200 + ... assert response.json() == {"Status": "Working"} diff --git a/modules/generic/testcontainers/generic/__init__.py b/modules/generic/testcontainers/generic/__init__.py new file mode 100644 index 000000000..f239a80c6 --- /dev/null +++ b/modules/generic/testcontainers/generic/__init__.py @@ -0,0 +1 @@ +from .server import ServerContainer # noqa: F401 diff --git a/modules/generic/testcontainers/generic/server.py b/modules/generic/testcontainers/generic/server.py new file mode 100644 index 000000000..03a546772 --- /dev/null +++ b/modules/generic/testcontainers/generic/server.py @@ -0,0 +1,80 @@ +from typing import Union +from urllib.error import HTTPError +from urllib.request import urlopen + +import httpx + +from testcontainers.core.container import DockerContainer +from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.image import DockerImage +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class ServerContainer(DockerContainer): + """ + Container for a generic server that is based on a custom image. + + Example: + + .. doctest:: + + >>> import httpx + >>> from testcontainers.generic import ServerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-srv:latest") as image: + ... with ServerContainer(port=9000, image=image) as srv: + ... url = srv._create_connection_url() + ... response = httpx.get(f"{url}", timeout=5) + ... assert response.status_code == 200, "Response status code is not 200" + ... delay = wait_for_logs(srv, "GET / HTTP/1.1") + + + :param path: Path to the Dockerfile to build the image + :param tag: Tag for the image to be built (default: None) + """ + + def __init__(self, port: int, image: Union[str, DockerImage]) -> None: + super().__init__(str(image)) + self.internal_port = port + self.with_exposed_ports(self.internal_port) + + @wait_container_is_ready(HTTPError) + def _connect(self) -> None: + # noinspection HttpUrlsUsage + url = self._create_connection_url() + try: + with urlopen(url) as r: + assert b"" in r.read() + except HTTPError as e: + # 404 is expected, as the server may not have the specific endpoint we are looking for + if e.code == 404: + pass + else: + raise + + def get_api_url(self) -> str: + raise NotImplementedError + + def _create_connection_url(self) -> str: + if self._container is None: + raise ContainerStartException("container has not been started") + host = self.get_container_host_ip() + exposed_port = self.get_exposed_port(self.internal_port) + url = f"http://{host}:{exposed_port}" + return url + + def start(self) -> "ServerContainer": + super().start() + self._connect() + return self + + def stop(self, force=True, delete_volume=True) -> None: + super().stop(force, delete_volume) + + def get_client(self) -> httpx.Client: + return httpx.Client(base_url=self.get_api_url()) + + def get_stdout(self) -> str: + return self.get_logs()[0].decode("utf-8") diff --git a/modules/generic/tests/conftest.py b/modules/generic/tests/conftest.py new file mode 100644 index 000000000..4f69565f4 --- /dev/null +++ b/modules/generic/tests/conftest.py @@ -0,0 +1,22 @@ +import pytest +from typing import Callable +from testcontainers.core.container import DockerClient + + +@pytest.fixture +def check_for_image() -> Callable[[str, bool], None]: + """Warp the check_for_image function in a fixture""" + + def _check_for_image(image_short_id: str, cleaned: bool) -> None: + """ + Validates if the image is present or not. + + :param image_short_id: The short id of the image + :param cleaned: True if the image should not be present, False otherwise + """ + client = DockerClient() + images = client.client.images.list() + found = any(image.short_id.endswith(image_short_id) for image in images) + assert found is not cleaned, f'Image {image_short_id} was {"found" if cleaned else "not found"}' + + return _check_for_image diff --git a/modules/generic/tests/samples/fastapi/Dockerfile b/modules/generic/tests/samples/fastapi/Dockerfile new file mode 100644 index 000000000..f56288cd5 --- /dev/null +++ b/modules/generic/tests/samples/fastapi/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.9 + +WORKDIR /app + +RUN pip install fastapi + +COPY ./app /app + +EXPOSE 80 + +CMD ["fastapi", "run", "main.py", "--port", "80"] diff --git a/modules/generic/tests/samples/fastapi/app/__init__.py b/modules/generic/tests/samples/fastapi/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/generic/tests/samples/fastapi/app/main.py b/modules/generic/tests/samples/fastapi/app/main.py new file mode 100644 index 000000000..f96073d9f --- /dev/null +++ b/modules/generic/tests/samples/fastapi/app/main.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/api/v1/") +def read_root(): + return {"Status": "Working"} diff --git a/core/tests/image_fixtures/python_server/Dockerfile b/modules/generic/tests/samples/python_server/Dockerfile similarity index 100% rename from core/tests/image_fixtures/python_server/Dockerfile rename to modules/generic/tests/samples/python_server/Dockerfile diff --git a/core/tests/test_generics.py b/modules/generic/tests/test_generic.py similarity index 74% rename from core/tests/test_generics.py rename to modules/generic/tests/test_generic.py index 340ac6655..5943b4a4d 100644 --- a/core/tests/test_generics.py +++ b/modules/generic/tests/test_generic.py @@ -7,17 +7,17 @@ from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.core.image import DockerImage -from testcontainers.core.generic import ServerContainer +from testcontainers.generic import ServerContainer TEST_DIR = Path(__file__).parent @pytest.mark.parametrize("test_image_cleanup", [True, False]) @pytest.mark.parametrize("test_image_tag", [None, "custom-image:test"]) -def test_srv_container(test_image_tag: Optional[str], test_image_cleanup: bool, check_for_image, port=9000): +def test_server_container(test_image_tag: Optional[str], test_image_cleanup: bool, check_for_image, port=9000): with ( DockerImage( - path=TEST_DIR / "image_fixtures/python_server", + path=TEST_DIR / "samples/python_server", tag=test_image_tag, clean_up=test_image_cleanup, # @@ -37,8 +37,14 @@ def test_srv_container(test_image_tag: Optional[str], test_image_cleanup: bool, check_for_image(image_short_id, test_image_cleanup) +def test_server_container_no_port(): + with pytest.raises(TypeError): + with ServerContainer(path="./modules/generic/tests/samples/python_server", tag="test-srv:latest"): + pass + + def test_like_doctest(): - with DockerImage(path=TEST_DIR / "image_fixtures/python_server", tag="test-srv:latest") as image: + with DockerImage(path=TEST_DIR / "samples/python_server", tag="test-srv:latest") as image: with ServerContainer(port=9000, image=image) as srv: url = srv._create_connection_url() response = get(f"{url}", timeout=5) diff --git a/modules/testmoduleimport/README.rst b/modules/testmoduleimport/README.rst new file mode 100644 index 000000000..ae5d5708a --- /dev/null +++ b/modules/testmoduleimport/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.testmoduleimport.NewSubModuleContainer +.. title:: testcontainers.testmoduleimport.NewSubModuleContainer diff --git a/modules/testmoduleimport/testcontainers/testmoduleimport/__init__.py b/modules/testmoduleimport/testcontainers/testmoduleimport/__init__.py new file mode 100644 index 000000000..74074699e --- /dev/null +++ b/modules/testmoduleimport/testcontainers/testmoduleimport/__init__.py @@ -0,0 +1 @@ +from .new_sub_module import NewSubModuleContainer # noqa: F401 diff --git a/modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py b/modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py new file mode 100644 index 000000000..f45796f76 --- /dev/null +++ b/modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py @@ -0,0 +1,27 @@ +from testcontainers.generic.server import ServerContainer + + +class NewSubModuleContainer(ServerContainer): + """ + This class is a mock container for testing purposes. It is used to test importing from other modules. + + .. doctest:: + + >>> import httpx + >>> from testcontainers.core.image import DockerImage + >>> from testcontainers.testmoduleimport import NewSubModuleContainer + + >>> with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-mod:latest") as image: + ... with NewSubModuleContainer(port=9000, image=image) as srv: + ... url = srv._create_connection_url() + ... response = httpx.get(f"{url}", timeout=5) + ... assert response.status_code == 200, "Response status code is not 200" + ... assert srv.print_mock() == "NewSubModuleContainer" + + """ + + def __init__(self, port: int, image: str) -> None: + super().__init__(port, image) + + def print_mock(self) -> str: + return "NewSubModuleContainer" diff --git a/modules/testmoduleimport/tests/test_mock_one.py b/modules/testmoduleimport/tests/test_mock_one.py new file mode 100644 index 000000000..85ac6c315 --- /dev/null +++ b/modules/testmoduleimport/tests/test_mock_one.py @@ -0,0 +1,15 @@ +import httpx + +from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.image import DockerImage +from testcontainers.testmoduleimport import NewSubModuleContainer + + +def test_like_doctest(): + with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-srv:latest") as image: + with NewSubModuleContainer(port=9000, image=image) as srv: + assert srv.print_mock() == "NewSubModuleContainer" + url = srv._create_connection_url() + response = httpx.get(f"{url}", timeout=5) + assert response.status_code == 200, "Response status code is not 200" + _ = wait_for_logs(srv, "GET / HTTP/1.1") diff --git a/poetry.lock b/poetry.lock index b2cb7e81f..aa5fdc29b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1898,7 +1898,6 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, - {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -4464,6 +4463,7 @@ chroma = ["chromadb-client"] clickhouse = ["clickhouse-driver"] cockroachdb = [] elasticsearch = [] +generic = ["httpx"] google = ["google-cloud-datastore", "google-cloud-pubsub"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] @@ -4490,10 +4490,11 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] +testmoduleimport = ["httpx"] vault = [] weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "6f7697a84a674802e30ceea61276d800b6b98224863a0c512138447d9b4af524" +content-hash = "e07f8edf8cefba872bbf48dcfa187163cefb00a60122daa62de8891b61fc55de" diff --git a/pyproject.toml b/pyproject.toml index cf9a3710c..0b6088954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,8 @@ packages = [ { include = "testcontainers", from = "modules/clickhouse" }, { include = "testcontainers", from = "modules/cockroachdb" }, { include = "testcontainers", from = "modules/elasticsearch" }, + { include = "testcontainers", from = "modules/generic" }, + { include = "testcontainers", from = "modules/testmoduleimport"}, { include = "testcontainers", from = "modules/google" }, { include = "testcontainers", from = "modules/influxdb" }, { include = "testcontainers", from = "modules/k3s" }, @@ -61,7 +63,7 @@ packages = [ { include = "testcontainers", from = "modules/registry" }, { include = "testcontainers", from = "modules/selenium" }, { include = "testcontainers", from = "modules/vault" }, - { include = "testcontainers", from = "modules/weaviate" } + { include = "testcontainers", from = "modules/weaviate" }, ] [tool.poetry.urls] @@ -103,6 +105,7 @@ weaviate-client = { version = "^4.5.4", optional = true } chromadb-client = { version = "*", optional = true } qdrant-client = { version = "*", optional = true } bcrypt = { version = "*", optional = true } +httpx = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -111,6 +114,8 @@ cassandra = [] clickhouse = ["clickhouse-driver"] cockroachdb = [] elasticsearch = [] +generic = ["httpx"] +testmoduleimport = ["httpx"] google = ["google-cloud-pubsub", "google-cloud-datastore"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] From 8045a806fcb6908567339a14f2f0d7a169461675 Mon Sep 17 00:00:00 2001 From: Mehdi Ben Abdallah Date: Fri, 28 Jun 2024 11:03:46 +0200 Subject: [PATCH 396/425] fix(cosmosdb): Add support for the CosmosDB Emulator (#579) Adds support for the [CosmosDB Emulator container](https://learn.microsoft.com/en-us/azure/cosmos-db/emulator) --------- Co-authored-by: Mehdi BEN ABDALLAH <@mbenabda> Co-authored-by: David Ankin --- index.rst | 82 ++++--------- modules/cosmosdb/README.rst | 5 + .../testcontainers/cosmosdb/__init__.py | 4 + .../testcontainers/cosmosdb/_emulator.py | 110 ++++++++++++++++++ .../cosmosdb/testcontainers/cosmosdb/_grab.py | 26 +++++ .../testcontainers/cosmosdb/mongodb.py | 47 ++++++++ .../cosmosdb/testcontainers/cosmosdb/nosql.py | 69 +++++++++++ modules/cosmosdb/tests/test_emulator.py | 8 ++ modules/cosmosdb/tests/test_mongodb.py | 16 +++ modules/cosmosdb/tests/test_nosql.py | 7 ++ poetry.lock | 18 ++- pyproject.toml | 3 + 12 files changed, 333 insertions(+), 62 deletions(-) create mode 100644 modules/cosmosdb/README.rst create mode 100644 modules/cosmosdb/testcontainers/cosmosdb/__init__.py create mode 100644 modules/cosmosdb/testcontainers/cosmosdb/_emulator.py create mode 100644 modules/cosmosdb/testcontainers/cosmosdb/_grab.py create mode 100644 modules/cosmosdb/testcontainers/cosmosdb/mongodb.py create mode 100644 modules/cosmosdb/testcontainers/cosmosdb/nosql.py create mode 100644 modules/cosmosdb/tests/test_emulator.py create mode 100644 modules/cosmosdb/tests/test_mongodb.py create mode 100644 modules/cosmosdb/tests/test_nosql.py diff --git a/index.rst b/index.rst index ead699b2a..8c02832fe 100644 --- a/index.rst +++ b/index.rst @@ -13,7 +13,6 @@ testcontainers-python testcontainers-python facilitates the use of Docker containers for functional and integration testing. The collection of packages currently supports the following features. .. toctree:: - :maxdepth: 1 core/README modules/index @@ -60,15 +59,12 @@ Installation ------------ The suite of testcontainers packages is available on `PyPI `_, -and the package can be installed using :code:`pip`. +and individual packages can be installed using :code:`pip`. -Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsustainable to maintain ownership. +Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsutainable to maintain ownership. Instead packages can be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[postgres]`. -Please note, that community modules are supported on a best-effort basis and breaking changes DO NOT create major versions in the package. -Therefore, only the package core is strictly following SemVer. If your workflow is broken by a minor update, please look at the changelogs for guidance. - Custom Containers ----------------- @@ -84,75 +80,40 @@ For common use cases, you can also use the generic containers provided by the `t Docker in Docker (DinD) ----------------------- -When trying to launch Testcontainers from within a Docker container, e.g., in continuous integration testing, two things have to be provided: +When trying to launch a testcontainer from within a Docker container, e.g., in continuous integration testing, two things have to be provided: 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. -Private Docker registry ------------------------ - -Using a private docker registry requires the `DOCKER_AUTH_CONFIG` environment variable to be set. -`official documentation `_ - -The value of this variable should be a JSON string containing the authentication information for the registry. - -Example: - -.. code-block:: bash - - DOCKER_AUTH_CONFIG='{"auths": {"https://myregistry.com": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="}}}' - -In order to generate the JSON string, you can use the following command: - -.. code-block:: bash - - echo -n '{"auths": {"": {"auth": "'$(echo -n ":" | base64 -w 0)'"}}}' - -Fetching passwords from cloud providers: - -.. code-block:: bash - - ECR_PASSWORD = $(aws ecr get-login-password --region eu-west-1) - GCP_PASSWORD = $(gcloud auth print-access-token) - AZURE_PASSWORD = $(az acr login --name --expose-token --output tsv) - - Configuration ------------- -+-------------------------------------------+---------------------------------------------------+------------------------------------------+ -| Env Variable | Example | Description | -+===========================================+===================================================+==========================================+ -| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | -+-------------------------------------------+---------------------------------------------------+------------------------------------------+ -| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | -+-------------------------------------------+---------------------------------------------------+------------------------------------------+ -| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | -+-------------------------------------------+---------------------------------------------------+------------------------------------------+ -| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | -+-------------------------------------------+---------------------------------------------------+------------------------------------------+ -| ``DOCKER_AUTH_CONFIG`` | ``{"auths": {"": {"auth": ""}}}`` | Custom registry auth config | -+-------------------------------------------+---------------------------------------------------+------------------------------------------+ ++-------------------------------------------+-------------------------------+------------------------------------------+ +| Env Variable | Example | Description | ++===========================================+===============================+==========================================+ +| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | ++-------------------------------------------+-------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | ++-------------------------------------------+-------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | ++-------------------------------------------+-------------------------------+------------------------------------------+ +| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | ++-------------------------------------------+-------------------------------+------------------------------------------+ Development and Contributing ---------------------------- -We recommend you use a `Poetry `_ for development. -After having installed `poetry`, you can run the following snippet to set up your local dev environment. +We recommend you use a `virtual environment `_ for development (:code:`python>=3.7` is required). After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. .. code-block:: bash - make install + poetry install --all-extras + make /tests Package Structure ^^^^^^^^^^^^^^^^^ -Testcontainers is a collection of `implicit namespace packages `__ -to decouple the development of different extensions, -e.g., :code:`testcontainers[mysql]` and :code:`testcontainers[postgres]` for MySQL and PostgreSQL database containers, respectively. - -The folder structure is as follows: +Testcontainers is a collection of `implicit namespace packages `__ to decouple the development of different extensions, e.g., :code:`testcontainers-mysql` and :code:`testcontainers-postgres` for MySQL and PostgreSQL database containers, respectively. The folder structure is as follows. .. code-block:: bash @@ -172,11 +133,10 @@ The folder structure is as follows: ... # README for this feature. README.rst + # Setup script for this feature. + setup.py Contributing a New Feature ^^^^^^^^^^^^^^^^^^^^^^^^^^ -You want to contribute a new feature or container? Great! -- We recommend you first `open an issue `_ -- Then follow the suggestions from the team -- We also have a Pull Request `template `_ for new containers! +You want to contribute a new feature or container? Great! You can do that in six steps as outlined `here __`. diff --git a/modules/cosmosdb/README.rst b/modules/cosmosdb/README.rst new file mode 100644 index 000000000..802cffa4e --- /dev/null +++ b/modules/cosmosdb/README.rst @@ -0,0 +1,5 @@ +.. autoclass:: testcontainers.cosmosdb.CosmosDBMongoEndpointContainer +.. title:: testcontainers.cosmosdb.CosmosDBMongoEndpointContainer + +.. autoclass:: testcontainers.cosmosdb.CosmosDBNoSQLEndpointContainer +.. title:: testcontainers.cosmosdb.CosmosDBNoSQLEndpointContainer diff --git a/modules/cosmosdb/testcontainers/cosmosdb/__init__.py b/modules/cosmosdb/testcontainers/cosmosdb/__init__.py new file mode 100644 index 000000000..619ddb3b4 --- /dev/null +++ b/modules/cosmosdb/testcontainers/cosmosdb/__init__.py @@ -0,0 +1,4 @@ +from .mongodb import CosmosDBMongoEndpointContainer +from .nosql import CosmosDBNoSQLEndpointContainer + +__all__ = ["CosmosDBMongoEndpointContainer", "CosmosDBNoSQLEndpointContainer"] diff --git a/modules/cosmosdb/testcontainers/cosmosdb/_emulator.py b/modules/cosmosdb/testcontainers/cosmosdb/_emulator.py new file mode 100644 index 000000000..161a01c29 --- /dev/null +++ b/modules/cosmosdb/testcontainers/cosmosdb/_emulator.py @@ -0,0 +1,110 @@ +import os +import socket +import ssl +from collections.abc import Iterable +from distutils.util import strtobool +from urllib.error import HTTPError, URLError +from urllib.request import urlopen + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + +from . import _grab as grab + +__all__ = ["CosmosDBEmulatorContainer"] + +EMULATOR_PORT = 8081 + + +class CosmosDBEmulatorContainer(DockerContainer): + """ + Abstract class for CosmosDB Emulator endpoints. + + Concrete implementations for each endpoint is provided by a separate class: + NoSQLEmulatorContainer and MongoDBEmulatorContainer. + """ + + def __init__( + self, + image: str = os.getenv( + "AZURE_COSMOS_EMULATOR_IMAGE", "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest" + ), + partition_count: int = os.getenv("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", None), + enable_data_persistence: bool = strtobool(os.getenv("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")), + key: str = os.getenv( + "AZURE_COSMOS_EMULATOR_KEY", + "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", + ), + bind_ports: bool = strtobool(os.getenv("AZURE_COSMOS_EMULATOR_BIND_PORTS", "true")), + endpoint_ports: Iterable[int] = [], + **other_kwargs, + ): + super().__init__(image=image, **other_kwargs) + self.endpoint_ports = endpoint_ports + self.partition_count = partition_count + self.key = key + self.enable_data_persistence = enable_data_persistence + self.bind_ports = bind_ports + + @property + def host(self) -> str: + """ + Emulator host + """ + return self.get_container_host_ip() + + @property + def server_certificate_pem(self) -> bytes: + """ + PEM-encoded server certificate + """ + return self._cert_pem_bytes + + def start(self) -> Self: + self._configure() + super().start() + self._wait_until_ready() + self._cert_pem_bytes = self._download_cert() + return self + + def _configure(self) -> None: + all_ports = {EMULATOR_PORT, *self.endpoint_ports} + if self.bind_ports: + for port in all_ports: + self.with_bind_ports(port, port) + else: + self.with_exposed_ports(*all_ports) + + ( + self.with_env("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", str(self.partition_count)) + .with_env("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", socket.gethostbyname(socket.gethostname())) + .with_env("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", str(self.enable_data_persistence)) + .with_env("AZURE_COSMOS_EMULATOR_KEY", str(self.key)) + ) + + def _wait_until_ready(self) -> Self: + wait_for_logs(container=self, predicate="Started\\s*$") + + if self.bind_ports: + self._wait_for_url(f"https://{self.host}:{EMULATOR_PORT}/_explorer/index.html") + self._wait_for_query_success() + + return self + + def _download_cert(self) -> bytes: + with grab.file( + self.get_wrapped_container(), + "/tmp/cosmos/appdata/.system/profiles/Client/AppData/Local/CosmosDBEmulator/emulator.pem", + ) as cert: + return cert.read() + + @wait_container_is_ready(HTTPError, URLError) + def _wait_for_url(self, url: str) -> Self: + with urlopen(url, context=ssl._create_unverified_context()) as response: + response.read() + return self + + def _wait_for_query_success(self) -> None: + pass diff --git a/modules/cosmosdb/testcontainers/cosmosdb/_grab.py b/modules/cosmosdb/testcontainers/cosmosdb/_grab.py new file mode 100644 index 000000000..e1895019a --- /dev/null +++ b/modules/cosmosdb/testcontainers/cosmosdb/_grab.py @@ -0,0 +1,26 @@ +import tarfile +import tempfile +from contextlib import contextmanager +from os import path +from pathlib import Path + +from docker.models.containers import Container + + +@contextmanager +def file(container: Container, target: str): + target_path = Path(target) + assert target_path.is_absolute(), "target must be an absolute path" + + with tempfile.TemporaryDirectory() as tmp: + archive = Path(tmp) / "grabbed.tar" + + # download from container as tar archive + with open(archive, "wb") as f: + tar_bits, _ = container.get_archive(target) + for chunk in tar_bits: + f.write(chunk) + + # extract target file from tar archive + with tarfile.TarFile(archive) as tar: + yield tar.extractfile(path.basename(target)) diff --git a/modules/cosmosdb/testcontainers/cosmosdb/mongodb.py b/modules/cosmosdb/testcontainers/cosmosdb/mongodb.py new file mode 100644 index 000000000..82e8c096b --- /dev/null +++ b/modules/cosmosdb/testcontainers/cosmosdb/mongodb.py @@ -0,0 +1,47 @@ +import os + +from ._emulator import CosmosDBEmulatorContainer + +__all__ = ["CosmosDBMongoEndpointContainer"] + +ENDPOINT_PORT = 10255 + + +class CosmosDBMongoEndpointContainer(CosmosDBEmulatorContainer): + """ + CosmosDB MongoDB enpoint Emulator. + + Example: + + .. code-block:: python + + >>> from testcontainers.cosmosdb import CosmosDBMongoEndpointContainer + + >>> with CosmosDBMongoEndpointContainer(mongodb_version="4.0") as emulator: + ... print(f"Point your MongoDB client at {emulator.host}:{emulator.port} using key {emulator.key}") + ... print(f"and eiher disable TLS server auth or trust the server's self signed cert (emulator.server_certificate_pem)") + + """ + + def __init__( + self, + mongodb_version: str, + image: str = os.getenv( + "AZURE_COSMOS_EMULATOR_IMAGE", "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:mongodb" + ), + **other_kwargs, + ): + super().__init__(image=image, endpoint_ports=[ENDPOINT_PORT], **other_kwargs) + assert mongodb_version is not None, "A MongoDB version is required to use the MongoDB Endpoint" + self.mongodb_version = mongodb_version + + @property + def port(self) -> str: + """ + The exposed port to the MongoDB endpoint + """ + return self.get_exposed_port(ENDPOINT_PORT) + + def _configure(self) -> None: + super()._configure() + self.with_env("AZURE_COSMOS_EMULATOR_ENABLE_MONGODB_ENDPOINT", self.mongodb_version) diff --git a/modules/cosmosdb/testcontainers/cosmosdb/nosql.py b/modules/cosmosdb/testcontainers/cosmosdb/nosql.py new file mode 100644 index 000000000..f78469674 --- /dev/null +++ b/modules/cosmosdb/testcontainers/cosmosdb/nosql.py @@ -0,0 +1,69 @@ +from azure.core.exceptions import ServiceRequestError +from azure.cosmos import CosmosClient as SyncCosmosClient +from azure.cosmos.aio import CosmosClient as AsyncCosmosClient + +from testcontainers.core.waiting_utils import wait_container_is_ready + +from ._emulator import CosmosDBEmulatorContainer + +__all__ = ["CosmosDBNoSQLEndpointContainer"] + +NOSQL_PORT = 8081 + + +class CosmosDBNoSQLEndpointContainer(CosmosDBEmulatorContainer): + """ + CosmosDB NoSQL enpoint Emulator. + + Example: + + .. code-block:: python + + >>> from testcontainers.cosmosdb import CosmosDBNoSQLEndpointContainer + >>> with CosmosDBNoSQLEndpointContainer() as emulator: + ... db = emulator.insecure_sync_client().create_database_if_not_exists("test") + + .. code-block:: python + + >>> from testcontainers.cosmosdb import CosmosDBNoSQLEndpointContainer + >>> from azure.cosmos import CosmosClient + + >>> with CosmosDBNoSQLEndpointContainer() as emulator: + ... client = CosmosClient(url=emulator.url, credential=emulator.key, connection_verify=False) + ... db = client.create_database_if_not_exists("test") + + """ + + def __init__(self, **kwargs): + super().__init__(endpoint_ports=[NOSQL_PORT], **kwargs) + + @property + def port(self) -> str: + """ + The exposed port to the NoSQL endpoint + """ + return self.get_exposed_port(NOSQL_PORT) + + @property + def url(self) -> str: + """ + The url to the NoSQL endpoint + """ + return f"https://{self.host}:{self.port}" + + def insecure_async_client(self): + """ + Returns an asynchronous CosmosClient instance + """ + return AsyncCosmosClient(url=self.url, credential=self.key, connection_verify=False) + + def insecure_sync_client(self): + """ + Returns a synchronous CosmosClient instance + """ + return SyncCosmosClient(url=self.url, credential=self.key, connection_verify=False) + + @wait_container_is_ready(ServiceRequestError) + def _wait_for_query_success(self) -> None: + with self.insecure_sync_client() as c: + list(c.list_databases()) diff --git a/modules/cosmosdb/tests/test_emulator.py b/modules/cosmosdb/tests/test_emulator.py new file mode 100644 index 000000000..542ddd11c --- /dev/null +++ b/modules/cosmosdb/tests/test_emulator.py @@ -0,0 +1,8 @@ +import pytest +from testcontainers.cosmosdb._emulator import CosmosDBEmulatorContainer + + +def test_runs(): + with CosmosDBEmulatorContainer(partition_count=1, bind_ports=False) as emulator: + assert emulator.server_certificate_pem is not None + assert emulator.get_exposed_port(8081) is not None diff --git a/modules/cosmosdb/tests/test_mongodb.py b/modules/cosmosdb/tests/test_mongodb.py new file mode 100644 index 000000000..a50ee82ea --- /dev/null +++ b/modules/cosmosdb/tests/test_mongodb.py @@ -0,0 +1,16 @@ +import pytest +from testcontainers.cosmosdb import CosmosDBMongoEndpointContainer + + +def test_requires_a_version(): + with pytest.raises(AssertionError, match="A MongoDB version is required"): + CosmosDBMongoEndpointContainer(mongodb_version=None) + + # instanciates + CosmosDBMongoEndpointContainer(mongodb_version="4.0") + + +def test_runs(): + with CosmosDBMongoEndpointContainer(mongodb_version="4.0", partition_count=1, bind_ports=False) as emulator: + assert emulator.env["AZURE_COSMOS_EMULATOR_ENABLE_MONGODB_ENDPOINT"] == "4.0" + assert emulator.get_exposed_port(10255) is not None, "The MongoDB endpoint's port should be exposed" diff --git a/modules/cosmosdb/tests/test_nosql.py b/modules/cosmosdb/tests/test_nosql.py new file mode 100644 index 000000000..a9460a1b0 --- /dev/null +++ b/modules/cosmosdb/tests/test_nosql.py @@ -0,0 +1,7 @@ +import pytest +from testcontainers.cosmosdb import CosmosDBNoSQLEndpointContainer + + +def test_runs(): + with CosmosDBNoSQLEndpointContainer(partition_count=1, bind_ports=False) as emulator: + assert emulator.get_exposed_port(8081) is not None, "The NoSQL endpoint's port should be exposed" diff --git a/poetry.lock b/poetry.lock index aa5fdc29b..90a83f33f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -175,6 +175,21 @@ typing-extensions = ">=4.6.0" [package.extras] aio = ["aiohttp (>=3.0)"] +[[package]] +name = "azure-cosmos" +version = "4.7.0" +description = "Microsoft Azure Cosmos Client Library for Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "azure-cosmos-4.7.0.tar.gz", hash = "sha256:72d714033134656302a2e8957c4b93590673bd288b0ca60cb123e348ae99a241"}, + {file = "azure_cosmos-4.7.0-py3-none-any.whl", hash = "sha256:03d8c7740ddc2906fb16e07b136acc0fe6a6a02656db46c5dd6f1b127b58cc96"}, +] + +[package.dependencies] +azure-core = ">=1.25.1" +typing-extensions = ">=4.6.0" + [[package]] name = "azure-storage-blob" version = "12.19.1" @@ -4462,6 +4477,7 @@ cassandra = [] chroma = ["chromadb-client"] clickhouse = ["clickhouse-driver"] cockroachdb = [] +cosmosdb = ["azure-cosmos"] elasticsearch = [] generic = ["httpx"] google = ["google-cloud-datastore", "google-cloud-pubsub"] @@ -4497,4 +4513,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "e07f8edf8cefba872bbf48dcfa187163cefb00a60122daa62de8891b61fc55de" +content-hash = "2b87af7b69af2cc83f8198ab0fcfef7ceaf8411a8300c4ca72c0521e5d966445" diff --git a/pyproject.toml b/pyproject.toml index 0b6088954..c7a398d7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ packages = [ { include = "testcontainers", from = "modules/chroma" }, { include = "testcontainers", from = "modules/clickhouse" }, { include = "testcontainers", from = "modules/cockroachdb" }, + { include = "testcontainers", from = "modules/cosmosdb" }, { include = "testcontainers", from = "modules/elasticsearch" }, { include = "testcontainers", from = "modules/generic" }, { include = "testcontainers", from = "modules/testmoduleimport"}, @@ -106,12 +107,14 @@ chromadb-client = { version = "*", optional = true } qdrant-client = { version = "*", optional = true } bcrypt = { version = "*", optional = true } httpx = { version = "*", optional = true } +azure-cosmos = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] azurite = ["azure-storage-blob"] cassandra = [] clickhouse = ["clickhouse-driver"] +cosmosdb = ["azure-cosmos"] cockroachdb = [] elasticsearch = [] generic = ["httpx"] From a9621474d6b07465a1b83bf28c4b14c1a2fe7f96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 05:24:03 -0400 Subject: [PATCH 397/425] chore(main): release testcontainers 4.7.0 (#613) :robot: I have created a release *beep* *boop* --- ## [4.7.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.6.0...testcontainers-v4.7.0) (2024-06-28) ### Features * **core:** Added Generic module ([#612](https://github.com/testcontainers/testcontainers-python/issues/612)) ([e575b28](https://github.com/testcontainers/testcontainers-python/commit/e575b28da912147c5b806abab40a0c92329e2eb7)) * **core:** allow custom dockerfile path for image build and bypassing build cache ([#615](https://github.com/testcontainers/testcontainers-python/issues/615)) ([ead0f79](https://github.com/testcontainers/testcontainers-python/commit/ead0f797902a94d3b2558e489fe2a0a55c3bb7ad)), closes [#610](https://github.com/testcontainers/testcontainers-python/issues/610) * **core:** DockerCompose.stop now stops only services that it starts (does not stop the other services) ([#620](https://github.com/testcontainers/testcontainers-python/issues/620)) ([e711800](https://github.com/testcontainers/testcontainers-python/commit/e71180039441e3c7d49467298ef0f498fe786149)) ### Bug Fixes * **ollama:** Add support for ollama module ([#618](https://github.com/testcontainers/testcontainers-python/issues/618)) ([5442d05](https://github.com/testcontainers/testcontainers-python/commit/5442d054cb8bc11887e09d24e29d9f91dd943307)) * **cosmosdb:** Add support for the CosmosDB Emulator ([#579](https://github.com/testcontainers/testcontainers-python/issues/579)) ([8045a80](https://github.com/testcontainers/testcontainers-python/commit/8045a806fcb6908567339a14f2f0d7a169461675)) * improve ollama docs, s/ollama_dir/ollama_home/g ([#619](https://github.com/testcontainers/testcontainers-python/issues/619)) ([27f2a6b](https://github.com/testcontainers/testcontainers-python/commit/27f2a6bdca8b9c860a96920eebc96f53682ea750)) * **kafka:** Add Kraft to Kafka containers ([#611](https://github.com/testcontainers/testcontainers-python/issues/611)) ([762d2a2](https://github.com/testcontainers/testcontainers-python/commit/762d2a2130f7ce17dacaed5a96a6898a08cf2bc5)) ### Documentation * **contributing:** add contribution and new-container guide ([#460](https://github.com/testcontainers/testcontainers-python/issues/460)) ([3519f4b](https://github.com/testcontainers/testcontainers-python/commit/3519f4bdad6eac6c172977303b51cf52b4fa4c04)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 22 ++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index c69cd293a..e2f35d5a1 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.6.0" + ".": "4.7.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 120adbeee..00b3c38bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [4.7.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.6.0...testcontainers-v4.7.0) (2024-06-28) + + +### Features + +* **core:** Add support for ollama module ([#618](https://github.com/testcontainers/testcontainers-python/issues/618)) ([5442d05](https://github.com/testcontainers/testcontainers-python/commit/5442d054cb8bc11887e09d24e29d9f91dd943307)) +* **core:** Added Generic module ([#612](https://github.com/testcontainers/testcontainers-python/issues/612)) ([e575b28](https://github.com/testcontainers/testcontainers-python/commit/e575b28da912147c5b806abab40a0c92329e2eb7)) +* **core:** allow custom dockerfile path for image build and bypassing build cache ([#615](https://github.com/testcontainers/testcontainers-python/issues/615)) ([ead0f79](https://github.com/testcontainers/testcontainers-python/commit/ead0f797902a94d3b2558e489fe2a0a55c3bb7ad)), closes [#610](https://github.com/testcontainers/testcontainers-python/issues/610) +* **core:** DockerCompose.stop now stops only services that it starts (does not stop the other services) ([#620](https://github.com/testcontainers/testcontainers-python/issues/620)) ([e711800](https://github.com/testcontainers/testcontainers-python/commit/e71180039441e3c7d49467298ef0f498fe786149)) + + +### Bug Fixes + +* **cosmosdb:** Add support for the CosmosDB Emulator ([#579](https://github.com/testcontainers/testcontainers-python/issues/579)) ([8045a80](https://github.com/testcontainers/testcontainers-python/commit/8045a806fcb6908567339a14f2f0d7a169461675)) +* improve ollama docs, s/ollama_dir/ollama_home/g ([#619](https://github.com/testcontainers/testcontainers-python/issues/619)) ([27f2a6b](https://github.com/testcontainers/testcontainers-python/commit/27f2a6bdca8b9c860a96920eebc96f53682ea750)) +* **kafka:** Add Kraft to Kafka containers ([#611](https://github.com/testcontainers/testcontainers-python/issues/611)) ([762d2a2](https://github.com/testcontainers/testcontainers-python/commit/762d2a2130f7ce17dacaed5a96a6898a08cf2bc5)) + + +### Documentation + +* **contributing:** add contribution and new-container guide ([#460](https://github.com/testcontainers/testcontainers-python/issues/460)) ([3519f4b](https://github.com/testcontainers/testcontainers-python/commit/3519f4bdad6eac6c172977303b51cf52b4fa4c04)) + ## [4.6.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.5.1...testcontainers-v4.6.0) (2024-06-18) diff --git a/pyproject.toml b/pyproject.toml index c7a398d7b..c7876ea5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.6.0" # auto-incremented by release-please +version = "4.7.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 01d6c182485555ee83f560739c34f089b0e54e0b Mon Sep 17 00:00:00 2001 From: Oliver Lambson Date: Fri, 28 Jun 2024 18:54:33 +0100 Subject: [PATCH 398/425] fix(postgres): get_connection_url(driver=None) should return postgres://... (#588) Fixes #587 --- .../testcontainers/postgres/__init__.py | 2 +- modules/postgres/tests/test_postgres.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index 9b347aa61..80baef752 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -79,7 +79,7 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] = driver. The optional driver argument to :code:`get_connection_url` overwrites the constructor set value. Pass :code:`driver=None` to get URLs without a driver. """ - driver_str = self.driver if driver is _UNSET else f"+{driver}" + driver_str = "" if driver is None else self.driver if driver is _UNSET else f"+{driver}" return super()._create_connection_url( dialect=f"postgresql{driver_str}", username=self.username, diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index 528403617..38c856bf9 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -97,3 +97,27 @@ def test_show_how_to_initialize_db_via_initdb_dir(): result = result.fetchall() assert len(result) == 1 assert result[0] == (1, "sally", "sells seashells") + + +def test_none_driver_urls(): + user = "root" + password = "pass" + kwargs = { + "username": user, + "password": password, + } + with PostgresContainer("postgres:16-alpine", driver=None, **kwargs) as container: + port = container.get_exposed_port(5432) + host = container.get_container_host_ip() + expected_url = f"postgresql://{user}:{password}@{host}:{port}/test" + + url = container.get_connection_url() + assert url == expected_url + + with PostgresContainer("postgres:16-alpine", **kwargs) as container: + port = container.get_exposed_port(5432) + host = container.get_container_host_ip() + expected_url = f"postgresql://{user}:{password}@{host}:{port}/test" + + url = container.get_connection_url(driver=None) + assert url == expected_url From 0b866ff3c2d462fa5032945dfa2efd4bd59079da Mon Sep 17 00:00:00 2001 From: Oliver Lambson Date: Sun, 30 Jun 2024 18:33:56 +0100 Subject: [PATCH 399/425] fix(modules): Mailpit Container (#625) # New Container Fixes #626 # PR Checklist - [x] Your PR title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) syntax as we make use of this for detecting Semantic Versioning changes. - [x] Your PR allows maintainers to edit your branch, this will speed up resolving minor issues! - [x] The new container is implemented under `modules/*` - Your module follows [PEP 420](https://peps.python.org/pep-0420/) with implicit namespace packages (if unsure, look at other existing community modules) - Your package namespacing follows `testcontainers..*` and you DO NOT have an `__init__.py` above your module's level. - Your module has it's own tests under `modules/*/tests` - Your module has a `README.rst` and hooks in the `.. auto-class` and `.. title` of your container - Implement the new feature (typically in `__init__.py`) and corresponding tests. - [x] Your module is added in `pyproject.toml` - it is declared under `tool.poetry.packages` - see other community modules - it is declared under `tool.poetry.extras` with the same name as your module name, we still prefer adding _NO EXTRA DEPENDENCIES_, meaning `mymodule = []` is the preferred addition (see the notes at the bottom) - [x] (seems to not be needed anymore) The `INDEX.rst` at the project root includes your module under the `.. toctree` directive - [x] Your branch is up to date (or we'll use GH's "update branch" function through the UI) --------- Co-authored-by: Dave Ankin --- .github/settings.yml | 1 + modules/mailpit/README.rst | 3 + .../testcontainers/mailpit/__init__.py | 243 ++++++++++++++++++ .../mailpit/testcontainers/mailpit/py.typed | 0 modules/mailpit/tests/test_mailpit.py | 124 +++++++++ poetry.lock | 4 +- pyproject.toml | 4 + 7 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 modules/mailpit/README.rst create mode 100644 modules/mailpit/testcontainers/mailpit/__init__.py create mode 100644 modules/mailpit/testcontainers/mailpit/py.typed create mode 100644 modules/mailpit/tests/test_mailpit.py diff --git a/.github/settings.yml b/.github/settings.yml index 122fd660d..50ad365f3 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -71,6 +71,7 @@ labels: - { name: '📦 package: google', color: '#0052CC', description: '' } - { name: '📦 package: kafka', color: '#0052CC', description: '' } - { name: '📦 package: keycloak', color: '#0052CC', description: '' } + - { name: '📦 package: mailpit', color: '#0052CC', description: '' } - { name: '📦 package: mongodb', color: '#0052CC', description: '' } - { name: '📦 package: mssql', color: '#0052CC', description: '' } - { name: '📦 package: neo4j', color: '#0052CC', description: '' } diff --git a/modules/mailpit/README.rst b/modules/mailpit/README.rst new file mode 100644 index 000000000..f2c238a37 --- /dev/null +++ b/modules/mailpit/README.rst @@ -0,0 +1,3 @@ +.. autoclass:: testcontainers.mailpit.MailpitUser +.. autoclass:: testcontainers.mailpit.MailpitContainer +.. title:: testcontainers.mailpit.MailpitContainer diff --git a/modules/mailpit/testcontainers/mailpit/__init__.py b/modules/mailpit/testcontainers/mailpit/__init__.py new file mode 100644 index 000000000..63a26a7c6 --- /dev/null +++ b/modules/mailpit/testcontainers/mailpit/__init__.py @@ -0,0 +1,243 @@ +# +# 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. +from __future__ import annotations + +import os +import tempfile +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, Any, NamedTuple + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import ( + NoEncryption, +) +from cryptography.x509.oid import NameOID + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + +if TYPE_CHECKING: + from typing_extensions import Self + + +class MailpitUser(NamedTuple): + """Mailpit user for authentication + + Helper class to define a user for Mailpit authentication. + + This is just a named tuple for username and password. + + + Example: + + .. doctest:: + + >>> from testcontainers.mailpit import MailpitUser + + >>> users = [ + ... MailpitUser("jane", "secret"), + ... MailpitUser("ron", "pass2"), + ... ] + + >>> for user in users: + ... print(user.username, user.password) + ... + jane secret + ron pass2 + + >>> username, password = users[0] + + >>> print(username, password) + jane secret + """ + + username: str + password: str + + +class MailpitContainer(DockerContainer): + """ + Test container for Mailpit. The example below spins up a Mailpit server + + Default configuration supports SMTP with STARTTLS and allows login with any + user/password. + + Options: + + * ``require_tls = True`` forces the use of SSL + * ``users = [MailpitUser("jane", "secret"), MailpitUser("ron", "pass2")]`` \ + only allows login with ``jane:secret`` or ``ron:pass2`` + + Simple example: + + .. doctest:: + + >>> import smtplib + + >>> from testcontainers.mailpit import MailpitContainer + + >>> with MailpitContainer() as mailpit_container: + ... host_ip = mailpit_container.get_container_host_ip() + ... host_port = mailpit_container.get_exposed_smtp_port() + ... server = smtplib.SMTP( + ... mailpit_container.get_container_host_ip(), + ... mailpit_container.get_exposed_smtp_port(), + ... ) + ... code, _ = server.login("any", "auth") + ... assert code == 235 # authentication successful + ... # use server.sendmail(...) to send emails + + Example with auth and forced TLS: + + .. doctest:: + + >>> import smtplib + + >>> from testcontainers.mailpit import MailpitContainer, MailpitUser + + >>> users = [MailpitUser("jane", "secret"), MailpitUser("ron", "pass2")] + + >>> with MailpitContainer(users=users, require_tls=True) as mailpit_container: + ... host_ip = mailpit_container.get_container_host_ip() + ... host_port = mailpit_container.get_exposed_smtp_port() + ... server = smtplib.SMTP_SSL( + ... mailpit_container.get_container_host_ip(), + ... mailpit_container.get_exposed_smtp_port(), + ... ) + ... code, _ = server.login("jane", "secret") + ... assert code == 235 # authentication successful + ... # use server.sendmail(...) to send emails + """ + + def __init__( + self, + image: str = "axllent/mailpit", + *, + smtp_port: int = 1025, + ui_port: int = 8025, + users: list[MailpitUser] | None = None, + require_tls: bool = False, + **kwargs: Any, + ) -> None: + super().__init__(image=image, **kwargs) + self.smtp_port = smtp_port + self.ui_port = ui_port + + self.users = users if users is not None else [] + self.auth_accept_any = int(len(self.users) == 0) + + self.require_tls = int(require_tls) + self.tls_key, self.tls_cert = _generate_tls_certificates() + with tempfile.NamedTemporaryFile(delete=False) as tls_key_file: + tls_key_file.write(self.tls_key) + self.tls_key_file = tls_key_file.name + + with tempfile.NamedTemporaryFile(delete=False) as tls_cert_file: + tls_cert_file.write(self.tls_cert) + self.tls_cert_file = tls_cert_file.name + + @property + def _users_conf(self) -> str: + """Mailpit user configuration string + + "user:password user2:pass2 ...] + """ + return " ".join(f"{user.username}:{user.password}" for user in self.users) + + def _configure(self) -> None: + if self.users: + self.with_env("MP_SMTP_AUTH", self._users_conf) + self.with_env("MP_SMTP_AUTH_ACCEPT_ANY", str(self.auth_accept_any)) + + self.with_env("MP_SMTP_REQUIRE_TLS", str(self.require_tls)) + + self.with_volume_mapping(self.tls_cert_file, "/cert.pem") + self.with_volume_mapping(self.tls_key_file, "/key.pem") + self.with_env("MP_SMTP_TLS_CERT", "/cert.pem") + self.with_env("MP_SMTP_TLS_KEY", "/key.pem") + + self.with_exposed_ports(self.smtp_port, self.ui_port) + + def start(self) -> Self: + super().start() + wait_for_logs(self, ".*accessible via.*") + return self + + def stop(self, *args: Any, **kwargs: Any) -> None: + super().stop(*args, **kwargs) + os.remove(self.tls_key_file) + os.remove(self.tls_cert_file) + + def get_exposed_smtp_port(self) -> int: + return int(self.get_exposed_port(self.smtp_port)) + + +class _TLSCertificates(NamedTuple): + private_key: bytes + certificate: bytes + + +def _generate_tls_certificates() -> _TLSCertificates: + """Generate self-signed TLS certificates as bytes""" + private_key = _generate_private_key() + certificate = _generate_self_signed_certificate(private_key) + + private_key_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=NoEncryption(), + ) + certificate_bytes = certificate.public_bytes(serialization.Encoding.PEM) + + return _TLSCertificates(private_key_bytes, certificate_bytes) + + +def _generate_private_key() -> rsa.RSAPrivateKey: + """Generate RSA private key""" + return rsa.generate_private_key( + public_exponent=65537, + key_size=4096, + ) + + +def _generate_self_signed_certificate( + private_key: rsa.RSAPrivateKey, +) -> x509.Certificate: + """Generate self-signed certificate with RSA private key""" + domain = "mydomain.com" + subject = issuer = x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "The Post Office"), + x509.NameAttribute(NameOID.COMMON_NAME, domain), + ] + ) + + return ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.now(timezone.utc)) + .not_valid_after(datetime.now(timezone.utc) + timedelta(days=3650)) # 10 years + .add_extension( + x509.SubjectAlternativeName([x509.DNSName(domain)]), + critical=False, + ) + .sign(private_key, hashes.SHA256()) + ) diff --git a/modules/mailpit/testcontainers/mailpit/py.typed b/modules/mailpit/testcontainers/mailpit/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/modules/mailpit/tests/test_mailpit.py b/modules/mailpit/tests/test_mailpit.py new file mode 100644 index 000000000..53247f49d --- /dev/null +++ b/modules/mailpit/tests/test_mailpit.py @@ -0,0 +1,124 @@ +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +import pytest + +from testcontainers.mailpit import MailpitContainer, MailpitUser + +_sender = "from@example.com" +_receivers = ["to@example.com"] +_msg = MIMEMultipart("mixed") +_msg["From"] = _sender +_msg["To"] = ", ".join(_receivers) +_msg["Subject"] = "test" +_msg.attach(MIMEText("test", "plain")) +_sendmail_args = (_sender, _receivers, _msg.as_string()) + + +def test_mailpit_basic(): + config = MailpitContainer() + with config as mailpit: + server = smtplib.SMTP( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.login("any", "auth") + server.sendmail(*_sendmail_args) + + +def test_mailpit_starttls(): + config = MailpitContainer() + with config as mailpit: + server = smtplib.SMTP( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.starttls() + server.login("any", "auth") + server.sendmail(*_sendmail_args) + + +def test_mailpit_force_tls(): + config = MailpitContainer(require_tls=True) + with config as mailpit: + server = smtplib.SMTP_SSL( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.login("any", "auth") + server.sendmail(*_sendmail_args) + + +def test_mailpit_basic_with_users_pass_auth(): + users = [MailpitUser("user", "password")] + config = MailpitContainer(users=users) + with config as mailpit: + server = smtplib.SMTP( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.login(mailpit.users[0].username, mailpit.users[0].password) + server.sendmail(*_sendmail_args) + + +def test_mailpit_basic_with_users_fail_auth(): + users = [MailpitUser("user", "password")] + config = MailpitContainer(users=users) + with pytest.raises(smtplib.SMTPAuthenticationError): + with config as mailpit: + server = smtplib.SMTP( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.login("not", "good") + + +def test_mailpit_starttls_with_users_pass_auth(): + users = [MailpitUser("user", "password")] + config = MailpitContainer(users=users) + with config as mailpit: + server = smtplib.SMTP( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.starttls() + server.login(mailpit.users[0].username, mailpit.users[0].password) + server.sendmail(*_sendmail_args) + + +def test_mailpit_starttls_with_users_fail_auth(): + users = [MailpitUser("user", "password")] + config = MailpitContainer(users=users) + with pytest.raises(smtplib.SMTPAuthenticationError): + with config as mailpit: + server = smtplib.SMTP( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.starttls() + server.login("not", "good") + + +def test_mailpit_force_tls_with_users_pass_auth(): + users = [MailpitUser("user", "password")] + config = MailpitContainer(users=users, require_tls=True) + with config as mailpit: + server = smtplib.SMTP_SSL( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.login(mailpit.users[0].username, mailpit.users[0].password) + server.sendmail(*_sendmail_args) + + +def test_mailpit_force_tls_with_users_fail_auth(): + users = [MailpitUser("user", "password")] + config = MailpitContainer(users=users, require_tls=True) + with pytest.raises(smtplib.SMTPAuthenticationError): + with config as mailpit: + server = smtplib.SMTP_SSL( + mailpit.get_container_host_ip(), + mailpit.get_exposed_smtp_port(), + ) + server.login("not", "good") diff --git a/poetry.lock b/poetry.lock index 90a83f33f..c7d5dc5ed 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1913,6 +1913,7 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, + {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -4486,6 +4487,7 @@ k3s = ["kubernetes", "pyyaml"] kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] +mailpit = ["cryptography"] memcached = [] milvus = [] minio = ["minio"] @@ -4513,4 +4515,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "2b87af7b69af2cc83f8198ab0fcfef7ceaf8411a8300c4ca72c0521e5d966445" +content-hash = "bda3a38eb7c78fae7290b2dfb1535e676ba3fc3189d2baeda9454caa96e572f5" diff --git a/pyproject.toml b/pyproject.toml index c7876ea5c..54516f43c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ packages = [ { include = "testcontainers", from = "modules/kafka" }, { include = "testcontainers", from = "modules/keycloak" }, { include = "testcontainers", from = "modules/localstack" }, + { include = "testcontainers", from = "modules/mailpit" }, { include = "testcontainers", from = "modules/memcached" }, { include = "testcontainers", from = "modules/minio" }, { include = "testcontainers", from = "modules/milvus" }, @@ -108,6 +109,7 @@ qdrant-client = { version = "*", optional = true } bcrypt = { version = "*", optional = true } httpx = { version = "*", optional = true } azure-cosmos = { version = "*", optional = true } +cryptography = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -125,6 +127,7 @@ k3s = ["kubernetes", "pyyaml"] kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] +mailpit = ["cryptography"] memcached = [] minio = ["minio"] milvus = [] @@ -276,6 +279,7 @@ mypy_path = [ # "modules/kafka", # "modules/keycloak", # "modules/localstack", + "modules/mailpit", # "modules/minio", # "modules/mongodb", # "modules/mssql", From 16f6ca42621866d8ff87ca539a84da27dbe9a4c4 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Sun, 30 Jun 2024 23:39:00 +0300 Subject: [PATCH 400/425] fix: update test module import (#623) Following #612, updated the test module import with a better name and some minor fixes. --- modules/test_module_import/README.rst | 2 ++ .../testcontainers/test_module_import}/__init__.py | 0 .../test_module_import}/new_sub_module.py | 12 ++++++------ .../tests/test_mock_one.py | 11 +++++------ modules/testmoduleimport/README.rst | 2 -- poetry.lock | 5 ++--- pyproject.toml | 4 ++-- 7 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 modules/test_module_import/README.rst rename modules/{testmoduleimport/testcontainers/testmoduleimport => test_module_import/testcontainers/test_module_import}/__init__.py (100%) rename modules/{testmoduleimport/testcontainers/testmoduleimport => test_module_import/testcontainers/test_module_import}/new_sub_module.py (68%) rename modules/{testmoduleimport => test_module_import}/tests/test_mock_one.py (50%) delete mode 100644 modules/testmoduleimport/README.rst diff --git a/modules/test_module_import/README.rst b/modules/test_module_import/README.rst new file mode 100644 index 000000000..3d2e7543a --- /dev/null +++ b/modules/test_module_import/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.test_module_import.NewSubModuleContainer +.. title:: testcontainers.test_module_import.NewSubModuleContainer diff --git a/modules/testmoduleimport/testcontainers/testmoduleimport/__init__.py b/modules/test_module_import/testcontainers/test_module_import/__init__.py similarity index 100% rename from modules/testmoduleimport/testcontainers/testmoduleimport/__init__.py rename to modules/test_module_import/testcontainers/test_module_import/__init__.py diff --git a/modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py b/modules/test_module_import/testcontainers/test_module_import/new_sub_module.py similarity index 68% rename from modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py rename to modules/test_module_import/testcontainers/test_module_import/new_sub_module.py index f45796f76..6f25d2777 100644 --- a/modules/testmoduleimport/testcontainers/testmoduleimport/new_sub_module.py +++ b/modules/test_module_import/testcontainers/test_module_import/new_sub_module.py @@ -9,19 +9,19 @@ class NewSubModuleContainer(ServerContainer): >>> import httpx >>> from testcontainers.core.image import DockerImage - >>> from testcontainers.testmoduleimport import NewSubModuleContainer + >>> from testcontainers.test_module_import import NewSubModuleContainer - >>> with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-mod:latest") as image: - ... with NewSubModuleContainer(port=9000, image=image) as srv: - ... url = srv._create_connection_url() + >>> with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-new-mod:latest") as image: + ... with NewSubModuleContainer(port=9000, image=image) as new_mod: + ... url = new_mod._create_connection_url() ... response = httpx.get(f"{url}", timeout=5) ... assert response.status_code == 200, "Response status code is not 200" - ... assert srv.print_mock() == "NewSubModuleContainer" + ... assert new_mod.additional_capability() == "NewSubModuleContainer" """ def __init__(self, port: int, image: str) -> None: super().__init__(port, image) - def print_mock(self) -> str: + def additional_capability(self) -> str: return "NewSubModuleContainer" diff --git a/modules/testmoduleimport/tests/test_mock_one.py b/modules/test_module_import/tests/test_mock_one.py similarity index 50% rename from modules/testmoduleimport/tests/test_mock_one.py rename to modules/test_module_import/tests/test_mock_one.py index 85ac6c315..915c95b11 100644 --- a/modules/testmoduleimport/tests/test_mock_one.py +++ b/modules/test_module_import/tests/test_mock_one.py @@ -2,14 +2,13 @@ from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.core.image import DockerImage -from testcontainers.testmoduleimport import NewSubModuleContainer +from testcontainers.test_module_import import NewSubModuleContainer def test_like_doctest(): - with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-srv:latest") as image: - with NewSubModuleContainer(port=9000, image=image) as srv: - assert srv.print_mock() == "NewSubModuleContainer" - url = srv._create_connection_url() + with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-new-mod:latest") as image: + with NewSubModuleContainer(port=9000, image=image) as new_mod: + url = new_mod._create_connection_url() response = httpx.get(f"{url}", timeout=5) assert response.status_code == 200, "Response status code is not 200" - _ = wait_for_logs(srv, "GET / HTTP/1.1") + assert new_mod.additional_capability() == "NewSubModuleContainer" diff --git a/modules/testmoduleimport/README.rst b/modules/testmoduleimport/README.rst deleted file mode 100644 index ae5d5708a..000000000 --- a/modules/testmoduleimport/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.testmoduleimport.NewSubModuleContainer -.. title:: testcontainers.testmoduleimport.NewSubModuleContainer diff --git a/poetry.lock b/poetry.lock index c7d5dc5ed..548521e52 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1913,7 +1913,6 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, - {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -4508,11 +4507,11 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] -testmoduleimport = ["httpx"] +test-module-import = ["httpx"] vault = [] weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "bda3a38eb7c78fae7290b2dfb1535e676ba3fc3189d2baeda9454caa96e572f5" +content-hash = "e95316f2de630e690a4e62f240dad0461e9adb936474c9d7cb4a556ec54cb70b" diff --git a/pyproject.toml b/pyproject.toml index 54516f43c..616c42061 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ packages = [ { include = "testcontainers", from = "modules/cosmosdb" }, { include = "testcontainers", from = "modules/elasticsearch" }, { include = "testcontainers", from = "modules/generic" }, - { include = "testcontainers", from = "modules/testmoduleimport"}, + { include = "testcontainers", from = "modules/test_module_import"}, { include = "testcontainers", from = "modules/google" }, { include = "testcontainers", from = "modules/influxdb" }, { include = "testcontainers", from = "modules/k3s" }, @@ -120,7 +120,7 @@ cosmosdb = ["azure-cosmos"] cockroachdb = [] elasticsearch = [] generic = ["httpx"] -testmoduleimport = ["httpx"] +test_module_import = ["httpx"] google = ["google-cloud-pubsub", "google-cloud-datastore"] influxdb = ["influxdb", "influxdb-client"] k3s = ["kubernetes", "pyyaml"] From 2e7dbf1185c68c7cbfb6bdac7457d1d5f86aba19 Mon Sep 17 00:00:00 2001 From: Oliver Lambson Date: Mon, 1 Jul 2024 00:44:36 +0100 Subject: [PATCH 401/425] fix(modules): SFTP Server Container (#629) # New Container Fixes #628 # PR Checklist - [x] Your PR title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) syntax as we make use of this for detecting Semantic Versioning changes. - [x] Your PR allows maintainers to edit your branch, this will speed up resolving minor issues! - [x] The new container is implemented under `modules/*` - Your module follows [PEP 420](https://peps.python.org/pep-0420/) with implicit namespace packages (if unsure, look at other existing community modules) - Your package namespacing follows `testcontainers..*` and you DO NOT have an `__init__.py` above your module's level. - Your module has it's own tests under `modules/*/tests` - Your module has a `README.rst` and hooks in the `.. auto-class` and `.. title` of your container - Implement the new feature (typically in `__init__.py`) and corresponding tests. - [x] Your module is added in `pyproject.toml` - it is declared under `tool.poetry.packages` - see other community modules - it is declared under `tool.poetry.extras` with the same name as your module name, we still prefer adding _NO EXTRA DEPENDENCIES_, meaning `mymodule = []` is the preferred addition (see the notes at the bottom) - [x] ~The `INDEX.rst` at the project root includes your module under the `.. toctree` directive~ - [x] Your branch is up to date (or we'll use GH's "update branch" function through the UI) --- .github/settings.yml | 1 + modules/sftp/README.rst | 3 + modules/sftp/testcontainers/sftp/__init__.py | 301 +++++++++++++++++++ modules/sftp/testcontainers/sftp/py.typed | 0 modules/sftp/tests/test_sftp.py | 159 ++++++++++ poetry.lock | 67 ++++- pyproject.toml | 5 + 7 files changed, 534 insertions(+), 2 deletions(-) create mode 100644 modules/sftp/README.rst create mode 100644 modules/sftp/testcontainers/sftp/__init__.py create mode 100644 modules/sftp/testcontainers/sftp/py.typed create mode 100644 modules/sftp/tests/test_sftp.py diff --git a/.github/settings.yml b/.github/settings.yml index 50ad365f3..4e9d96d10 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -79,6 +79,7 @@ labels: - { name: '📦 package: postgres', color: '#0052CC', description: '' } - { name: '📦 package: rabbitmq', color: '#0052CC', description: '' } - { name: '📦 package: selenium', color: '#0052CC', description: '' } + - { name: '📦 package: sftp', color: '#0052CC', description: '' } - { name: '🔀 requires triage', color: '#bfdadc', description: '' } - { name: '🔧 maintenance', color: '#c2f759', description: '' } - { name: '🚀 enhancement', color: '#84b6eb', description: '' } diff --git a/modules/sftp/README.rst b/modules/sftp/README.rst new file mode 100644 index 000000000..2287d59c9 --- /dev/null +++ b/modules/sftp/README.rst @@ -0,0 +1,3 @@ +.. autoclass:: testcontainers.sftp.SFTPContainer +.. autoclass:: testcontainers.sftp.SFTPUser +.. title:: testcontainers.sftp.SFTPContainer diff --git a/modules/sftp/testcontainers/sftp/__init__.py b/modules/sftp/testcontainers/sftp/__init__.py new file mode 100644 index 000000000..0e073ea1a --- /dev/null +++ b/modules/sftp/testcontainers/sftp/__init__.py @@ -0,0 +1,301 @@ +# +# 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. +from __future__ import annotations + +import os +import tempfile +from typing import TYPE_CHECKING, Any, NamedTuple + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_for_logs + +if TYPE_CHECKING: + from typing_extensions import Self + + +class SFTPUser: + """ + Helper class to define a user for SFTPContainer authentication. + + Constructor args/kwargs: + + * ``name``: (req.) username + * ``public_key``: (opt.) bytes of publickey + * ``private_key``: (opt.) bytes of privatekey (useful if you want to access \ + them later in test code) + * ``password``: (opt.) password + * ``uid``: (opt.) user ID + * ``gid``: (opt.) group ID + * ``folders``: (opt.) folders to create inside the user's directory (e.g. upload/) + * ``mount_dir``: (opt.) a local folder to mount to the user's root directory + + Properties: + + * ``public_key_file``: str path of public key tempfile (gets mounted to \ + SFTPContainer as a volume) + * ``private_key_file``: str path of private key tempfile (useful to pass to \ + paramiko when connecting to the sftp server using ssh + + Methods: + + * ``with_keypair``: classmethod to create a new user with an auto-generated RSA keypair + * ``conf``: str configuration string to register user on server + + + Example: + + .. doctest:: + + >>> from testcontainers.sftp import SFTPUser + + >>> users = [ + ... SFTPUser("jane", password="secret"), + ... SFTPUser.with_keypair("ron", folders=["stuff"]), + ... ] + + >>> for user in users: + ... print(user.name, user.folders[0]) + ... + jane upload + ron stuff + + >>> assert users[0].password == "secret" + + >>> assert users[1].public_key is not None + + >>> assert users[1].public_key.decode().startswith("ssh-rsa ") + + >>> assert users[1].private_key is not None + + >>> assert users[1].private_key.decode().startswith("-----BEGIN RSA PRIVATE KEY-----") + """ + + def __init__( + self, + name: str, + *, + public_key: bytes | None = None, + private_key: bytes | None = None, + password: str | None = None, + uid: str | None = None, + gid: str | None = None, + folders: list[str] | None = None, + mount_dir: str | None = None, + ) -> None: + if folders is None: + folders = ["upload"] + self.name = name + self.public_key = public_key + self.private_key = private_key + self.password = password + self.uid = uid + self.gid = gid + self.folders = folders + self.mount_dir = mount_dir + + self.public_key_file: str | None = None + if self.public_key is not None: + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write(self.public_key) + self.public_key_file = f.name + + self.private_key_file: str | None = None + if self.private_key is not None: + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write(self.private_key) + self.private_key_file = f.name + + def __del__(self) -> None: + """Clean up keypair temp files""" + if self.public_key_file is not None: + os.unlink(self.public_key_file) + if self.private_key_file is not None: + os.unlink(self.private_key_file) + + @property + def conf(self) -> str: + """Configuration string to register user on server""" + return ":".join( + [ + self.name, + self.password or "", + self.uid or "", + self.gid or "", + ",".join(self.folders), + ] + ) + + @classmethod + def with_keypair( + cls, + name: str, + password: str | None = None, + uid: str | None = None, + gid: str | None = None, + folders: list[str] | None = None, + mount_dir: str | None = None, + ) -> SFTPUser: + """Construct a new SFTPUser with an auto-generated RSA keypair""" + keypair = _generate_keypair() + return SFTPUser( + name=name, + public_key=keypair.public_key, + private_key=keypair.private_key, + password=password, + uid=uid, + gid=gid, + folders=folders, + mount_dir=mount_dir, + ) + + def __repr__(self) -> str: + return ( + f"SFTPUser({self.name}, password={self.password}, uid={self.uid}," + f" gid={self.gid}, folders={self.folders}," + f" public_key_file={self.public_key_file}," + f" private_key_file={self.private_key_file})" + ) + + +class SFTPContainer(DockerContainer): + """Test container for an SFTP server. + + Default configuration creates two users, ``basic:password`` and ``keypair`` + which has no password but should use the private key accessible at + ``my_container.users[1].private_key``. + + **Users can only download from their root user folder, but can upload & + download from any subfolder** (``upload/`` by default). + + Options: + + * ``users = [SFTPUser("jane", password="secret"), SFTPUser.with_keypair("ron")]`` \ + creates ``jane:secret`` or ``ron`` who uses the private key accessible at \ + ``users[1].private_key``. + + Simple example with basic auth: + + .. doctest:: + + >>> import paramiko + + >>> from testcontainers.sftp import SFTPContainer + + >>> with SFTPContainer() as sftp_container: + ... host_ip = sftp_container.get_container_host_ip() + ... host_port = sftp_container.get_exposed_sftp_port() + ... ssh = paramiko.SSHClient() + ... ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ... ssh.connect(host_ip, host_port, "basic", "password") + ... # ssh.get(...) + ... # ssh.listdir() + ... # ssh.chdir("upload") + ... # ssh.put(...) + + Example with keypair auth: + + .. doctest:: + + >>> import paramiko + + >>> from testcontainers.sftp import SFTPContainer + + >>> with SFTPContainer() as sftp_container: + ... host_ip = sftp_container.get_container_host_ip() + ... host_port = sftp_container.get_exposed_sftp_port() + ... ssh = paramiko.SSHClient() + ... ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ... private_key_file = sftp_container.users[1].private_key_file + ... ssh.connect(host_ip, host_port, "keypair", key_filename=private_key_file) + ... # ssh.listdir() + ... # ssh.get(...) + ... # ssh.chdir("upload") + ... # ssh.put(...) + """ + + def __init__( + self, + image: str = "atmoz/sftp:alpine", + port: int = 22, + *, + users: list[SFTPUser] | None = None, + **kwargs: Any, + ) -> None: + if users is None: + users = [ + SFTPUser(name="basic", password="password"), + SFTPUser.with_keypair(name="keypair"), + ] + + super().__init__(image=image, **kwargs) + self.port = port + self.users = users + + @property + def _users_conf(self) -> str: + return " ".join(user.conf for user in self.users) + + def _configure(self) -> None: + for user in self.users: + if user.public_key_file is not None: + self.with_volume_mapping( + user.public_key_file, + f"/home/{user.name}/.ssh/keys/{user.name}.pub", + ) + if user.mount_dir is not None: + self.with_volume_mapping( + user.mount_dir, + f"/home/{user.name}/", + "rw", + ) + self.with_env("SFTP_USERS", self._users_conf) + self.with_exposed_ports(self.port) + + def start(self) -> Self: + super().start() + wait_for_logs(self, f".*Server listening on 0.0.0.0 port {self.port}.*") + return self + + def get_exposed_sftp_port(self) -> int: + return int(self.get_exposed_port(self.port)) + + +class _Keypair(NamedTuple): + """RSA keypair as bytes""" + + private_key: bytes + public_key: bytes + + +def _generate_keypair() -> _Keypair: + """Generate RSA keypair as bytes in OpenSSH format.""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=4096, + ) + private_key_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + public_key_bytes = private_key.public_key().public_bytes( + encoding=serialization.Encoding.OpenSSH, # paramiko flakiness fix + format=serialization.PublicFormat.OpenSSH, + ) + return _Keypair( + private_key=private_key_bytes, + public_key=public_key_bytes, + ) diff --git a/modules/sftp/testcontainers/sftp/py.typed b/modules/sftp/testcontainers/sftp/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/modules/sftp/tests/test_sftp.py b/modules/sftp/tests/test_sftp.py new file mode 100644 index 000000000..e3dab2e3b --- /dev/null +++ b/modules/sftp/tests/test_sftp.py @@ -0,0 +1,159 @@ +import tempfile +from pathlib import Path + +import paramiko +import pytest + +from testcontainers.sftp import SFTPContainer, SFTPUser + + +def test_sftp_login_with_default_basic_auth(): + with SFTPContainer() as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=sftp_container.users[0].name, + password=sftp_container.users[0].password, + ) + + +def test_sftp_login_with_default_keypair_auth(): + with SFTPContainer() as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=sftp_container.users[1].name, + key_filename=sftp_container.users[1].private_key_file, + ) + + +def test_sftp_login_with_custom_user_basic_auth(): + user = SFTPUser(name="custom", password="custom_password") + with SFTPContainer(users=[user]) as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=user.name, + password=user.password, + ) + + +def test_sftp_login_with_custom_user_keypair_auth(): + user = SFTPUser.with_keypair(name="custom") + with SFTPContainer(users=[user]) as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=user.name, + key_filename=user.private_key_file, + ) + + +def test_sftp_login_with_custom_user_password_and_keypair_auth(): + user = SFTPUser.with_keypair(name="custom", password="custom_password") + with SFTPContainer(users=[user]) as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=user.name, + password=user.password, + key_filename=user.private_key_file, + ) + + +def test_sftp_user_can_upload(): + with SFTPContainer() as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=sftp_container.users[0].name, + password=sftp_container.users[0].password, + ) + sftp = ssh.open_sftp() + sftp.chdir("upload") + with tempfile.NamedTemporaryFile() as f: + f.write(b"test") + f.seek(0) + sftp.put(f.name, "test.txt") + + with tempfile.NamedTemporaryFile() as f: + sftp.get("test.txt", f.name) + f.seek(0) + assert f.read() == b"test" + + +def test_sftp_user_can_download_from_mounted(tmp_path: Path): + temp_dir = tmp_path / "sub" + temp_dir.mkdir() + temp_file = temp_dir / "test.txt" + temp_file.write_text("test") + user = SFTPUser.with_keypair(name="custom", mount_dir=temp_dir.as_posix()) + with SFTPContainer(users=[user]) as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=user.name, + key_filename=user.private_key_file, + ) + sftp = ssh.open_sftp() + with tempfile.NamedTemporaryFile() as f: + sftp.get(temp_file.name, f.name) + f.seek(0) + assert f.read() == b"test" + + +def test_sftp_user_cant_upload_to_root(tmp_path: Path): + temp_dir = tmp_path / "sub" + temp_dir.mkdir() + temp_file = temp_dir / "test.txt" + temp_file.write_text("test") + with SFTPContainer() as sftp_container: + sftp_container.start() + host_ip = sftp_container.get_container_host_ip() + host_port = sftp_container.get_exposed_sftp_port() + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + hostname=host_ip, + port=host_port, + username=sftp_container.users[0].name, + password=sftp_container.users[0].password, + ) + sftp = ssh.open_sftp() + with pytest.raises(PermissionError): + sftp.put(temp_file.as_posix(), temp_file.name) diff --git a/poetry.lock b/poetry.lock index 548521e52..596444b7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -239,7 +239,7 @@ files = [ name = "bcrypt" version = "4.1.2" description = "Modern password hashing for your software and your servers" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, @@ -1913,6 +1913,7 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, + {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -2541,6 +2542,27 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "paramiko" +version = "3.4.0" +description = "SSH2 protocol library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, + {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, +] + +[package.dependencies] +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" + +[package.extras] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=2.0)"] + [[package]] name = "pg8000" version = "1.30.5" @@ -3259,6 +3281,32 @@ cryptography = {version = "*", optional = true, markers = "extra == \"rsa\""} ed25519 = ["PyNaCl (>=1.4.0)"] rsa = ["cryptography"] +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + [[package]] name = "pysocks" version = "1.7.1" @@ -4118,6 +4166,20 @@ rfc3986 = ">=1.4.0" rich = ">=12.0.0" urllib3 = ">=1.26.0" +[[package]] +name = "types-paramiko" +version = "3.4.0.20240423" +description = "Typing stubs for paramiko" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-paramiko-3.4.0.20240423.tar.gz", hash = "sha256:aaa98dda232c47886563d66743d3a8b66c432790c596bc3bdd3f17f91be2a8c1"}, + {file = "types_paramiko-3.4.0.20240423-py3-none-any.whl", hash = "sha256:c56e0d43399a1b909901b1e0375e0ff6ee62e16cd6e00695024abc2e9fe02035"}, +] + +[package.dependencies] +cryptography = ">=37.0.0" + [[package]] name = "typing-extensions" version = "4.11.0" @@ -4507,6 +4569,7 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] +sftp = ["cryptography"] test-module-import = ["httpx"] vault = [] weaviate = ["weaviate-client"] @@ -4514,4 +4577,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "e95316f2de630e690a4e62f240dad0461e9adb936474c9d7cb4a556ec54cb70b" +content-hash = "4694e6bedeb7263ba9b7de579b81913f285161d5d27d453bd36a616e9ce3eade" diff --git a/pyproject.toml b/pyproject.toml index 616c42061..dfa47f7fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ packages = [ { include = "testcontainers", from = "modules/rabbitmq" }, { include = "testcontainers", from = "modules/redis" }, { include = "testcontainers", from = "modules/registry" }, + { include = "testcontainers", from = "modules/sftp" }, { include = "testcontainers", from = "modules/selenium" }, { include = "testcontainers", from = "modules/vault" }, { include = "testcontainers", from = "modules/weaviate" }, @@ -148,6 +149,7 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] +sftp = ["cryptography"] vault = [] weaviate = ["weaviate-client"] chroma = ["chromadb-client"] @@ -173,6 +175,8 @@ pymilvus = "2.4.3" httpx = "0.27.0" paho-mqtt = "2.1.0" sqlalchemy-cockroachdb = "2.0.2" +paramiko = "^3.4.0" +types-paramiko = "^3.4.0.20240423" [[tool.poetry.source]] name = "PyPI" @@ -293,6 +297,7 @@ mypy_path = [ # "modules/rabbitmq", # "modules/redis", # "modules/selenium" + "modules/sftp", # "modules/vault" # "modules/weaviate" ] From e93bc29c1781c4e73840c4c587160f8e5805feea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fukan=20=C3=87a=C4=9Fatay?= <970872+dogukancagatay@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:04:26 +0200 Subject: [PATCH 402/425] fix(network): Now able to use Network without context, and has labels to be automatically cleaned up (#627) (#630) This PR adds `create` method to the `Network` class to enable a non-context-manager usage. Fixes #627 --------- Co-authored-by: Dave Ankin --- core/testcontainers/core/docker_client.py | 4 +++ core/testcontainers/core/network.py | 7 ++-- core/tests/test_network.py | 40 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 9b7fe7479..286e1ef9f 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -211,6 +211,10 @@ def login(self, docker_auth_config: str) -> None: login_info = self.client.login(**auth_config._asdict()) LOGGER.debug(f"logged in using {login_info}") + def client_networks_create(self, name: str, param: dict): + labels = create_labels("", param.get("labels")) + return self.client.networks.create(name, **{**param, "labels": labels}) + def get_docker_host() -> Optional[str]: return c.tc_properties_get_tc_host() or os.getenv("DOCKER_HOST") diff --git a/core/testcontainers/core/network.py b/core/testcontainers/core/network.py index 9903d0710..d149d5e48 100644 --- a/core/testcontainers/core/network.py +++ b/core/testcontainers/core/network.py @@ -32,10 +32,13 @@ def connect(self, container_id: str, network_aliases: Optional[list] = None): def remove(self) -> None: self._network.remove() - def __enter__(self) -> "Network": - self._network = self._docker.client.networks.create(self.name, **self._docker_network_kw) + def create(self) -> "Network": + self._network = self._docker.client_networks_create(self.name, self._docker_network_kw) self.id = self._network.id return self + def __enter__(self) -> "Network": + return self.create() + def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.remove() diff --git a/core/tests/test_network.py b/core/tests/test_network.py index 4b0764d4d..7191153bb 100644 --- a/core/tests/test_network.py +++ b/core/tests/test_network.py @@ -1,7 +1,12 @@ +from http import HTTPStatus from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient +from testcontainers.core.labels import LABEL_SESSION_ID from testcontainers.core.network import Network +import docker.errors +import pytest + NGINX_ALPINE_SLIM_IMAGE = "nginx:1.25.4-alpine-slim" @@ -14,6 +19,31 @@ def test_network_gets_created_and_cleaned_up(): assert not docker.client.networks.list(network.name) +def test_network_create_wo_cm(): + network = Network() + network.create() + docker = DockerClient() + networks_list = docker.client.networks.list(network.name) + assert networks_list[0].name == network.name + assert networks_list[0].id == network.id + + network.remove() + assert not docker.client.networks.list(network.name) + + +def test_network_create_errors(): + network = Network() + network.create() + + # calling create the second time should raise an error + with pytest.raises(docker.errors.APIError) as excinfo: + network.create() + + assert excinfo.value.response.status_code == HTTPStatus.CONFLICT + excinfo.match(f"network with name {network.name} already exists") + network.remove() + + def test_containers_can_communicate_over_network(): with Network() as network: with ( @@ -41,3 +71,13 @@ def assert_can_ping(container: DockerContainer, remote_name: str): status, output = container.exec("ping -c 1 %s" % remote_name) assert status == 0 assert "64 bytes" in str(output) + + +def test_network_has_labels(): + network = Network() + try: + network.create() + network = network._docker.client.networks.get(network_id=network.id) + assert LABEL_SESSION_ID in network.attrs.get("Labels") + finally: + network.remove() From 4766e4829407c19de039effc7ea8fcc8b6dcc214 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Tue, 2 Jul 2024 09:11:12 -0400 Subject: [PATCH 403/425] fix(core): bad rebase from #579 (#635) fixes some incorrectly resolved conflicts during the rebase in #579 fixes #632 - **fix #607 - no longer need to manually include for toctree** - **semver note** - **fix rest of incorrectly resolved conflicts in index.rst** --- .../PULL_REQUEST_TEMPLATE/new_container.md | 10 ++- index.rst | 84 ++++++++++++++----- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index 27057310d..c64d66785 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -14,13 +14,18 @@ It helps reduce unnecessary work for you and the maintainers! - [ ] Your PR title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) syntax as we make use of this for detecting Semantic Versioning changes. + - Additions to the community modules do not contribute to SemVer scheme: + all community features will be tagged [community-feat](https://github.com/testcontainers/testcontainers-python/issues?q=label%3Acommunity-feat+), + but we do not want to release minor or major versions due to features or breaking changes outside of core. + So please use `fix(postgres):` or `fix(my_new_vector_db):` if you want to add or modify community modules. + This may change in the future if we have a separate package released with community modules. - [ ] Your PR allows maintainers to edit your branch, this will speed up resolving minor issues! - [ ] The new container is implemented under `modules/*` - Your module follows [PEP 420](https://peps.python.org/pep-0420/) with implicit namespace packages (if unsure, look at other existing community modules) - Your package namespacing follows `testcontainers..*` and you DO NOT have an `__init__.py` above your module's level. - - Your module has it's own tests under `modules/*/tests` + - Your module has its own tests under `modules/*/tests` - Your module has a `README.rst` and hooks in the `.. auto-class` and `.. title` of your container - Implement the new feature (typically in `__init__.py`) and corresponding tests. - [ ] Your module is added in `pyproject.toml` @@ -28,8 +33,7 @@ It helps reduce unnecessary work for you and the maintainers! - it is declared under `tool.poetry.extras` with the same name as your module name, we still prefer adding _NO EXTRA DEPENDENCIES_, meaning `mymodule = []` is the preferred addition (see the notes at the bottom) -- [ ] The `INDEX.rst` at the project root includes your module under the `.. toctree` directive -- [ ] Your branch is up to date (or we'll use GH's "update branch" function through the UI) +- [ ] Your branch is up-to-date (or your branch will be rebased with `git rebase`) # Preferred implementation diff --git a/index.rst b/index.rst index 8c02832fe..70708a247 100644 --- a/index.rst +++ b/index.rst @@ -13,6 +13,7 @@ testcontainers-python testcontainers-python facilitates the use of Docker containers for functional and integration testing. The collection of packages currently supports the following features. .. toctree:: + :maxdepth: 1 core/README modules/index @@ -59,12 +60,15 @@ Installation ------------ The suite of testcontainers packages is available on `PyPI `_, -and individual packages can be installed using :code:`pip`. +the package can be installed using :code:`pip`. -Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsutainable to maintain ownership. +Version `4.0.0` onwards we do not support the `testcontainers-*` packages as it is unsustainable to maintain ownership. Instead packages can be installed by specifying `extras `__, e.g., :code:`pip install testcontainers[postgres]`. +Please note, that community modules are supported on a best-effort basis and breaking changes DO NOT create major versions in the package. +Therefore, only the package core is strictly following SemVer. If your workflow is broken by a minor update, please look at the changelogs for guidance. + Custom Containers ----------------- @@ -80,40 +84,77 @@ For common use cases, you can also use the generic containers provided by the `t Docker in Docker (DinD) ----------------------- -When trying to launch a testcontainer from within a Docker container, e.g., in continuous integration testing, two things have to be provided: +When trying to launch Testcontainers from within a Docker container, e.g., in continuous integration testing, two things have to be provided: 1. The container has to provide a docker client installation. Either use an image that has docker pre-installed (e.g. the `official docker images `_) or install the client from within the `Dockerfile` specification. 2. The container has to have access to the docker daemon which can be achieved by mounting `/var/run/docker.sock` or setting the `DOCKER_HOST` environment variable as part of your `docker run` command. + +Private Docker registry +----------------------- + +Using a private docker registry requires the `DOCKER_AUTH_CONFIG` environment variable to be set. +`official documentation `_ + +The value of this variable should be a JSON string containing the authentication information for the registry. + +Example: + +.. code-block:: bash + + DOCKER_AUTH_CONFIG='{"auths": {"https://myregistry.com": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="}}}' + +In order to generate the JSON string, you can use the following command: + +.. code-block:: bash + + echo -n '{"auths": {"": {"auth": "'$(echo -n ":" | base64 -w 0)'"}}}' + +Fetching passwords from cloud providers: + +.. code-block:: bash + + ECR_PASSWORD = $(aws ecr get-login-password --region eu-west-1) + GCP_PASSWORD = $(gcloud auth print-access-token) + AZURE_PASSWORD = $(az acr login --name --expose-token --output tsv) + + Configuration ------------- -+-------------------------------------------+-------------------------------+------------------------------------------+ -| Env Variable | Example | Description | -+===========================================+===============================+==========================================+ -| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | -+-------------------------------------------+-------------------------------+------------------------------------------+ -| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | -+-------------------------------------------+-------------------------------+------------------------------------------+ -| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | -+-------------------------------------------+-------------------------------+------------------------------------------+ -| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | -+-------------------------------------------+-------------------------------+------------------------------------------+ ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| Env Variable | Example | Description | ++===========================================+===================================================+==========================================+ +| ``TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE`` | ``/var/run/docker.sock`` | Path to Docker's socket used by ryuk | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_PRIVILEGED`` | ``false`` | Run ryuk as a privileged container | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ +| ``DOCKER_AUTH_CONFIG`` | ``{"auths": {"": {"auth": ""}}}`` | Custom registry auth config | ++-------------------------------------------+---------------------------------------------------+------------------------------------------+ Development and Contributing ---------------------------- -We recommend you use a `virtual environment `_ for development (:code:`python>=3.7` is required). After setting up your virtual environment, you can install all dependencies and test the installation by running the following snippet. + +We recommend you use a `Poetry `_ for development. +After having installed `poetry`, you can run the following snippet to set up your local dev environment. .. code-block:: bash - poetry install --all-extras - make /tests + make install Package Structure ^^^^^^^^^^^^^^^^^ -Testcontainers is a collection of `implicit namespace packages `__ to decouple the development of different extensions, e.g., :code:`testcontainers-mysql` and :code:`testcontainers-postgres` for MySQL and PostgreSQL database containers, respectively. The folder structure is as follows. +Testcontainers is a collection of `implicit namespace packages `__ +to decouple the development of different extensions, +e.g., :code:`testcontainers[mysql]` and :code:`testcontainers[postgres]` for MySQL and PostgreSQL database containers, respectively. + +The folder structure is as follows: .. code-block:: bash @@ -133,10 +174,11 @@ Testcontainers is a collection of `implicit namespace packages __`. +You want to contribute a new feature or container? Great! +- We recommend you first `open an issue `_ +- Then follow the suggestions from the team +- We also have a Pull Request `template `_ for new containers! From 41fbdd05b1dab13db6ff413893e2c430520fd109 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:23:20 -0400 Subject: [PATCH 404/425] chore(main): release testcontainers 4.7.1 (#624) :robot: I have created a release *beep* *boop* --- ## [4.7.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.7.0...testcontainers-v4.7.1) (2024-07-02) ### Bug Fixes * **core:** bad rebase from [#579](https://github.com/testcontainers/testcontainers-python/issues/579) ([#635](https://github.com/testcontainers/testcontainers-python/issues/635)) ([4766e48](https://github.com/testcontainers/testcontainers-python/commit/4766e4829407c19de039effc7ea8fcc8b6dcc214)) * **modules:** Mailpit Container ([#625](https://github.com/testcontainers/testcontainers-python/issues/625)) ([0b866ff](https://github.com/testcontainers/testcontainers-python/commit/0b866ff3c2d462fa5032945dfa2efd4bd59079da)) * **modules:** SFTP Server Container ([#629](https://github.com/testcontainers/testcontainers-python/issues/629)) ([2e7dbf1](https://github.com/testcontainers/testcontainers-python/commit/2e7dbf1185c68c7cbfb6bdac7457d1d5f86aba19)) * **network:** Now able to use Network without context, and has labels to be automatically cleaned up ([#627](https://github.com/testcontainers/testcontainers-python/issues/627)) ([#630](https://github.com/testcontainers/testcontainers-python/issues/630)) ([e93bc29](https://github.com/testcontainers/testcontainers-python/commit/e93bc29c1781c4e73840c4c587160f8e5805feea)) * **postgres:** get_connection_url(driver=None) should return postgres://... ([#588](https://github.com/testcontainers/testcontainers-python/issues/588)) ([01d6c18](https://github.com/testcontainers/testcontainers-python/commit/01d6c182485555ee83f560739c34f089b0e54e0b)), closes [#587](https://github.com/testcontainers/testcontainers-python/issues/587) * update test module import ([#623](https://github.com/testcontainers/testcontainers-python/issues/623)) ([16f6ca4](https://github.com/testcontainers/testcontainers-python/commit/16f6ca42621866d8ff87ca539a84da27dbe9a4c4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index e2f35d5a1..951bf34a4 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.7.0" + ".": "4.7.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b3c38bc..e7939b504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [4.7.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.7.0...testcontainers-v4.7.1) (2024-07-02) + + +### Bug Fixes + +* **core:** bad rebase from [#579](https://github.com/testcontainers/testcontainers-python/issues/579) ([#635](https://github.com/testcontainers/testcontainers-python/issues/635)) ([4766e48](https://github.com/testcontainers/testcontainers-python/commit/4766e4829407c19de039effc7ea8fcc8b6dcc214)) +* **modules:** Mailpit Container ([#625](https://github.com/testcontainers/testcontainers-python/issues/625)) ([0b866ff](https://github.com/testcontainers/testcontainers-python/commit/0b866ff3c2d462fa5032945dfa2efd4bd59079da)) +* **modules:** SFTP Server Container ([#629](https://github.com/testcontainers/testcontainers-python/issues/629)) ([2e7dbf1](https://github.com/testcontainers/testcontainers-python/commit/2e7dbf1185c68c7cbfb6bdac7457d1d5f86aba19)) +* **network:** Now able to use Network without context, and has labels to be automatically cleaned up ([#627](https://github.com/testcontainers/testcontainers-python/issues/627)) ([#630](https://github.com/testcontainers/testcontainers-python/issues/630)) ([e93bc29](https://github.com/testcontainers/testcontainers-python/commit/e93bc29c1781c4e73840c4c587160f8e5805feea)) +* **postgres:** get_connection_url(driver=None) should return postgres://... ([#588](https://github.com/testcontainers/testcontainers-python/issues/588)) ([01d6c18](https://github.com/testcontainers/testcontainers-python/commit/01d6c182485555ee83f560739c34f089b0e54e0b)), closes [#587](https://github.com/testcontainers/testcontainers-python/issues/587) +* update test module import ([#623](https://github.com/testcontainers/testcontainers-python/issues/623)) ([16f6ca4](https://github.com/testcontainers/testcontainers-python/commit/16f6ca42621866d8ff87ca539a84da27dbe9a4c4)) + ## [4.7.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.6.0...testcontainers-v4.7.0) (2024-06-28) diff --git a/pyproject.toml b/pyproject.toml index dfa47f7fa..58814ace1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.7.0" # auto-incremented by release-please +version = "4.7.1" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 0ce4fecb2872620fd4cb96313abcba4353442cfd Mon Sep 17 00:00:00 2001 From: David Ankin Date: Thu, 4 Jul 2024 11:35:33 -0700 Subject: [PATCH 405/425] fix(kafka): add a flag to limit to first hostname for use with networks (#638) fix #637 --- .../kafka/testcontainers/kafka/__init__.py | 21 ++++++++++++++++- modules/kafka/tests/test_kafka.py | 23 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index ea837be37..ccd7f5b77 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -1,6 +1,8 @@ import tarfile import time +from dataclasses import dataclass, field from io import BytesIO +from os import environ from textwrap import dedent from typing_extensions import Self @@ -14,7 +16,21 @@ __all__ = [ "KafkaContainer", "RedpandaContainer", + "kafka_config", ] +LIMIT_BROKER_ENV_VAR = "TC_KAFKA_LIMIT_BROKER_TO_FIRST_HOST" + + +@dataclass +class _KafkaConfig: + limit_broker_to_first_host: bool = field(default_factory=lambda: environ.get(LIMIT_BROKER_ENV_VAR) == "true") + """ + This option is useful for a setup with a network, + see testcontainers/testcontainers-python#637 for more details + """ + + +kafka_config = _KafkaConfig() class KafkaContainer(DockerContainer): @@ -136,7 +152,10 @@ def get_bootstrap_server(self) -> str: def tc_start(self) -> None: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) - listeners = f"PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092" + if kafka_config.limit_broker_to_first_host: + listeners = f"PLAINTEXT://{host}:{port},BROKER://$(hostname -i | cut -d' ' -f1):9092" + else: + listeners = f"PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092" data = ( dedent( f""" diff --git a/modules/kafka/tests/test_kafka.py b/modules/kafka/tests/test_kafka.py index eb1a48127..901f3f0c3 100644 --- a/modules/kafka/tests/test_kafka.py +++ b/modules/kafka/tests/test_kafka.py @@ -1,6 +1,8 @@ -from kafka import KafkaConsumer, KafkaProducer, TopicPartition +import pytest +from kafka import KafkaAdminClient, KafkaConsumer, KafkaProducer, TopicPartition -from testcontainers.kafka import KafkaContainer +from testcontainers.core.network import Network +from testcontainers.kafka import KafkaContainer, kafka_config def test_kafka_producer_consumer(): @@ -20,6 +22,23 @@ def test_kafka_producer_consumer_custom_port(): produce_and_consume_kafka_message(container) +def test_kafka_on_networks(monkeypatch: pytest.MonkeyPatch): + """ + this test case comes from testcontainers/testcontainers-python#637 + """ + monkeypatch.setattr(kafka_config, "limit_broker_to_first_host", True) + + with Network() as network: + kafka_ctr = KafkaContainer() + kafka_ctr.with_network(network) + kafka_ctr.with_network_aliases("kafka") + + with kafka_ctr: + print("started") # Will not reach here and timeout + admin_client = KafkaAdminClient(bootstrap_servers=[kafka_ctr.get_bootstrap_server()]) + print(admin_client.describe_cluster()) + + def produce_and_consume_kafka_message(container): topic = "test-topic" bootstrap_server = container.get_bootstrap_server() From 49ce5a5ff2ac46cf51920e16c5e39684886b699a Mon Sep 17 00:00:00 2001 From: Grieve Date: Tue, 16 Jul 2024 03:58:07 +0800 Subject: [PATCH 406/425] fix: Add container Trino (#642) Resolve #641 --- modules/trino/README.rst | 2 + .../trino/testcontainers/trino/__init__.py | 56 +++++++++++++++++++ modules/trino/tests/test_trino.py | 17 ++++++ poetry.lock | 29 +++++++++- pyproject.toml | 3 + 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 modules/trino/README.rst create mode 100644 modules/trino/testcontainers/trino/__init__.py create mode 100644 modules/trino/tests/test_trino.py diff --git a/modules/trino/README.rst b/modules/trino/README.rst new file mode 100644 index 000000000..95b4be930 --- /dev/null +++ b/modules/trino/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.trino.TrinoContainer +.. title:: testcontainers.trino.TrinoContainer diff --git a/modules/trino/testcontainers/trino/__init__.py b/modules/trino/testcontainers/trino/__init__.py new file mode 100644 index 000000000..97e3f9de4 --- /dev/null +++ b/modules/trino/testcontainers/trino/__init__.py @@ -0,0 +1,56 @@ +# +# 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 + +from testcontainers.core.config import testcontainers_config as c +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs +from trino.dbapi import connect + + +class TrinoContainer(DbContainer): + def __init__( + self, + image="trinodb/trino:latest", + user: str = "test", + port: int = 8080, + **kwargs, + ): + super().__init__(image=image, **kwargs) + self.user = user + self.port = port + self.with_exposed_ports(self.port) + + @wait_container_is_ready() + def _connect(self) -> None: + wait_for_logs( + self, + re.compile(".*======== SERVER STARTED ========.*", re.MULTILINE).search, + c.max_tries, + c.sleep_time, + ) + conn = connect( + host=self.get_container_host_ip(), + port=self.get_exposed_port(self.port), + user=self.user, + ) + cur = conn.cursor() + cur.execute("SELECT 1") + cur.fetchall() + conn.close() + + def get_connection_url(self): + return f"trino://{self.user}@{self.get_container_host_ip()}:{self.port}" + + def _configure(self): + pass diff --git a/modules/trino/tests/test_trino.py b/modules/trino/tests/test_trino.py new file mode 100644 index 000000000..c1a70230b --- /dev/null +++ b/modules/trino/tests/test_trino.py @@ -0,0 +1,17 @@ +from testcontainers.trino import TrinoContainer +from trino.dbapi import connect + + +def test_docker_run_trino(): + container = TrinoContainer("trinodb/trino:451") + with container as trino: + conn = connect( + host=trino.get_container_host_ip(), + port=trino.get_exposed_port(trino.port), + user="test", + ) + cur = conn.cursor() + cur.execute("SELECT version()") + rows = cur.fetchall() + assert rows[0][0] == "451" + conn.close() diff --git a/poetry.lock b/poetry.lock index 596444b7d..56f42ea3b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1913,7 +1913,6 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, - {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -4108,6 +4107,31 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "trino" +version = "0.329.0" +description = "Client for the Trino distributed SQL Engine" +optional = true +python-versions = ">=3.8" +files = [ + {file = "trino-0.329.0-py3-none-any.whl", hash = "sha256:74b82a38f16193ad869e63fb837d651e66c044f19a817232787e27c5d44b671f"}, + {file = "trino-0.329.0.tar.gz", hash = "sha256:1d976467726ec3d0fa120a64e61fdb8caf13295db207051e2cc267a952af989b"}, +] + +[package.dependencies] +python-dateutil = "*" +pytz = "*" +requests = ">=2.31.0" +tzlocal = "*" + +[package.extras] +all = ["requests-kerberos", "sqlalchemy (>=1.3)"] +external-authentication-token-cache = ["keyring"] +gssapi = ["requests-gssapi"] +kerberos = ["requests-kerberos"] +sqlalchemy = ["sqlalchemy (>=1.3)"] +tests = ["black", "httpretty (<1.1)", "isort", "pre-commit", "pytest", "pytest-runner", "requests-gssapi", "requests-kerberos", "sqlalchemy (>=1.3)"] + [[package]] name = "trio" version = "0.24.0" @@ -4571,10 +4595,11 @@ registry = ["bcrypt"] selenium = ["selenium"] sftp = ["cryptography"] test-module-import = ["httpx"] +trino = ["trino"] vault = [] weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "4694e6bedeb7263ba9b7de579b81913f285161d5d27d453bd36a616e9ce3eade" +content-hash = "ef48ca48ddc2bc6ac68487e1674d1e6973c3a14b2b5c41235262af20695fe432" diff --git a/pyproject.toml b/pyproject.toml index 58814ace1..ac59f12ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ packages = [ { include = "testcontainers", from = "modules/registry" }, { include = "testcontainers", from = "modules/sftp" }, { include = "testcontainers", from = "modules/selenium" }, + { include = "testcontainers", from = "modules/trino" }, { include = "testcontainers", from = "modules/vault" }, { include = "testcontainers", from = "modules/weaviate" }, ] @@ -111,6 +112,7 @@ bcrypt = { version = "*", optional = true } httpx = { version = "*", optional = true } azure-cosmos = { version = "*", optional = true } cryptography = { version = "*", optional = true } +trino = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -153,6 +155,7 @@ sftp = ["cryptography"] vault = [] weaviate = ["weaviate-client"] chroma = ["chromadb-client"] +trino = ["trino"] [tool.poetry.group.dev.dependencies] mypy = "1.7.1" From df07586d8844c757db62ac0f8b7914c67fd96e05 Mon Sep 17 00:00:00 2001 From: Oliver Lambson Date: Mon, 15 Jul 2024 21:58:20 +0200 Subject: [PATCH 407/425] fix(modules): Mailpit container base API URL helper method (#643) Minimal change to expose some helper methods on the `MailpitContainer` --- modules/mailpit/testcontainers/mailpit/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/mailpit/testcontainers/mailpit/__init__.py b/modules/mailpit/testcontainers/mailpit/__init__.py index 63a26a7c6..466755949 100644 --- a/modules/mailpit/testcontainers/mailpit/__init__.py +++ b/modules/mailpit/testcontainers/mailpit/__init__.py @@ -183,6 +183,12 @@ def stop(self, *args: Any, **kwargs: Any) -> None: def get_exposed_smtp_port(self) -> int: return int(self.get_exposed_port(self.smtp_port)) + def get_exposed_ui_port(self) -> int: + return int(self.get_exposed_port(self.ui_port)) + + def get_base_api_url(self) -> str: + return f"http://{self.get_container_host_ip()}:{self.get_exposed_ui_port()}" + class _TLSCertificates(NamedTuple): private_key: bytes From 766c382a3aee4eb512ee0f482d6595d3412097c3 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Mon, 15 Jul 2024 22:59:09 +0300 Subject: [PATCH 408/425] fix(core): Improve private registry support (tolerate not implemented fields in DOCKER_AUTH_CONFIG) (#647) Continuing #562, got some feedback regarding an issue with unsupported use cases. In this PR we will try to: 1. Map the use cases 2. Raise a warning regarding unsupported uses cases (hopefully they will be added later) 3. Address/Fix the issue where unsupported JSON schema for `DOCKER_AUTH_CONFIG` leads to an error As always any feedback will be much appreciated. _Please note this PR does not implement all use-cases just does a better job at preparing and handling them for now_ --- core/testcontainers/core/auth.py | 88 +++++++++++++++++++++++ core/testcontainers/core/docker_client.py | 11 +-- core/testcontainers/core/utils.py | 31 -------- core/tests/test_auth.py | 84 ++++++++++++++++++++++ core/tests/test_docker_client.py | 54 +++++++++++++- core/tests/test_utils.py | 42 ----------- 6 files changed, 232 insertions(+), 78 deletions(-) create mode 100644 core/testcontainers/core/auth.py create mode 100644 core/tests/test_auth.py delete mode 100644 core/tests/test_utils.py diff --git a/core/testcontainers/core/auth.py b/core/testcontainers/core/auth.py new file mode 100644 index 000000000..906dca1d5 --- /dev/null +++ b/core/testcontainers/core/auth.py @@ -0,0 +1,88 @@ +import base64 as base64 +import json as json +from collections import namedtuple +from logging import warning +from typing import Optional + +DockerAuthInfo = namedtuple("DockerAuthInfo", ["registry", "username", "password"]) + +_AUTH_WARNINGS = { + "credHelpers": "DOCKER_AUTH_CONFIG is experimental, credHelpers not supported yet", + "credsStore": "DOCKER_AUTH_CONFIG is experimental, credsStore not supported yet", +} + + +def process_docker_auth_config_encoded(auth_config_dict: dict) -> list[DockerAuthInfo]: + """ + Process the auths config. + + Example: + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=" + } + } + } + + Returns a list of DockerAuthInfo objects. + """ + auth_info: list[DockerAuthInfo] = [] + + auths = auth_config_dict.get("auths") + for registry, auth in auths.items(): + auth_str = auth.get("auth") + auth_str = base64.b64decode(auth_str).decode("utf-8") + username, password = auth_str.split(":") + auth_info.append(DockerAuthInfo(registry, username, password)) + + return auth_info + + +def process_docker_auth_config_cred_helpers(auth_config_dict: dict) -> None: + """ + Process the credHelpers config. + + Example: + { + "credHelpers": { + ".dkr.ecr..amazonaws.com": "ecr-login" + } + } + + This is not supported yet. + """ + if "credHelpers" in _AUTH_WARNINGS: + warning(_AUTH_WARNINGS.pop("credHelpers")) + + +def process_docker_auth_config_store(auth_config_dict: dict) -> None: + """ + Process the credsStore config. + + Example: + { + "credsStore": "ecr-login" + } + + This is not supported yet. + """ + if "credsStore" in _AUTH_WARNINGS: + warning(_AUTH_WARNINGS.pop("credsStore")) + + +def parse_docker_auth_config(auth_config: str) -> Optional[list[DockerAuthInfo]]: + """Parse the docker auth config from a string and handle the different formats.""" + try: + auth_config_dict: dict = json.loads(auth_config) + if "credHelpers" in auth_config: + process_docker_auth_config_cred_helpers(auth_config_dict) + if "credsStore" in auth_config: + process_docker_auth_config_store(auth_config_dict) + if "auths" in auth_config: + return process_docker_auth_config_encoded(auth_config_dict) + + except (json.JSONDecodeError, KeyError, ValueError) as exp: + raise ValueError("Could not parse docker auth config") from exp + + return None diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 286e1ef9f..c540b9f7f 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -24,9 +24,10 @@ from docker.models.images import Image, ImageCollection from typing_extensions import ParamSpec +from testcontainers.core.auth import DockerAuthInfo, parse_docker_auth_config from testcontainers.core.config import testcontainers_config as c from testcontainers.core.labels import SESSION_ID, create_labels -from testcontainers.core.utils import default_gateway_ip, inside_container, parse_docker_auth_config, setup_logger +from testcontainers.core.utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) @@ -67,8 +68,11 @@ def __init__(self, **kwargs) -> None: self.client.api.headers["x-tc-sid"] = SESSION_ID self.client.api.headers["User-Agent"] = "tc-python/" + importlib.metadata.version("testcontainers") + # Verify if we have a docker auth config and login if we do if docker_auth_config := get_docker_auth_config(): - self.login(docker_auth_config) + LOGGER.debug(f"DOCKER_AUTH_CONFIG found: {docker_auth_config}") + if auth_config := parse_docker_auth_config(docker_auth_config): + self.login(auth_config[0]) # Only using the first auth config) @_wrapped_container_collection def run( @@ -203,11 +207,10 @@ def host(self) -> str: return ip_address return "localhost" - def login(self, docker_auth_config: str) -> None: + def login(self, auth_config: DockerAuthInfo) -> None: """ Login to a docker registry using the given auth config. """ - auth_config = parse_docker_auth_config(docker_auth_config)[0] # Only using the first auth config login_info = self.client.login(**auth_config._asdict()) LOGGER.debug(f"logged in using {login_info}") diff --git a/core/testcontainers/core/utils.py b/core/testcontainers/core/utils.py index 0061e8329..5ca1c2f7d 100644 --- a/core/testcontainers/core/utils.py +++ b/core/testcontainers/core/utils.py @@ -1,18 +1,13 @@ -import base64 -import json import logging import os import platform import subprocess import sys -from collections import namedtuple LINUX = "linux" MAC = "mac" WIN = "win" -DockerAuthInfo = namedtuple("DockerAuthInfo", ["registry", "username", "password"]) - def setup_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) @@ -82,29 +77,3 @@ def raise_for_deprecated_parameter(kwargs: dict, name: str, replacement: str) -> if kwargs.pop(name, None): raise ValueError(f"Use `{replacement}` instead of `{name}`") return kwargs - - -def parse_docker_auth_config(auth_config: str) -> list[DockerAuthInfo]: - """ - Parse the docker auth config from a string. - - Example: - { - "auths": { - "https://index.docker.io/v1/": { - "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=" - } - } - } - """ - auth_info: list[DockerAuthInfo] = [] - try: - auth_config_dict: dict = json.loads(auth_config).get("auths") - for registry, auth in auth_config_dict.items(): - auth_str = auth.get("auth") - auth_str = base64.b64decode(auth_str).decode("utf-8") - username, password = auth_str.split(":") - auth_info.append(DockerAuthInfo(registry, username, password)) - return auth_info - except (json.JSONDecodeError, KeyError, ValueError) as exp: - raise ValueError("Could not parse docker auth config") from exp diff --git a/core/tests/test_auth.py b/core/tests/test_auth.py new file mode 100644 index 000000000..a7581f42e --- /dev/null +++ b/core/tests/test_auth.py @@ -0,0 +1,84 @@ +import json +import pytest + +from testcontainers.core.auth import parse_docker_auth_config, DockerAuthInfo + + +def test_parse_docker_auth_config_encoded(): + auth_config_json = '{"auths":{"https://index.docker.io/v1/":{"auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}' + auth_info = parse_docker_auth_config(auth_config_json) + assert len(auth_info) == 1 + assert auth_info[0] == DockerAuthInfo( + registry="https://index.docker.io/v1/", + username="username", + password="password", + ) + + +def test_parse_docker_auth_config_cred_helpers(): + auth_dict = {"credHelpers": {".dkr.ecr..amazonaws.com": "ecr-login"}} + auth_config_json = json.dumps(auth_dict) + assert parse_docker_auth_config(auth_config_json) is None + + +def test_parse_docker_auth_config_store(): + auth_dict = {"credsStore": "ecr-login"} + auth_config_json = json.dumps(auth_dict) + assert parse_docker_auth_config(auth_config_json) is None + + +def test_parse_docker_auth_config_encoded_multiple(): + auth_dict = { + "auths": { + "localhost:5000": {"auth": "dXNlcjE6cGFzczE=="}, + "https://example.com": {"auth": "dXNlcl9uZXc6cGFzc19uZXc=="}, + "example2.com": {"auth": "YWJjOjEyMw==="}, + } + } + auth_config_json = json.dumps(auth_dict) + auth_info = parse_docker_auth_config(auth_config_json) + assert len(auth_info) == 3 + assert auth_info[0] == DockerAuthInfo( + registry="localhost:5000", + username="user1", + password="pass1", + ) + assert auth_info[1] == DockerAuthInfo( + registry="https://example.com", + username="user_new", + password="pass_new", + ) + assert auth_info[2] == DockerAuthInfo( + registry="example2.com", + username="abc", + password="123", + ) + + +def test_parse_docker_auth_config_unknown(): + auth_config_str = '{"key": "value"}' + assert parse_docker_auth_config(auth_config_str) is None + + +def test_parse_docker_auth_config_error(): + auth_config_str = "bad//string" + with pytest.raises(ValueError): + parse_docker_auth_config(auth_config_str) + + +def test_parse_docker_auth_all(): + test_dict = { + "auths": { + "localhost:5000": {"auth": "dXNlcjE6cGFzczE=="}, + }, + "credHelpers": {".dkr.ecr..amazonaws.com": "ecr-login"}, + "credsStore": "ecr-login", + } + auth_config_json = json.dumps(test_dict) + assert parse_docker_auth_config(auth_config_json) == [ + DockerAuthInfo( + registry="localhost:5000", + username="user1", + password="pass1", + ) + ] diff --git a/core/tests/test_docker_client.py b/core/tests/test_docker_client.py index 9234d3062..6bfe388de 100644 --- a/core/tests/test_docker_client.py +++ b/core/tests/test_docker_client.py @@ -1,4 +1,5 @@ import os +import json from collections import namedtuple from unittest import mock from unittest.mock import MagicMock, patch @@ -8,9 +9,11 @@ from testcontainers.core.config import testcontainers_config as c from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient -from testcontainers.core.utils import parse_docker_auth_config +from testcontainers.core.auth import parse_docker_auth_config from testcontainers.core.image import DockerImage +from pytest import mark + def test_docker_client_from_env(): test_kwargs = {"test_kw": "test_value"} @@ -48,6 +51,55 @@ def test_docker_client_login(): mock_docker.from_env.return_value.login.assert_called_with(**{"value": "test"}) +def test_docker_client_login_empty_get_docker_auth_config(): + mock_docker = MagicMock(spec=docker) + mock_get_docker_auth_config = MagicMock() + mock_get_docker_auth_config.return_value = None + + with ( + mock.patch.object(c, "_docker_auth_config", "test"), + patch("testcontainers.core.docker_client.docker", mock_docker), + patch("testcontainers.core.docker_client.get_docker_auth_config", mock_get_docker_auth_config), + ): + DockerClient() + + mock_docker.from_env.return_value.login.assert_not_called() + + +def test_docker_client_login_empty_parse_docker_auth_config(): + mock_docker = MagicMock(spec=docker) + mock_parse_docker_auth_config = MagicMock(spec=parse_docker_auth_config) + mock_utils = MagicMock() + mock_utils.parse_docker_auth_config = mock_parse_docker_auth_config + mock_parse_docker_auth_config.return_value = None + + with ( + mock.patch.object(c, "_docker_auth_config", "test"), + patch("testcontainers.core.docker_client.docker", mock_docker), + patch("testcontainers.core.docker_client.parse_docker_auth_config", mock_parse_docker_auth_config), + ): + DockerClient() + + mock_docker.from_env.return_value.login.assert_not_called() + + +# This is used to make sure we don't fail (nor try to login) when we have unsupported auth config +@mark.parametrize("auth_config_sample", [{"credHelpers": {"test": "login"}}, {"credsStore": "login"}]) +def test_docker_client_login_unsupported_auth_config(auth_config_sample): + mock_docker = MagicMock(spec=docker) + mock_get_docker_auth_config = MagicMock() + mock_get_docker_auth_config.return_value = json.dumps(auth_config_sample) + + with ( + mock.patch.object(c, "_docker_auth_config", "test"), + patch("testcontainers.core.docker_client.docker", mock_docker), + patch("testcontainers.core.docker_client.get_docker_auth_config", mock_get_docker_auth_config), + ): + DockerClient() + + mock_docker.from_env.return_value.login.assert_not_called() + + def test_container_docker_client_kw(): test_kwargs = {"test_kw": "test_value"} mock_docker = MagicMock(spec=docker) diff --git a/core/tests/test_utils.py b/core/tests/test_utils.py deleted file mode 100644 index 56f96fbf0..000000000 --- a/core/tests/test_utils.py +++ /dev/null @@ -1,42 +0,0 @@ -import json - -from testcontainers.core.utils import parse_docker_auth_config, DockerAuthInfo - - -def test_parse_docker_auth_config(): - auth_config_json = '{"auths":{"https://index.docker.io/v1/":{"auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}' - auth_info = parse_docker_auth_config(auth_config_json) - assert len(auth_info) == 1 - assert auth_info[0] == DockerAuthInfo( - registry="https://index.docker.io/v1/", - username="username", - password="password", - ) - - -def test_parse_docker_auth_config_multiple(): - auth_dict = { - "auths": { - "localhost:5000": {"auth": "dXNlcjE6cGFzczE=="}, - "https://example.com": {"auth": "dXNlcl9uZXc6cGFzc19uZXc=="}, - "example2.com": {"auth": "YWJjOjEyMw==="}, - } - } - auth_config_json = json.dumps(auth_dict) - auth_info = parse_docker_auth_config(auth_config_json) - assert len(auth_info) == 3 - assert auth_info[0] == DockerAuthInfo( - registry="localhost:5000", - username="user1", - password="pass1", - ) - assert auth_info[1] == DockerAuthInfo( - registry="https://example.com", - username="user_new", - password="pass_new", - ) - assert auth_info[2] == DockerAuthInfo( - registry="example2.com", - username="abc", - password="123", - ) From 068c43113592369af58e354cdd352151244d89a3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:01:29 -0400 Subject: [PATCH 409/425] chore(main): release testcontainers 4.7.2 (#639) :robot: I have created a release *beep* *boop* --- ## [4.7.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.7.1...testcontainers-v4.7.2) (2024-07-15) ### Bug Fixes * Add container Trino ([#642](https://github.com/testcontainers/testcontainers-python/issues/642)) ([49ce5a5](https://github.com/testcontainers/testcontainers-python/commit/49ce5a5ff2ac46cf51920e16c5e39684886b699a)), closes [#641](https://github.com/testcontainers/testcontainers-python/issues/641) * **core:** Improve private registry support (tolerate not implemented fields in DOCKER_AUTH_CONFIG) ([#647](https://github.com/testcontainers/testcontainers-python/issues/647)) ([766c382](https://github.com/testcontainers/testcontainers-python/commit/766c382a3aee4eb512ee0f482d6595d3412097c3)) * **kafka:** add a flag to limit to first hostname for use with networks ([#638](https://github.com/testcontainers/testcontainers-python/issues/638)) ([0ce4fec](https://github.com/testcontainers/testcontainers-python/commit/0ce4fecb2872620fd4cb96313abcba4353442cfd)), closes [#637](https://github.com/testcontainers/testcontainers-python/issues/637) * **modules:** Mailpit container base API URL helper method ([#643](https://github.com/testcontainers/testcontainers-python/issues/643)) ([df07586](https://github.com/testcontainers/testcontainers-python/commit/df07586d8844c757db62ac0f8b7914c67fd96e05)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- CHANGELOG.md | 10 ++++++++++ pyproject.toml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 951bf34a4..e62ece491 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.7.1" + ".": "4.7.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e7939b504..e001e139b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [4.7.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.7.1...testcontainers-v4.7.2) (2024-07-15) + + +### Bug Fixes + +* Add container Trino ([#642](https://github.com/testcontainers/testcontainers-python/issues/642)) ([49ce5a5](https://github.com/testcontainers/testcontainers-python/commit/49ce5a5ff2ac46cf51920e16c5e39684886b699a)), closes [#641](https://github.com/testcontainers/testcontainers-python/issues/641) +* **core:** Improve private registry support (tolerate not implemented fields in DOCKER_AUTH_CONFIG) ([#647](https://github.com/testcontainers/testcontainers-python/issues/647)) ([766c382](https://github.com/testcontainers/testcontainers-python/commit/766c382a3aee4eb512ee0f482d6595d3412097c3)) +* **kafka:** add a flag to limit to first hostname for use with networks ([#638](https://github.com/testcontainers/testcontainers-python/issues/638)) ([0ce4fec](https://github.com/testcontainers/testcontainers-python/commit/0ce4fecb2872620fd4cb96313abcba4353442cfd)), closes [#637](https://github.com/testcontainers/testcontainers-python/issues/637) +* **modules:** Mailpit container base API URL helper method ([#643](https://github.com/testcontainers/testcontainers-python/issues/643)) ([df07586](https://github.com/testcontainers/testcontainers-python/commit/df07586d8844c757db62ac0f8b7914c67fd96e05)) + ## [4.7.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.7.0...testcontainers-v4.7.1) (2024-07-02) diff --git a/pyproject.toml b/pyproject.toml index ac59f12ba..76ca27ec2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "testcontainers" -version = "4.7.1" # auto-incremented by release-please +version = "4.7.2" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" authors = ["Sergey Pirogov "] maintainers = [ From 9161cb64a0a13b54a981b2b846a4d073db8c30a2 Mon Sep 17 00:00:00 2001 From: Roy Moore Date: Wed, 31 Jul 2024 16:14:03 +0300 Subject: [PATCH 410/425] feat(new): Added AWS Lambda module (#655) As part of the effort described, detailed and presented on https://github.com/testcontainers/testcontainers-python/pull/559 This is the 4th (and final in this track) PR that should provide support for AWS Lambda containers. This module will add the ability to test and run Amazon Lambdas (using the built-in runtime interface emulator) For example: ```python from testcontainers.aws import AWSLambdaContainer from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.core.image import DockerImage with DockerImage(path="./modules/aws/tests/lambda_sample", tag="test-lambda:latest") as image: with AWSLambdaContainer(image=image, port=8080) as func: response = func.send_request(data={'payload': 'some data'}) assert response.status_code == 200 assert "Hello from AWS Lambda using Python" in response.json() delay = wait_for_logs(func, "START RequestId:") ``` This can (and probably will) be used with the provided [LocalStackContainer](https://testcontainers-python.readthedocs.io/en/latest/modules/localstack/README.html) to help simulate more advance AWS cases. --- Based on the work done on: - https://github.com/testcontainers/testcontainers-python/pull/585 - https://github.com/testcontainers/testcontainers-python/pull/595 - https://github.com/testcontainers/testcontainers-python/pull/612 Expended from issue https://github.com/testcontainers/testcontainers-python/issues/83 --- modules/aws/README.rst | 22 ++++++++ modules/aws/testcontainers/aws/__init__.py | 1 + modules/aws/testcontainers/aws/aws_lambda.py | 53 ++++++++++++++++++ modules/aws/tests/lambda_sample/Dockerfile | 10 ++++ .../tests/lambda_sample/lambda_function.py | 5 ++ modules/aws/tests/test_aws.py | 56 +++++++++++++++++++ poetry.lock | 3 +- pyproject.toml | 2 + 8 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 modules/aws/README.rst create mode 100644 modules/aws/testcontainers/aws/__init__.py create mode 100644 modules/aws/testcontainers/aws/aws_lambda.py create mode 100644 modules/aws/tests/lambda_sample/Dockerfile create mode 100644 modules/aws/tests/lambda_sample/lambda_function.py create mode 100644 modules/aws/tests/test_aws.py diff --git a/modules/aws/README.rst b/modules/aws/README.rst new file mode 100644 index 000000000..a44dc856f --- /dev/null +++ b/modules/aws/README.rst @@ -0,0 +1,22 @@ +:code:`testcontainers-aws` is a set of AWS containers modules that can be used to create AWS containers. + +.. autoclass:: testcontainers.aws.AWSLambdaContainer +.. title:: testcontainers.aws.AWSLambdaContainer + +The following environment variables are used by the AWS Lambda container: + ++-------------------------------+--------------------------+------------------------------+ +| Env Variable | Default | Notes | ++===============================+==========================+==============================+ +| ``AWS_DEFAULT_REGION`` | ``us-west-1`` | Fetched from os environment | ++-------------------------------+--------------------------+------------------------------+ +| ``AWS_ACCESS_KEY_ID`` | ``testcontainers-aws`` | Fetched from os environment | ++-------------------------------+--------------------------+------------------------------+ +| ``AWS_SECRET_ACCESS_KEY`` | ``testcontainers-aws`` | Fetched from os environment | ++-------------------------------+--------------------------+------------------------------+ + + Each one of the environment variables is expected to be set in the host machine where the test is running. + +Make sure you are using an image based on :code:`public.ecr.aws/lambda/python` + +Please checkout https://docs.aws.amazon.com/lambda/latest/dg/python-image.html for more information on how to run AWS Lambda functions locally. diff --git a/modules/aws/testcontainers/aws/__init__.py b/modules/aws/testcontainers/aws/__init__.py new file mode 100644 index 000000000..f16705c86 --- /dev/null +++ b/modules/aws/testcontainers/aws/__init__.py @@ -0,0 +1 @@ +from .aws_lambda import AWSLambdaContainer # noqa: F401 diff --git a/modules/aws/testcontainers/aws/aws_lambda.py b/modules/aws/testcontainers/aws/aws_lambda.py new file mode 100644 index 000000000..30a1f0af9 --- /dev/null +++ b/modules/aws/testcontainers/aws/aws_lambda.py @@ -0,0 +1,53 @@ +import os +from typing import Union + +import httpx + +from testcontainers.core.image import DockerImage +from testcontainers.generic.server import ServerContainer + +RIE_PATH = "/2015-03-31/functions/function/invocations" +# AWS OS-only base images contain an Amazon Linux distribution and the runtime interface emulator (RIE) for Lambda. + + +class AWSLambdaContainer(ServerContainer): + """ + AWS Lambda container that is based on a custom image. + + Example: + + .. doctest:: + + >>> from testcontainers.aws import AWSLambdaContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path="./modules/aws/tests/lambda_sample", tag="test-lambda:latest") as image: + ... with AWSLambdaContainer(image=image, port=8080) as func: + ... response = func.send_request(data={'payload': 'some data'}) + ... assert response.status_code == 200 + ... assert "Hello from AWS Lambda using Python" in response.json() + ... delay = wait_for_logs(func, "START RequestId:") + + :param image: Docker image to be used for the container. + :param port: Port to be exposed on the container (default: 8080). + """ + + def __init__(self, image: Union[str, DockerImage], port: int = 8080) -> None: + super().__init__(port, str(image)) + self.with_env("AWS_DEFAULT_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-west-1")) + self.with_env("AWS_ACCESS_KEY_ID", os.environ.get("AWS_ACCESS_KEY_ID", "testcontainers-aws")) + self.with_env("AWS_SECRET_ACCESS_KEY", os.environ.get("AWS_SECRET_ACCESS_KEY", "testcontainers-aws")) + + def get_api_url(self) -> str: + return self._create_connection_url() + RIE_PATH + + def send_request(self, data: dict) -> httpx.Response: + """ + Send a request to the AWS Lambda function. + + :param data: Data to be sent to the AWS Lambda function. + :return: Response from the AWS Lambda function. + """ + client = self.get_client() + return client.post(self.get_api_url(), json=data) diff --git a/modules/aws/tests/lambda_sample/Dockerfile b/modules/aws/tests/lambda_sample/Dockerfile new file mode 100644 index 000000000..5d071c802 --- /dev/null +++ b/modules/aws/tests/lambda_sample/Dockerfile @@ -0,0 +1,10 @@ +FROM public.ecr.aws/lambda/python:3.9 + +RUN pip install boto3 + +COPY lambda_function.py ${LAMBDA_TASK_ROOT} + +EXPOSE 8080 + +# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) +CMD [ "lambda_function.handler" ] diff --git a/modules/aws/tests/lambda_sample/lambda_function.py b/modules/aws/tests/lambda_sample/lambda_function.py new file mode 100644 index 000000000..b253ed172 --- /dev/null +++ b/modules/aws/tests/lambda_sample/lambda_function.py @@ -0,0 +1,5 @@ +import sys + + +def handler(event, context): + return "Hello from AWS Lambda using Python" + sys.version + "!" diff --git a/modules/aws/tests/test_aws.py b/modules/aws/tests/test_aws.py new file mode 100644 index 000000000..873b87352 --- /dev/null +++ b/modules/aws/tests/test_aws.py @@ -0,0 +1,56 @@ +import re +import os + +import pytest +from unittest.mock import patch + +from testcontainers.core.image import DockerImage +from testcontainers.aws import AWSLambdaContainer +from testcontainers.aws.aws_lambda import RIE_PATH + +DOCKER_FILE_PATH = "./modules/aws/tests/lambda_sample" +IMAGE_TAG = "lambda:test" + + +def test_aws_lambda_container(): + with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda:latest") as image: + with AWSLambdaContainer(image=image, port=8080) as func: + assert func.get_container_host_ip() == "localhost" + assert func.internal_port == 8080 + assert func.env["AWS_DEFAULT_REGION"] == "us-west-1" + assert func.env["AWS_ACCESS_KEY_ID"] == "testcontainers-aws" + assert func.env["AWS_SECRET_ACCESS_KEY"] == "testcontainers-aws" + assert re.match(rf"http://localhost:\d+{RIE_PATH}", func.get_api_url()) + response = func.send_request(data={"payload": "test"}) + assert response.status_code == 200 + assert "Hello from AWS Lambda using Python" in response.json() + for log_str in ["START RequestId", "END RequestId", "REPORT RequestId"]: + assert log_str in func.get_stdout() + + +def test_aws_lambda_container_external_env_vars(): + vars = { + "AWS_DEFAULT_REGION": "region", + "AWS_ACCESS_KEY_ID": "id", + "AWS_SECRET_ACCESS_KEY": "key", + } + with patch.dict(os.environ, vars): + with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-env-vars:latest") as image: + with AWSLambdaContainer(image=image, port=8080) as func: + assert func.env["AWS_DEFAULT_REGION"] == "region" + assert func.env["AWS_ACCESS_KEY_ID"] == "id" + assert func.env["AWS_SECRET_ACCESS_KEY"] == "key" + + +def test_aws_lambda_container_no_port(): + with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-no-port:latest") as image: + with AWSLambdaContainer(image=image) as func: + response = func.send_request(data={"payload": "test"}) + assert response.status_code == 200 + + +def test_aws_lambda_container_no_path(): + with pytest.raises(TypeError): + with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-no-path:latest") as image: + with AWSLambdaContainer() as func: # noqa: F841 + pass diff --git a/poetry.lock b/poetry.lock index 56f42ea3b..a5d956318 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4558,6 +4558,7 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [extras] arangodb = ["python-arango"] +aws = ["boto3", "httpx"] azurite = ["azure-storage-blob"] cassandra = [] chroma = ["chromadb-client"] @@ -4602,4 +4603,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "ef48ca48ddc2bc6ac68487e1674d1e6973c3a14b2b5c41235262af20695fe432" +content-hash = "00155615fffa7f316221c1fafb895105911a3cce003b57713d9b76b7fd3e3214" diff --git a/pyproject.toml b/pyproject.toml index 76ca27ec2..35edc0d51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ packages = [ { include = "testcontainers", from = "core" }, { include = "testcontainers", from = "modules/arangodb" }, + { include = "testcontainers", from = "modules/aws"}, { include = "testcontainers", from = "modules/azurite" }, { include = "testcontainers", from = "modules/cassandra" }, { include = "testcontainers", from = "modules/chroma" }, @@ -116,6 +117,7 @@ trino = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] +aws = ["boto3", "httpx"] azurite = ["azure-storage-blob"] cassandra = [] clickhouse = ["clickhouse-driver"] From fa2081a7b325cdd316de28c99b029150022db203 Mon Sep 17 00:00:00 2001 From: Morgan Trench Date: Wed, 31 Jul 2024 21:14:15 +0800 Subject: [PATCH 411/425] fix(rabbitmq): add `vhost` as parameter to RabbitMqContainer (#656) Adds a `vhost` parameter to the RabbitMQContainer constructor that allows the `RABBITMQ_DEFAULT_VHOST` [environment variable](https://www.rabbitmq.com/docs/configure#supported-environment-variables) to be modified. Subsequently `vhost` is then also used inside the `get_connection_params` method for the `pika` connection parameters, which is used to test if the container is ready. --- .../rabbitmq/testcontainers/rabbitmq/__init__.py | 4 ++++ modules/rabbitmq/tests/test_rabbitmq.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py index 3e5ad0b33..cd7b079a4 100644 --- a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -31,6 +31,7 @@ def __init__( port: Optional[int] = None, username: Optional[str] = None, password: Optional[str] = None, + vhost: Optional[str] = None, **kwargs, ) -> None: """Initialize the RabbitMQ test container. @@ -45,11 +46,13 @@ def __init__( self.port = port or int(os.environ.get("RABBITMQ_NODE_PORT", 5672)) self.username = username or os.environ.get("RABBITMQ_DEFAULT_USER", "guest") self.password = password or os.environ.get("RABBITMQ_DEFAULT_PASS", "guest") + self.vhost = vhost or os.environ.get("RABBITMQ_DEFAULT_VHOST", "/") self.with_exposed_ports(self.port) self.with_env("RABBITMQ_NODE_PORT", self.port) self.with_env("RABBITMQ_DEFAULT_USER", self.username) self.with_env("RABBITMQ_DEFAULT_PASS", self.password) + self.with_env("RABBITMQ_DEFAULT_VHOST", self.vhost) @wait_container_is_ready(pika.exceptions.IncompatibleProtocolError, pika.exceptions.AMQPConnectionError) def readiness_probe(self) -> bool: @@ -71,6 +74,7 @@ def get_connection_params(self) -> pika.ConnectionParameters: return pika.ConnectionParameters( host=self.get_container_host_ip(), port=self.get_exposed_port(self.port), + virtual_host=self.vhost, credentials=credentials, ) diff --git a/modules/rabbitmq/tests/test_rabbitmq.py b/modules/rabbitmq/tests/test_rabbitmq.py index 98fb7e6d3..c0c1894d8 100644 --- a/modules/rabbitmq/tests/test_rabbitmq.py +++ b/modules/rabbitmq/tests/test_rabbitmq.py @@ -13,14 +13,17 @@ @pytest.mark.parametrize( - argnames=["port", "username", "password"], + argnames=["port", "username", "password", "vhost"], argvalues=[ - [None, None, None], # use the defaults - [5673, None, None], # test with custom port - [None, "my_test_user", "my_secret_password"], # test with custom credentials + [None, None, None, None], # use the defaults + [5673, None, None, None], # test with custom port + [None, "my_test_user", "my_secret_password", None], # test with custom credentials + [None, None, None, "vhost"], # test with custom vhost ], ) -def test_docker_run_rabbitmq(port: Optional[int], username: Optional[str], password: Optional[str]): +def test_docker_run_rabbitmq( + port: Optional[int], username: Optional[str], password: Optional[str], vhost: Optional[str] +): """Run rabbitmq test container and use it to deliver a simple message.""" kwargs = {} if port is not None: @@ -29,6 +32,8 @@ def test_docker_run_rabbitmq(port: Optional[int], username: Optional[str], passw kwargs["username"] = username if password is not None: kwargs["password"] = password + if vhost is not None: + kwargs["vhost"] = vhost rabbitmq_container = RabbitMqContainer("rabbitmq:latest", **kwargs) with rabbitmq_container as rabbitmq: From b13b43da502b54af7a5b09fa70ee8f5e301d1fb7 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 31 Jul 2024 09:14:26 -0400 Subject: [PATCH 412/425] chore(reaper): upgrade from 0.7.0 -> 0.8.1 (#650) --- README.md | 2 +- core/testcontainers/core/config.py | 2 +- index.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cec096a47..43d5d2aa6 100644 --- a/README.md +++ b/README.md @@ -46,5 +46,5 @@ See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more details. | `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk | | `TESTCONTAINERS_RYUK_PRIVILEGED` | `false` | Run ryuk as a privileged container | | `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk | -| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.7.0` | Custom image for ryuk | +| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.8.1` | Custom image for ryuk | | `RYUK_RECONNECTION_TIMEOUT` | `10s` | Reconnection timeout for Ryuk TCP socket before Ryuk reaps all dangling containers | diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py index 3522b91f0..7b7279511 100644 --- a/core/testcontainers/core/config.py +++ b/core/testcontainers/core/config.py @@ -9,7 +9,7 @@ SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) TIMEOUT = MAX_TRIES * SLEEP_TIME -RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.7.0") +RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.8.1") RYUK_PRIVILEGED: bool = environ.get("TESTCONTAINERS_RYUK_PRIVILEGED", "false") == "true" RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true" RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock") diff --git a/index.rst b/index.rst index 70708a247..307f934c0 100644 --- a/index.rst +++ b/index.rst @@ -131,7 +131,7 @@ Configuration +-------------------------------------------+---------------------------------------------------+------------------------------------------+ | ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk | +-------------------------------------------+---------------------------------------------------+------------------------------------------+ -| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk | +| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.8.1`` | Custom image for ryuk | +-------------------------------------------+---------------------------------------------------+------------------------------------------+ | ``DOCKER_AUTH_CONFIG`` | ``{"auths": {"": {"auth": ""}}}`` | Custom registry auth config | +-------------------------------------------+---------------------------------------------------+------------------------------------------+ From e02c1b37a651374f47abe72bc17941849c1fd12e Mon Sep 17 00:00:00 2001 From: David Ankin Date: Thu, 1 Aug 2024 11:06:32 -0400 Subject: [PATCH 413/425] fix(selenium): add Arg/Options to api of selenium container (#654) fix #652 --- conf.py | 1 + .../testcontainers/selenium/__init__.py | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/conf.py b/conf.py index e95d3d135..b310e939b 100644 --- a/conf.py +++ b/conf.py @@ -160,4 +160,5 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3", None), + "selenium": ("https://seleniumhq.github.io/selenium/docs/api/py/", None), } diff --git a/modules/selenium/testcontainers/selenium/__init__.py b/modules/selenium/testcontainers/selenium/__init__.py index 50cc566bd..53305ef93 100644 --- a/modules/selenium/testcontainers/selenium/__init__.py +++ b/modules/selenium/testcontainers/selenium/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. from pathlib import Path -from typing import Optional +from typing import Any, Optional import urllib3 from typing_extensions import Self @@ -26,7 +26,7 @@ IMAGES = {"firefox": "selenium/standalone-firefox:latest", "chrome": "selenium/standalone-chrome:latest"} -def get_image_name(capabilities: str) -> str: +def get_image_name(capabilities: dict[str, Any]) -> str: return IMAGES[capabilities["browserName"]] @@ -48,9 +48,16 @@ class BrowserWebDriverContainer(DockerContainer): """ def __init__( - self, capabilities: str, image: Optional[str] = None, port: int = 4444, vnc_port: int = 5900, **kwargs + self, + capabilities: dict[str, Any], + options: Optional[ArgOptions] = None, + image: Optional[str] = None, + port: int = 4444, + vnc_port: int = 5900, + **kwargs, ) -> None: self.capabilities = capabilities + self.options = options self.image = image or get_image_name(capabilities) self.port = port self.vnc_port = vnc_port @@ -65,7 +72,7 @@ def _configure(self) -> None: @wait_container_is_ready(urllib3.exceptions.HTTPError) def _connect(self) -> webdriver.Remote: - options = ArgOptions() + options = ArgOptions() if self.options is None else self.options for key, value in self.capabilities.items(): options.set_capability(key, value) return webdriver.Remote(command_executor=(self.get_connection_url()), options=options) @@ -78,6 +85,10 @@ def get_connection_url(self) -> str: port = self.get_exposed_port(self.port) return f"http://{ip}:{port}/wd/hub" + def with_options(self, options: Optional[ArgOptions]): + self.options = options + return self + def with_video(self, image: Optional[str] = None, video_path: Optional[Path] = None) -> Self: video_path = video_path or Path.cwd() From b1453e87e1f5443f0f8d04c9b30a278aa835ca9b Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sat, 3 Aug 2024 00:19:00 -0400 Subject: [PATCH 414/425] feat(core): add ability to do OR & AND for waitforlogs (#661) --- core/testcontainers/core/waiting_utils.py | 16 ++++++++-- .../testcontainers/postgres/__init__.py | 32 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py index cc3351d11..82ea1f15f 100644 --- a/core/testcontainers/core/waiting_utils.py +++ b/core/testcontainers/core/waiting_utils.py @@ -78,7 +78,12 @@ def wait_for(condition: Callable[..., bool]) -> bool: def wait_for_logs( - container: "DockerContainer", predicate: Union[Callable, str], timeout: float = config.timeout, interval: float = 1 + container: "DockerContainer", + predicate: Union[Callable, str], + timeout: float = config.timeout, + interval: float = 1, + predicate_streams_and: bool = False, + # ) -> float: """ Wait for the container to emit logs satisfying the predicate. @@ -90,6 +95,7 @@ def wait_for_logs( timeout: Number of seconds to wait for the predicate to be satisfied. Defaults to wait indefinitely. interval: Interval at which to poll the logs. + predicate_streams_and: should the predicate be applied to both Returns: duration: Number of seconds until the predicate was satisfied. @@ -101,7 +107,13 @@ def wait_for_logs( duration = time.time() - start stdout = container.get_logs()[0].decode() stderr = container.get_logs()[1].decode() - if predicate(stdout) or predicate(stderr): + predicate_result = ( + predicate(stdout) or predicate(stderr) + if predicate_streams_and is False + else predicate(stdout) and predicate(stderr) + # + ) + if predicate_result: return duration if duration > timeout: raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " "seconds") diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/modules/postgres/testcontainers/postgres/__init__.py index 80baef752..c9ba2a22a 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/modules/postgres/testcontainers/postgres/__init__.py @@ -91,7 +91,37 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] = @wait_container_is_ready() def _connect(self) -> None: - wait_for_logs(self, ".*database system is ready to accept connections.*", c.max_tries, c.sleep_time) + # postgres itself logs these messages to the standard error stream: + # + # $ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14 \ + # > | grep -o -a -m 1 -h 'database system is ready to accept connections' + # 2024-08-03 00:13:02.799 EDT [70226] LOG: starting PostgreSQL 14.11 (Homebrew) .... + # 2024-08-03 00:13:02.804 EDT [70226] LOG: listening on IPv4 address "127.0.0.1", port 5432 + # ... + # ^C2024-08-03 00:13:04.226 EDT [70226] LOG: received fast shutdown request + # ... + # + # $ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14 2>&1 \ + # > | grep -o -a -m 1 -h 'database system is ready to accept connections' + # database system is ready to accept connections + # + # and the setup script inside docker library postgres + # uses pg_ctl: + # https://github.com/docker-library/postgres/blob/66da3846b40396249936938ee17e9684e6968a57/16/alpine3.20/docker-entrypoint.sh#L261-L282 + # which prints logs to stdout: + # https://www.postgresql.org/docs/current/app-pg-ctl.html#:~:text=the%20server%27s%20standard%20output%20and%20standard%20error%20are%20sent%20to%20pg_ctl%27s%20standard%20output + # + # so we must wait for both the setup and real startup: + predicate_streams_and = True + + wait_for_logs( + self, + ".*database system is ready to accept connections.*", + c.max_tries, + c.sleep_time, + predicate_streams_and=predicate_streams_and, + # + ) count = 0 while count < c.max_tries: From e1e3d13b47923dd7124196e6b743799bd87b6885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Bartha?= <39852431+totallyzen@users.noreply.github.com> Date: Sat, 3 Aug 2024 06:20:58 +0200 Subject: [PATCH 415/425] feat(compose): ability to retain volumes when using context manager (#659) # changes On fiddling with a local project of mine, I realised we default to removing volumes when using compose. This is neat, but the context manager should also allow control over the volumes kept. This change adds the `keep_volumes` flag and hooks into `self.stop()` that already had the option. I added a test to cover the new functionality : --- core/testcontainers/compose/compose.py | 3 ++- .../basic_volume/docker-compose.yaml | 17 ++++++++++++++ core/tests/test_compose.py | 22 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 core/tests/compose_fixtures/basic_volume/docker-compose.yaml diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index 08dd313a4..c5349b52e 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -165,6 +165,7 @@ class DockerCompose: pull: bool = False build: bool = False wait: bool = True + keep_volumes: bool = False env_file: Optional[str] = None services: Optional[list[str]] = None docker_command_path: Optional[str] = None @@ -178,7 +179,7 @@ def __enter__(self) -> "DockerCompose": return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: - self.stop() + self.stop(not self.keep_volumes) def docker_compose_command(self) -> list[str]: """ diff --git a/core/tests/compose_fixtures/basic_volume/docker-compose.yaml b/core/tests/compose_fixtures/basic_volume/docker-compose.yaml new file mode 100644 index 000000000..371ab9d38 --- /dev/null +++ b/core/tests/compose_fixtures/basic_volume/docker-compose.yaml @@ -0,0 +1,17 @@ +services: + alpine: + image: alpine:latest + init: true + command: + - sh + - -c + - 'while true; do sleep 0.1 ; date -Ins; done' + read_only: true + volumes: + - type: volume + source: my-data + target: /var/lib/example/data + read_only: false + +volumes: + my-data: {} diff --git a/core/tests/test_compose.py b/core/tests/test_compose.py index e1a42655e..72bfaefad 100644 --- a/core/tests/test_compose.py +++ b/core/tests/test_compose.py @@ -1,3 +1,4 @@ +import subprocess from pathlib import Path from re import split from time import sleep @@ -147,6 +148,27 @@ def test_compose_logs(): assert not line or container.Service in next(iter(line.split("|")), None) +def test_compose_volumes(): + _file_in_volume = "/var/lib/example/data/hello" + volumes = DockerCompose(context=FIXTURES / "basic_volume", keep_volumes=True) + with volumes: + stdout, stderr, exitcode = volumes.exec_in_container( + ["/bin/sh", "-c", f"echo hello > {_file_in_volume}"], "alpine" + ) + assert exitcode == 0 + + # execute another time to confirm the file is still there, but we're not keeping the volumes this time + volumes.keep_volumes = False + with volumes: + stdout, stderr, exitcode = volumes.exec_in_container(["cat", _file_in_volume], "alpine") + assert exitcode == 0 + assert "hello" in stdout + + # third time we expect the file to be missing + with volumes, pytest.raises(subprocess.CalledProcessError): + volumes.exec_in_container(["cat", _file_in_volume], "alpine") + + # noinspection HttpUrlsUsage def test_compose_ports(): # fairly straight forward - can we get the right port to request it From 8c28a861ce4ade9e8204783e2ef2fd99013c90ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Godard?= Date: Fri, 9 Aug 2024 13:40:12 +0200 Subject: [PATCH 416/425] feat(compose): add ability to get docker compose config (#669) This PR add a new function to the `testcontainers.compose.DockerComposer` class, `get_config` which use `docker compose config` command for resolving and returning the actual docker compose configuration. This can be useful for example if you want to retrieve a connection string you pass to your app in your docker compose in order to connect to your database service instead of copy pasting it from your compose file into your tests. Also note thats its way easier to rely on docker compose config to get you the config than trying to manually find, read and merge compose files in specified context (I tried it first ...). About the tests I mostly ensured the docker compose command was as expected. This is because the config produced by the docker compose can not always reflect exactly what is in the file. There is some normalization/resolving which is done (even when you pass all flags to disable them). But anyway, I'm not sure its a good idea to actually test the behavior of the docker config command itself. Let me know what you think of it! --------- Co-authored-by: David Ankin --- core/testcontainers/compose/compose.py | 34 +++++++++++++++++++++- core/tests/test_compose.py | 40 ++++++++++++++++++++++++++ poetry.lock | 20 ++++++++++++- pyproject.toml | 1 + 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index c5349b52e..564eda8fb 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -1,12 +1,13 @@ from dataclasses import asdict, dataclass, field, fields from functools import cached_property from json import loads +from logging import warning from os import PathLike from platform import system from re import split from subprocess import CompletedProcess from subprocess import run as subprocess_run -from typing import Callable, Literal, Optional, TypeVar, Union +from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast from urllib.error import HTTPError, URLError from urllib.request import urlopen @@ -14,6 +15,7 @@ from testcontainers.core.waiting_utils import wait_container_is_ready _IPT = TypeVar("_IPT") +_WARNINGS = {"DOCKER_COMPOSE_GET_CONFIG": "get_config is experimental, see testcontainers/testcontainers-python#669"} def _ignore_properties(cls: type[_IPT], dict_: any) -> _IPT: @@ -258,6 +260,36 @@ def get_logs(self, *services: str) -> tuple[str, str]: result = self._run_command(cmd=logs_cmd) return result.stdout.decode("utf-8"), result.stderr.decode("utf-8") + def get_config( + self, *, path_resolution: bool = True, normalize: bool = True, interpolate: bool = True + ) -> dict[str, Any]: + """ + Parse, resolve and returns compose file via `docker config --format json`. + In case of multiple compose files, the returned value will be a merge of all files. + + See: https://docs.docker.com/reference/cli/docker/compose/config/ for more details + + :param path_resolution: whether to resolve file paths + :param normalize: whether to normalize compose model + :param interpolate: whether to interpolate environment variables + + Returns: + Compose file + + """ + if "DOCKER_COMPOSE_GET_CONFIG" in _WARNINGS: + warning(_WARNINGS.pop("DOCKER_COMPOSE_GET_CONFIG")) + config_cmd = [*self.compose_command_property, "config", "--format", "json"] + if not path_resolution: + config_cmd.append("--no-path-resolution") + if not normalize: + config_cmd.append("--no-normalize") + if not interpolate: + config_cmd.append("--no-interpolate") + + cmd_output = self._run_command(cmd=config_cmd).stdout + return cast(dict[str, Any], loads(cmd_output)) + def get_containers(self, include_all=False) -> list[ComposeContainer]: """ Fetch information about running containers via `docker compose ps --format json`. diff --git a/core/tests/test_compose.py b/core/tests/test_compose.py index 72bfaefad..b43da28c5 100644 --- a/core/tests/test_compose.py +++ b/core/tests/test_compose.py @@ -6,6 +6,7 @@ from urllib.request import urlopen, Request import pytest +from pytest_mock import MockerFixture from testcontainers.compose import DockerCompose, ContainerIsNotRunning, NoSuchPortExposed @@ -304,6 +305,45 @@ def test_exec_in_container_multiple(): assert "test_exec_in_container" in body +CONTEXT_FIXTURES = [pytest.param(ctx, id=ctx.name) for ctx in FIXTURES.iterdir()] + + +@pytest.mark.parametrize("context", CONTEXT_FIXTURES) +def test_compose_config(context: Path, mocker: MockerFixture) -> None: + compose = DockerCompose(context) + run_command = mocker.spy(compose, "_run_command") + expected_cmd = [*compose.compose_command_property, "config", "--format", "json"] + + received_config = compose.get_config() + + assert received_config + assert isinstance(received_config, dict) + assert "services" in received_config + assert run_command.call_args.kwargs["cmd"] == expected_cmd + + +@pytest.mark.parametrize("context", CONTEXT_FIXTURES) +def test_compose_config_raw(context: Path, mocker: MockerFixture) -> None: + compose = DockerCompose(context) + run_command = mocker.spy(compose, "_run_command") + expected_cmd = [ + *compose.compose_command_property, + "config", + "--format", + "json", + "--no-path-resolution", + "--no-normalize", + "--no-interpolate", + ] + + received_config = compose.get_config(path_resolution=False, normalize=False, interpolate=False) + + assert received_config + assert isinstance(received_config, dict) + assert "services" in received_config + assert run_command.call_args.kwargs["cmd"] == expected_cmd + + def fetch(req: Union[Request, str]): if isinstance(req, str): req = Request(method="GET", url=req) diff --git a/poetry.lock b/poetry.lock index a5d956318..2c49ffcdc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1913,6 +1913,7 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, + {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -3376,6 +3377,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-arango" version = "7.9.1" @@ -4603,4 +4621,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "00155615fffa7f316221c1fafb895105911a3cce003b57713d9b76b7fd3e3214" +content-hash = "88b63308cfdc3de3002a4cb4f60aeff9d049bb057fd78f5a2711aff5aba59b03" diff --git a/pyproject.toml b/pyproject.toml index 35edc0d51..41c041bf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,6 +182,7 @@ paho-mqtt = "2.1.0" sqlalchemy-cockroachdb = "2.0.2" paramiko = "^3.4.0" types-paramiko = "^3.4.0.20240423" +pytest-mock = "^3.14.0" [[tool.poetry.source]] name = "PyPI" From 1e439232e35ce0091f20993273e1f01d8c0119c4 Mon Sep 17 00:00:00 2001 From: Xin Date: Sun, 11 Aug 2024 20:40:27 +0800 Subject: [PATCH 417/425] fix: Add Db2 support (#673) fix #672 Add Db2 support https://github.com/testcontainers/testcontainers-python/issues/672. --- modules/db2/README.rst | 2 + modules/db2/testcontainers/db2/__init__.py | 61 ++++++++++++++++++++++ modules/db2/tests/test_db2.py | 43 +++++++++++++++ modules/mongodb/tests/test_mongodb.py | 1 + modules/mysql/tests/test_mysql.py | 1 + modules/postgres/tests/test_postgres.py | 1 + poetry.lock | 58 +++++++++++++++++++- pyproject.toml | 3 ++ 8 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 modules/db2/README.rst create mode 100644 modules/db2/testcontainers/db2/__init__.py create mode 100644 modules/db2/tests/test_db2.py diff --git a/modules/db2/README.rst b/modules/db2/README.rst new file mode 100644 index 000000000..1afd1f6d7 --- /dev/null +++ b/modules/db2/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.db2.Db2Container +.. title:: testcontainers.db2.Db2Container diff --git a/modules/db2/testcontainers/db2/__init__.py b/modules/db2/testcontainers/db2/__init__.py new file mode 100644 index 000000000..b17c0efef --- /dev/null +++ b/modules/db2/testcontainers/db2/__init__.py @@ -0,0 +1,61 @@ +from os import environ +from typing import Optional + +from testcontainers.core.generic import DbContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class Db2Container(DbContainer): + """ + IBM Db2 database container. + + Example: + + .. doctest:: + + >>> import sqlalchemy + >>> from testcontainers.db2 import Db2Container + + >>> with Db2Container("icr.io/db2_community/db2:latest") as db2: + ... engine = sqlalchemy.create_engine(db2.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute(sqlalchemy.text("select service_level from sysibmadm.env_inst_info")) + """ + + def __init__( + self, + image: str = "icr.io/db2_community/db2:latest", + username: str = "db2inst1", + password: Optional[str] = None, + port: int = 50000, + dbname: str = "testdb", + dialect: str = "db2+ibm_db", + **kwargs, + ) -> None: + super().__init__(image, **kwargs) + + self.port = port + self.with_exposed_ports(self.port) + + self.password = password or environ.get("DB2_PASSWORD", "password") + self.username = username + self.dbname = dbname + self.dialect = dialect + + def _configure(self) -> None: + self.with_env("LICENSE", "accept") + self.with_env("DB2INSTANCE", self.username) + self.with_env("DB2INST1_PASSWORD", self.password) + self.with_env("DBNAME", self.dbname) + self.with_env("ARCHIVE_LOGS", "false") + self.with_env("AUTOCONFIG", "false") + self.with_kwargs(privileged=True) + + @wait_container_is_ready() + def _connect(self) -> None: + wait_for_logs(self, predicate="Setup has completed") + + def get_connection_url(self) -> str: + return super()._create_connection_url( + dialect=self.dialect, username=self.username, password=self.password, dbname=self.dbname, port=self.port + ) diff --git a/modules/db2/tests/test_db2.py b/modules/db2/tests/test_db2.py new file mode 100644 index 000000000..7b6ea844a --- /dev/null +++ b/modules/db2/tests/test_db2.py @@ -0,0 +1,43 @@ +from unittest import mock + +import pytest +import sqlalchemy + +from testcontainers.core.utils import is_arm +from testcontainers.db2 import Db2Container + + +@pytest.mark.skipif(is_arm(), reason="db2 container not available for ARM") +@pytest.mark.parametrize("version", ["11.5.9.0", "11.5.8.0"]) +def test_docker_run_db2(version: str): + with Db2Container(f"icr.io/db2_community/db2:{version}", password="password") as db2: + engine = sqlalchemy.create_engine(db2.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("select service_level from sysibmadm.env_inst_info")) + for row in result: + assert row[0] == f"DB2 v{version}" + + +# This is a feature in the generic DbContainer class +# but it can't be tested on its own +# so is tested in various database modules: +# - mysql / mariadb +# - postgresql +# - sqlserver +# - mongodb +# - db2 +def test_quoted_password(): + user = "db2inst1" + dbname = "testdb" + password = "p@$%25+0&%rd :/!=?" + quoted_password = "p%40%24%2525+0%26%25rd %3A%2F%21%3D%3F" + kwargs = { + "username": user, + "password": password, + "dbname": dbname, + } + with Db2Container("icr.io/db2_community/db2:11.5.9.0", **kwargs) as container: + port = container.get_exposed_port(50000) + host = container.get_container_host_ip() + expected_url = f"db2+ibm_db://{user}:{quoted_password}@{host}:{port}/{dbname}" + assert expected_url == container.get_connection_url() diff --git a/modules/mongodb/tests/test_mongodb.py b/modules/mongodb/tests/test_mongodb.py index da3465dbb..9bf3600f2 100644 --- a/modules/mongodb/tests/test_mongodb.py +++ b/modules/mongodb/tests/test_mongodb.py @@ -35,6 +35,7 @@ def test_docker_run_mongodb(version: str): # - postgresql # - sqlserver # - mongodb +# - db2 def test_quoted_password(): user = "root" password = "p@$%25+0&%rd :/!=?" diff --git a/modules/mysql/tests/test_mysql.py b/modules/mysql/tests/test_mysql.py index 847f99df4..323c35328 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/modules/mysql/tests/test_mysql.py @@ -69,6 +69,7 @@ def test_docker_env_variables(): # - postgresql # - sqlserver # - mongodb +# - db2 def test_quoted_password(): user = "root" password = "p@$%25+0&%rd :/!=?" diff --git a/modules/postgres/tests/test_postgres.py b/modules/postgres/tests/test_postgres.py index 38c856bf9..42bcfe858 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/modules/postgres/tests/test_postgres.py @@ -53,6 +53,7 @@ def test_docker_run_postgres_with_driver_pg8000(): # - postgresql # - sqlserver # - mongodb +# - db2 def test_quoted_password(): user = "root" password = "p@$%25+0&%rd :/!=?" diff --git a/poetry.lock b/poetry.lock index 2c49ffcdc..228c9c483 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1513,6 +1513,61 @@ files = [ {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, ] +[[package]] +name = "ibm-db" +version = "3.2.3" +description = "Python DBI driver for DB2 (LUW, zOS, i5) and IDS" +optional = true +python-versions = "*" +files = [ + {file = "ibm_db-3.2.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:3399466141c29704f4e8ba709a67ba27ab413239c0244c3c4510126e946ff603"}, + {file = "ibm_db-3.2.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e12ff6426d4f718e1ff6615e64a2880bd570826f19a031c82dbf296714cafd7d"}, + {file = "ibm_db-3.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:442a416a47e0d6ae3de671d227906487a1d731f36da8dc9ba341bd384b97f973"}, + {file = "ibm_db-3.2.3-cp310-cp310-win32.whl", hash = "sha256:8f508caca6407947f4156cae853942d1079736505231246ee51475d7f5af1792"}, + {file = "ibm_db-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:91154c151784be5234c9f327239f1a98fc4e4a5f112c3c94189e04cfab3d5cb0"}, + {file = "ibm_db-3.2.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:4fbf78b69d61997dad8ee1fdc273d0b287b43f25fe2ee8c945c034bddb527f1d"}, + {file = "ibm_db-3.2.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a13f20b40ca856ec2a5638f8f4e65287c23ff5e1d808fa58fd8d208678a00323"}, + {file = "ibm_db-3.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5a8ab31130beea18dcd3dd447d6e35ec840ccaed1d3add8ed04ac5c4f44f94c"}, + {file = "ibm_db-3.2.3-cp311-cp311-win32.whl", hash = "sha256:afa8c0a55be2b27ff7f3d50ae0b332562d3048af17557b86854e8e67429fdf0a"}, + {file = "ibm_db-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:d46fb0554631c18fc1f5b615112c68c1b250b7f977dc10cdb53db9258ca69f20"}, + {file = "ibm_db-3.2.3-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7c5011f47edf179c04b67e3472c25f40103679936b17a04dc00a9a7282aeb2b6"}, + {file = "ibm_db-3.2.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b10593eda579395d84254165dc5f5e5eff97d87b9a491181b8632f3db7aa17de"}, + {file = "ibm_db-3.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0170e4c09fb5cf146d26cce0700a40e6a3ab767996f8b4c668847ad1ddc3a06"}, + {file = "ibm_db-3.2.3-cp312-cp312-win32.whl", hash = "sha256:69b2ebf47122eff50497ba48dee7a32087e2698a771c5d86fc683e7baecd5e96"}, + {file = "ibm_db-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7c18f230f8202a386873c73bfe9b00d1c052c35e9a501e07700cb83e7a59c48f"}, + {file = "ibm_db-3.2.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:9fdb360b9de86422f8827774680f28ffeba98b702e86f689acaa0f97b62e1693"}, + {file = "ibm_db-3.2.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34bc14c9f4d7f56ab8c7650e06ff3982695dafea2aa90a3a3533e3bcd5ea7be8"}, + {file = "ibm_db-3.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067c2a76e230ffe703a89829c4063b9fa951230c91001846c5221bb6ef4a1ef"}, + {file = "ibm_db-3.2.3-cp37-cp37m-win32.whl", hash = "sha256:4141333b42e10eaf97c5712205e873fc4977bd77c2aef416385620f2a01cc32f"}, + {file = "ibm_db-3.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:a1b981485d82d9d23d2c19de2fa1087a6ed5ea134944b1ab10eb0b7758cec512"}, + {file = "ibm_db-3.2.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:60d7db80d645eb41c1ddfa9279550784566bdd701c4c52da206e28f9f91e8030"}, + {file = "ibm_db-3.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def6c72fcd31fc3e4fe91ffac94db2f6c3365ae4fe7bc1c284fca741f7a0861e"}, + {file = "ibm_db-3.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:611c1ea3c32067083072365eae86b939edb4bc730f6016b670f2264220ac2d63"}, + {file = "ibm_db-3.2.3-cp38-cp38-win32.whl", hash = "sha256:238460936016ec6bbe43dd5612829a6ad19a2f483dde57294869c48809e4c902"}, + {file = "ibm_db-3.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:6217177a6246ddf86463e090200e7c60459a62af5513b78793ac9f196ef34571"}, + {file = "ibm_db-3.2.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4611a10dc4b9eca06aadca5ea697c9af71b16ba0f1076fa7dd66d1698a23d2a6"}, + {file = "ibm_db-3.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f736bbd6fc2bec483f82b8e3243a12737fb46bbd0f50b1378c67a28cf2f9649"}, + {file = "ibm_db-3.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b90e5fc0ae75567539cd95d5ded86f7c5507084f6aa52eb16ea0dcc88b25382"}, + {file = "ibm_db-3.2.3-cp39-cp39-win32.whl", hash = "sha256:60db181462194cc1d5fa22514cb73d84c4edf79c12d98c8d16796e72ad179c8b"}, + {file = "ibm_db-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:48008a611a6ca724261866c81680f638e1a4116efb21da4fbc26188679a124ca"}, + {file = "ibm_db-3.2.3.tar.gz", hash = "sha256:ec7075246849437ed79c60447b05a4bee78a3f6ca2646f4e60a028333c72957a"}, +] + +[[package]] +name = "ibm-db-sa" +version = "0.4.1" +description = "SQLAlchemy support for IBM Data Servers" +optional = true +python-versions = "*" +files = [ + {file = "ibm_db_sa-0.4.1-py3-none-any.whl", hash = "sha256:49926ba9799e6ebd9ddd847141537c83d179ecf32fe24b7e997ac4614d3f616a"}, + {file = "ibm_db_sa-0.4.1.tar.gz", hash = "sha256:a46df130a3681646490925cf4e1bca12b46283f71eea39b70b4f9a56e95341ac"}, +] + +[package.dependencies] +ibm-db = ">=2.0.0" +sqlalchemy = ">=0.7.3" + [[package]] name = "identify" version = "2.5.35" @@ -4583,6 +4638,7 @@ chroma = ["chromadb-client"] clickhouse = ["clickhouse-driver"] cockroachdb = [] cosmosdb = ["azure-cosmos"] +db2 = ["ibm_db_sa", "sqlalchemy"] elasticsearch = [] generic = ["httpx"] google = ["google-cloud-datastore", "google-cloud-pubsub"] @@ -4621,4 +4677,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "88b63308cfdc3de3002a4cb4f60aeff9d049bb057fd78f5a2711aff5aba59b03" +content-hash = "18a5763385d12114513ef5d65268de3ea6567e79b21049b6d58d1803f4257306" diff --git a/pyproject.toml b/pyproject.toml index 41c041bf2..3bccf8800 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ packages = [ { include = "testcontainers", from = "modules/clickhouse" }, { include = "testcontainers", from = "modules/cockroachdb" }, { include = "testcontainers", from = "modules/cosmosdb" }, + { include = "testcontainers", from = "modules/db2" }, { include = "testcontainers", from = "modules/elasticsearch" }, { include = "testcontainers", from = "modules/generic" }, { include = "testcontainers", from = "modules/test_module_import"}, @@ -114,6 +115,7 @@ httpx = { version = "*", optional = true } azure-cosmos = { version = "*", optional = true } cryptography = { version = "*", optional = true } trino = { version = "*", optional = true } +ibm_db_sa = { version = "*", optional = true } [tool.poetry.extras] arangodb = ["python-arango"] @@ -123,6 +125,7 @@ cassandra = [] clickhouse = ["clickhouse-driver"] cosmosdb = ["azure-cosmos"] cockroachdb = [] +db2 = ["sqlalchemy", "ibm_db_sa"] elasticsearch = [] generic = ["httpx"] test_module_import = ["httpx"] From d5de0aa01c7d3ba304446dd73347a1a7ec1facc7 Mon Sep 17 00:00:00 2001 From: mgorsk1 Date: Tue, 13 Aug 2024 12:58:54 +0200 Subject: [PATCH 418/425] feat: refactor network setup (#678) fixes https://github.com/testcontainers/testcontainers-python/issues/645 - network should be attached as the container is started, not as a post-start action. This will make sure port binding and exposing works correctly. --------- Signed-off-by: mgorsk1 --- core/testcontainers/core/container.py | 18 ++++++++-- core/tests/test_network.py | 52 ++++++++++++++++----------- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 085fc58e1..e9415441a 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Optional import docker.errors +from docker import version +from docker.types import EndpointConfig from typing_extensions import Self from testcontainers.core.config import testcontainers_config as c @@ -88,6 +90,18 @@ def start(self) -> Self: logger.info("Pulling image %s", self.image) docker_client = self.get_docker_client() self._configure() + + network_kwargs = ( + { + "network": self._network.name, + "networking_config": { + self._network.name: EndpointConfig(version.__version__, aliases=self._network_aliases) + }, + } + if self._network + else {} + ) + self._container = docker_client.run( self.image, command=self._command, @@ -96,11 +110,11 @@ def start(self) -> Self: ports=self.ports, name=self._name, volumes=self.volumes, + **network_kwargs, **self._kwargs, ) + logger.info("Container started: %s", self._container.short_id) - if self._network: - self._network.connect(self._container.id, self._network_aliases) return self def stop(self, force=True, delete_volume=True) -> None: diff --git a/core/tests/test_network.py b/core/tests/test_network.py index 7191153bb..868032e26 100644 --- a/core/tests/test_network.py +++ b/core/tests/test_network.py @@ -45,26 +45,32 @@ def test_network_create_errors(): def test_containers_can_communicate_over_network(): - with Network() as network: - with ( - DockerContainer(NGINX_ALPINE_SLIM_IMAGE) - .with_name("alpine1") - .with_network_aliases("alpine1-alias-1", "alpine1-alias-2") - .with_network(network) as alpine1 - ): - with ( - DockerContainer(NGINX_ALPINE_SLIM_IMAGE) - .with_name("alpine2") - .with_network_aliases("alpine2-alias-1", "alpine2-alias-2") - .with_network(network) as alpine2 - ): - assert_can_ping(alpine1, "alpine2") - assert_can_ping(alpine1, "alpine2-alias-1") - assert_can_ping(alpine1, "alpine2-alias-2") - - assert_can_ping(alpine2, "alpine1") - assert_can_ping(alpine2, "alpine1-alias-1") - assert_can_ping(alpine2, "alpine1-alias-2") + with ( + Network() as network, + DockerContainer(NGINX_ALPINE_SLIM_IMAGE) + .with_name("alpine1") + .with_network_aliases("alpine1-alias-1", "alpine1-alias-2") + .with_network(network) as alpine1, + DockerContainer(NGINX_ALPINE_SLIM_IMAGE) + .with_name("alpine2") + .with_network_aliases("alpine2-alias-1", "alpine2-alias-2") + .with_network(network) as alpine2, + ): + assert_can_ping(alpine1, "alpine2") + assert_can_ping(alpine1, "alpine2-alias-1") + assert_can_ping(alpine1, "alpine2-alias-2") + + assert_can_ping(alpine2, "alpine1") + assert_can_ping(alpine2, "alpine1-alias-1") + assert_can_ping(alpine2, "alpine1-alias-2") + + assert_can_request(alpine1, "alpine2") + assert_can_request(alpine1, "alpine2-alias-1") + assert_can_request(alpine1, "alpine2-alias-2") + + assert_can_request(alpine2, "alpine1") + assert_can_request(alpine2, "alpine1-alias-1") + assert_can_request(alpine2, "alpine1-alias-2") def assert_can_ping(container: DockerContainer, remote_name: str): @@ -73,6 +79,12 @@ def assert_can_ping(container: DockerContainer, remote_name: str): assert "64 bytes" in str(output) +def assert_can_request(container: DockerContainer, remote_name: str): + status, output = container.exec(f"wget -qO- http://{remote_name}") + assert status == 0 + assert "Welcome to nginx!" in output.decode() + + def test_network_has_labels(): network = Network() try: From 2d8bc11c8e151af66456ebad156afc4a87822676 Mon Sep 17 00:00:00 2001 From: Israel Fruchter Date: Wed, 14 Aug 2024 15:34:53 +0300 Subject: [PATCH 419/425] feat: Adding support for Cassandra and Scylla (#167) This add the support for those cassandra based dbs and their drivers, cassandra-driver, scylla-driver Ref: https://cassandra.apache.org/ Ref: https://www.scylladb.com/ Ref: https://pypi.org/project/cassandra-driver/ Ref: https://pypi.org/project/scylla-driver/ --------- Co-authored-by: David Ankin --- modules/scylla/README.rst | 2 + .../scylla/testcontainers/scylla/__init__.py | 47 +++++++++++++++++++ modules/scylla/tests/test_scylla.py | 18 +++++++ poetry.lock | 10 ++-- pyproject.toml | 4 +- 5 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 modules/scylla/README.rst create mode 100644 modules/scylla/testcontainers/scylla/__init__.py create mode 100644 modules/scylla/tests/test_scylla.py diff --git a/modules/scylla/README.rst b/modules/scylla/README.rst new file mode 100644 index 000000000..fd1ea03fb --- /dev/null +++ b/modules/scylla/README.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.scylla.ScyllaContainer +.. title:: testcontainers.scylla.ScyllaContainer diff --git a/modules/scylla/testcontainers/scylla/__init__.py b/modules/scylla/testcontainers/scylla/__init__.py new file mode 100644 index 000000000..ca0f44afb --- /dev/null +++ b/modules/scylla/testcontainers/scylla/__init__.py @@ -0,0 +1,47 @@ +from testcontainers.core.config import MAX_TRIES +from testcontainers.core.generic import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +class ScyllaContainer(DockerContainer): + """ + Scylla database container. + + Example + ------- + .. doctest:: + + >>> from testcontainers.scylla import ScyllaContainer + + >>> with ScyllaContainer() as scylla: + ... cluster = scylla.get_cluster() + ... with cluster.connect() as session: + ... result = session.execute( + ... "CREATE KEYSPACE keyspace1 WITH replication " + ... "= {'class': 'SimpleStrategy', 'replication_factor': '1'};") + """ + + def __init__(self, image="scylladb/scylla:latest", ports_to_expose=(9042,)): + super().__init__(image) + self.ports_to_expose = ports_to_expose + self.with_exposed_ports(*self.ports_to_expose) + self.with_command("--skip-wait-for-gossip-to-settle=0") + + @wait_container_is_ready() + def _connect(self): + wait_for_logs(self, predicate="Starting listening for CQL clients", timeout=MAX_TRIES) + cluster = self.get_cluster() + cluster.connect() + + def start(self): + super().start() + self._connect() + return self + + def get_cluster(self, **kwargs): + from cassandra.cluster import Cluster + + container = self.get_wrapped_container() + container.reload() + hostname = container.attrs["NetworkSettings"]["IPAddress"] + return Cluster(contact_points=[hostname], **kwargs) diff --git a/modules/scylla/tests/test_scylla.py b/modules/scylla/tests/test_scylla.py new file mode 100644 index 000000000..3d1ecf44d --- /dev/null +++ b/modules/scylla/tests/test_scylla.py @@ -0,0 +1,18 @@ +from testcontainers.scylla import ScyllaContainer + + +def test_docker_run_scylla(): + with ScyllaContainer() as scylla: + cluster = scylla.get_cluster() + with cluster.connect() as session: + session.execute( + "CREATE KEYSPACE keyspace1 WITH replication = " + "{'class': 'SimpleStrategy', 'replication_factor': '1'};" + ) + session.execute("CREATE TABLE keyspace1.table1 (key1 int, key2 int, PRIMARY KEY (key1));") + session.execute("INSERT INTO keyspace1.table1 (key1,key2) values (1,2);") + + response = session.execute("SELECT * FROM keyspace1.table1") + + assert response.one().key1 == 1 + assert response.one().key2 == 2 diff --git a/poetry.lock b/poetry.lock index 228c9c483..f35433bb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -331,7 +331,7 @@ files = [ name = "cassandra-driver" version = "3.29.1" description = "DataStax Driver for Apache Cassandra" -optional = false +optional = true python-versions = "*" files = [ {file = "cassandra-driver-3.29.1.tar.gz", hash = "sha256:38e9c2a2f2a9664bb03f1f852d5fccaeff2163942b5db35dffcf8bf32a51cfe5"}, @@ -588,7 +588,7 @@ typing-extensions = ">=4.5.0" name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, @@ -1001,7 +1001,7 @@ typing = ["typing-extensions (>=4.8)"] name = "geomet" version = "0.2.1.post1" description = "GeoJSON <-> WKT/WKB conversion utilities" -optional = false +optional = true python-versions = ">2.6, !=3.3.*, <4" files = [ {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, @@ -1968,7 +1968,6 @@ python-versions = ">=3.7" files = [ {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, - {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, ] @@ -4667,6 +4666,7 @@ qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] +scylla = ["cassandra-driver"] selenium = ["selenium"] sftp = ["cryptography"] test-module-import = ["httpx"] @@ -4677,4 +4677,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "18a5763385d12114513ef5d65268de3ea6567e79b21049b6d58d1803f4257306" +content-hash = "de6e3fcb9a3c1a402682f9681ba2c8270a9a0e8882f82b9835a2f50acb1e37d5" diff --git a/pyproject.toml b/pyproject.toml index 3bccf8800..b80c7e417 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ packages = [ { include = "testcontainers", from = "modules/registry" }, { include = "testcontainers", from = "modules/sftp" }, { include = "testcontainers", from = "modules/selenium" }, + { include = "testcontainers", from = "modules/scylla" }, { include = "testcontainers", from = "modules/trino" }, { include = "testcontainers", from = "modules/vault" }, { include = "testcontainers", from = "modules/weaviate" }, @@ -86,6 +87,7 @@ typing-extensions = "*" # community modules python-arango = { version = "^7.8", optional = true } azure-storage-blob = { version = "^12.19", optional = true } +cassandra-driver = { version = "3.29.1", optional = true } clickhouse-driver = { version = "*", optional = true } google-cloud-pubsub = { version = ">=2", optional = true } google-cloud-datastore = { version = ">=2", optional = true } @@ -156,6 +158,7 @@ rabbitmq = ["pika"] redis = ["redis"] registry = ["bcrypt"] selenium = ["selenium"] +scylla = ["cassandra-driver"] sftp = ["cryptography"] vault = [] weaviate = ["weaviate-client"] @@ -175,7 +178,6 @@ psycopg2-binary = "2.9.9" pg8000 = "1.30.5" sqlalchemy = "2.0.28" psycopg = "3.1.18" -cassandra-driver = "3.29.1" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" hvac = "2.1.0" From 71c3a1a29e1839de91f05c6bcd4c620122195a94 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 14 Aug 2024 08:38:44 -0400 Subject: [PATCH 420/425] fix: bring back cassandra driver bc otherwise how does it get installed for cassandra module test run? (#680) cleanup after incorrect decision made on scylla pr rebase --- poetry.lock | 8 ++++---- pyproject.toml | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index f35433bb3..bf8872cb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -331,7 +331,7 @@ files = [ name = "cassandra-driver" version = "3.29.1" description = "DataStax Driver for Apache Cassandra" -optional = true +optional = false python-versions = "*" files = [ {file = "cassandra-driver-3.29.1.tar.gz", hash = "sha256:38e9c2a2f2a9664bb03f1f852d5fccaeff2163942b5db35dffcf8bf32a51cfe5"}, @@ -588,7 +588,7 @@ typing-extensions = ">=4.5.0" name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, @@ -1001,7 +1001,7 @@ typing = ["typing-extensions (>=4.8)"] name = "geomet" version = "0.2.1.post1" description = "GeoJSON <-> WKT/WKB conversion utilities" -optional = true +optional = false python-versions = ">2.6, !=3.3.*, <4" files = [ {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, @@ -4677,4 +4677,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "de6e3fcb9a3c1a402682f9681ba2c8270a9a0e8882f82b9835a2f50acb1e37d5" +content-hash = "69d30cc8cd59a8aa0d019c42b1f171e449dabf6959828160d11f3084c5a03f7f" diff --git a/pyproject.toml b/pyproject.toml index b80c7e417..957a5041e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,6 +178,7 @@ psycopg2-binary = "2.9.9" pg8000 = "1.30.5" sqlalchemy = "2.0.28" psycopg = "3.1.18" +cassandra-driver = "3.29.1" pytest-asyncio = "0.23.5" kafka-python-ng = "^2.2.0" hvac = "2.1.0" From 370dfa47634064104cbef1c3a5e9186e47dc284b Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 14 Aug 2024 08:58:37 -0400 Subject: [PATCH 421/425] attempt to fix code cov --- .github/workflows/ci-community.yml | 7 ++++--- .github/workflows/ci-core.yml | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index caebace06..e6369aa15 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -4,11 +4,11 @@ name: modules on: push: - branches: [ main ] + branches: [ main, master ] paths: - "modules/**" pull_request: - branches: [ main ] + branches: [ main, master ] paths: - "modules/**" @@ -35,6 +35,7 @@ jobs: modules=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | jq '.[] | split("/") | first' | jq -s -c '. | unique') echo "computed_modules=$modules" echo "computed_modules=$modules" >> $GITHUB_OUTPUT + echo 'computed_modules=["arangodb","aws","azurite","cassandra","chroma","clickhouse","cockroachdb","cosmosdb","db2","elasticsearch","generic","google","influxdb","k3s","kafka","keycloak","localstack","mailpit","memcached","milvus","minio","mongodb","mqtt","mssql","mysql","nats","neo4j","nginx","ollama","opensearch","oracle"-free,"postgres","qdrant","rabbitmq","redis","registry","scylla","selenium","sftp","test_module_import","trino","vault","weaviate"]' >> $GITHUB_OUTPUT outputs: changed_modules: ${{ steps.compute-changes.outputs.computed_modules }} test: @@ -44,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ${{ github.ref_name == 'master' && format('["3.11"]') || format('["3.9", "3.10", "3.11", "3.12"]') }} module: ${{ fromJSON(needs.track-modules.outputs.changed_modules) }} steps: - name: Checkout contents diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 0f6a5e4e2..021033903 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -4,9 +4,9 @@ name: core on: push: - branches: [main] + branches: [ main, master ] pull_request: - branches: [main] + branches: [ main, master ] jobs: run-tests-and-coverage: @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ${{ github.ref_name == 'master' && format('["3.11"]') || format('["3.9", "3.10", "3.11", "3.12"]') }} steps: - uses: actions/checkout@v4 - name: Set up Python @@ -22,7 +22,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: poetry install --all-extras + run: poetry install --with dev - name: Run twine check run: poetry build && poetry run twine check dist/*.tar.gz - name: Run tests @@ -37,6 +37,7 @@ jobs: retention-days: 1 - name: Run doctests run: make core/doctests + if: github.ref_name != 'master' coverage-compile: needs: "run-tests-and-coverage" From 2da7579304b79c9293a3cc3702b9c8aa779685e0 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 14 Aug 2024 09:00:18 -0400 Subject: [PATCH 422/425] maybe-modules? --- modules/test | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 modules/test diff --git a/modules/test b/modules/test new file mode 100644 index 000000000..e69de29bb From 4ac98307e99b176542a43f53bf639108f8959c9d Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 14 Aug 2024 09:01:33 -0400 Subject: [PATCH 423/425] maybe-modules?-2 --- .github/workflows/ci-community.yml | 2 +- modules/test | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 modules/test diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index e6369aa15..7c0a819c4 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -34,7 +34,7 @@ jobs: run: | modules=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | jq '.[] | split("/") | first' | jq -s -c '. | unique') echo "computed_modules=$modules" - echo "computed_modules=$modules" >> $GITHUB_OUTPUT + #echo "computed_modules=$modules" >> $GITHUB_OUTPUT echo 'computed_modules=["arangodb","aws","azurite","cassandra","chroma","clickhouse","cockroachdb","cosmosdb","db2","elasticsearch","generic","google","influxdb","k3s","kafka","keycloak","localstack","mailpit","memcached","milvus","minio","mongodb","mqtt","mssql","mysql","nats","neo4j","nginx","ollama","opensearch","oracle"-free,"postgres","qdrant","rabbitmq","redis","registry","scylla","selenium","sftp","test_module_import","trino","vault","weaviate"]' >> $GITHUB_OUTPUT outputs: changed_modules: ${{ steps.compute-changes.outputs.computed_modules }} diff --git a/modules/test b/modules/test deleted file mode 100644 index e69de29bb..000000000 From d54c544a8b9a728a65ae14892bd8e43c05005359 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 14 Aug 2024 09:03:48 -0400 Subject: [PATCH 424/425] maybe fix fromJSON in gha --- .github/workflows/ci-community.yml | 2 +- .github/workflows/ci-core.yml | 2 +- modules/test | 0 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 modules/test diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 7c0a819c4..a850fcfd9 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ github.ref_name == 'master' && format('["3.11"]') || format('["3.9", "3.10", "3.11", "3.12"]') }} + python-version: ${{ github.ref_name == 'master' && fromJSON('["3.11"]') || fromJSON('["3.9", "3.10", "3.11", "3.12"]') }} module: ${{ fromJSON(needs.track-modules.outputs.changed_modules) }} steps: - name: Checkout contents diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 021033903..00b163141 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ github.ref_name == 'master' && format('["3.11"]') || format('["3.9", "3.10", "3.11", "3.12"]') }} + python-version: ${{ github.ref_name == 'master' && fromJSON('["3.11"]') || fromJSON('["3.9", "3.10", "3.11", "3.12"]') }} steps: - uses: actions/checkout@v4 - name: Set up Python diff --git a/modules/test b/modules/test new file mode 100644 index 000000000..e69de29bb From ec1ae0e49f04d248ab2a4f40ca7689b22fc1171d Mon Sep 17 00:00:00 2001 From: David Ankin Date: Wed, 14 Aug 2024 09:12:08 -0400 Subject: [PATCH 425/425] try to run community - theres not codecov upload in there though --- .github/workflows/ci-community.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index a850fcfd9..5ff430683 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -32,10 +32,14 @@ jobs: - name: Compute modules from files id: compute-changes run: | + if [[ "${{ github.ref_name }}" == "master" ]]; then + echo computed_modules=$(find modules/ -mindepth 1 -maxdepth 1 -type d | sed 's#modules/##') + echo computed_modules=$(find modules/ -mindepth 1 -maxdepth 1 -type d | sed 's#modules/##') >> $GITHUB_OUTPUT + else modules=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | jq '.[] | split("/") | first' | jq -s -c '. | unique') echo "computed_modules=$modules" #echo "computed_modules=$modules" >> $GITHUB_OUTPUT - echo 'computed_modules=["arangodb","aws","azurite","cassandra","chroma","clickhouse","cockroachdb","cosmosdb","db2","elasticsearch","generic","google","influxdb","k3s","kafka","keycloak","localstack","mailpit","memcached","milvus","minio","mongodb","mqtt","mssql","mysql","nats","neo4j","nginx","ollama","opensearch","oracle"-free,"postgres","qdrant","rabbitmq","redis","registry","scylla","selenium","sftp","test_module_import","trino","vault","weaviate"]' >> $GITHUB_OUTPUT + fi outputs: changed_modules: ${{ steps.compute-changes.outputs.computed_modules }} test: