diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index 5188b9d4..9284463c 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 65bb2388..c39eb1ea 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 d8537efe..1816f64b 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 5db9477e..4c5ff938 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 d6e7ac7d..71828de2 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 843283d6..ca570229 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 6eac4215..ce54e467 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 fd537f92..3810ea0f 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 f6d4447a..d0f61e64 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 d27c491a..357dd660 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 09dde883..c48dce7e 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]]