Skip to content

Commit

Permalink
fix: Perform docker client cached lookup fallback, with optional conf…
Browse files Browse the repository at this point in the history
…ig and environment settings.
  • Loading branch information
DanCardin committed Oct 13, 2023
1 parent 7dc5d20 commit 22ee3b4
Show file tree
Hide file tree
Showing 9 changed files with 696 additions and 643 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
matrix:
# Test our minimum version bound, the highest version available,
# and something in the middle (i.e. what gets run locally).
python-version: ["3.7", "3.9", "3.11"]
python-version: ["3.7", "3.9", "3.12"]
pytest-asyncio-version: ["0.16.0", "0.19.0"]
sqlalchemy-version: ["1.3.0", "1.4.0", "2.0.0"]

Expand Down
23 changes: 23 additions & 0 deletions docs/source/docker_client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Docker/Podman/Nerdctl
=====================

Docker-alike clients which are CLI-compatible with Docker, i.e. podman and nerdctl,
can alternatively be configured to be used instead of docker.

There are a number of ways to configure this setting, depending on the scenarios
in which you expect the code to be used.

Known-compatible string values for all settings options are: docker, podman, and nerdctl.

* Environment variable `PMR_DOCKER_CLIENT=docker`: Use the environment variable option if
the setting is environment-specific.

* CLI options `pytest --pmr-docker-client docker`: Use this option for ad-hoc selection

* pytest.ini setting `pmr_docker_client=docker`: Use this option to default all users to
the selected value

* Fallback: If none of the above options are set, each of the above options will be
searched for, in order. The first option to be found will be used.

Note, this fallback logic will be executed at most once per test run and cached.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Welcome to Pytest Mock Resource's documentation!
Fixture Configuration <config>
CLI (Startup Lag) <cli>
CI Support <ci>
Docker/Podman/Nerdctl <docker_client>
API <api>
Contributing <contributing>

Expand Down
1,214 changes: 604 additions & 610 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pytest-mock-resources"
version = "2.9.1"
version = "2.9.2"
description = "A pytest plugin for easily instantiating reproducible mock resources."
authors = [
"Omar Khan <[email protected]>",
Expand Down Expand Up @@ -54,7 +54,7 @@ python-on-whales = {version = ">=0.22.0", optional = true}

[tool.poetry.dev-dependencies]
black = "22.3.0"
botocore = ">=1.10.84"
botocore = ">=1.31.63"
coverage = "*"
flake8 = "*"
isort = ">=5.0"
Expand Down
5 changes: 2 additions & 3 deletions src/pytest_mock_resources/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pytest_mock_resources.config import DockerContainerConfig
from pytest_mock_resources.container.base import container_name, get_container
from pytest_mock_resources.hooks import get_docker_client


class StubPytestConfig:
Expand Down Expand Up @@ -65,9 +66,7 @@ def execute(fixture: str, pytestconfig: StubPytestConfig, start=True, stop=False
pass

if stop:
from .whales import get_docker_client

docker = get_docker_client()
docker = get_docker_client(config)

assert config.port
name = container_name(fixture, int(config.port))
Expand Down
27 changes: 19 additions & 8 deletions src/pytest_mock_resources/container/base.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from __future__ import annotations

import contextlib
import json
import pathlib
import socket
import time
import types
from typing import Awaitable, Callable, Optional, TypeVar
from typing import Awaitable, Callable, Optional, TYPE_CHECKING, TypeVar

from pytest_mock_resources.hooks import get_pytest_flag, use_multiprocess_safe_mode
from pytest_mock_resources.hooks import (
get_docker_client,
get_pytest_flag,
use_multiprocess_safe_mode,
)

try:
import responses as _responses
Expand All @@ -16,6 +22,10 @@
except ImportError:
responses = None

if TYPE_CHECKING:

from python_on_whales.docker_client import DockerClient


DEFAULT_RETRIES = 40
DEFAULT_INTERVAL = 0.5
Expand Down Expand Up @@ -76,6 +86,7 @@ def retry(

def get_container(pytestconfig, config, *, retries=DEFAULT_RETRIES, interval=DEFAULT_INTERVAL):
multiprocess_safe_mode = use_multiprocess_safe_mode(pytestconfig)
docker = get_docker_client(pytestconfig)

if responses:
# XXX: moto library may over-mock responses. SEE: https://github.com/spulec/moto/issues/1026
Expand All @@ -95,6 +106,7 @@ def get_container(pytestconfig, config, *, retries=DEFAULT_RETRIES, interval=DEF
# wait for the container one process at a time
with FileLock(str(fn)):
container = wait_for_container(
docker,
config,
retries=retries,
interval=interval,
Expand All @@ -104,6 +116,7 @@ def get_container(pytestconfig, config, *, retries=DEFAULT_RETRIES, interval=DEF

else:
container = wait_for_container(
docker,
config,
retries=retries,
interval=interval,
Expand All @@ -116,16 +129,14 @@ def get_container(pytestconfig, config, *, retries=DEFAULT_RETRIES, interval=DEF
container.kill()


def wait_for_container(config, *, retries=DEFAULT_RETRIES, interval=DEFAULT_INTERVAL):
def wait_for_container(
docker: DockerClient, config, *, retries=DEFAULT_RETRIES, interval=DEFAULT_INTERVAL
):
"""Wait for evidence that the container is up and healthy.
The caller must provide a `check_fn` which should `raise ContainerCheckFailed` if
it finds that the container is not yet up.
"""
from ..whales import get_docker_client

docker = get_docker_client()

if config.port is None:
config.set("port", unused_tcp_port())

Expand Down Expand Up @@ -166,7 +177,7 @@ def wait_for_container(config, *, retries=DEFAULT_RETRIES, interval=DEFAULT_INTE
return None


def container_name(name: str, port: int) -> str:
def container_name(name: str, port) -> str:
return f"pmr_{name}_{port}"


Expand Down
49 changes: 44 additions & 5 deletions src/pytest_mock_resources/hooks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import warnings

_resource_kinds = ["postgres", "redshift", "mongo", "redis", "mysql", "moto"]
Expand All @@ -16,6 +17,12 @@ def pytest_addoption(parser):
type="bool",
default=True,
)
parser.addini(
"pmr_docker_client",
"Optional docker client name to use: docker, podman, nerdctl",
type="string",
default=None,
)

group = parser.getgroup("collect")
group.addoption(
Expand All @@ -32,19 +39,53 @@ def pytest_addoption(parser):
help="Optionally disable attempts to cleanup created containers",
dest="pmr_cleanup_container",
)
group.addoption(
"--pmr-docker-client",
default=None,
help="Optional docker client name to use: docker, podman, nerdctl",
dest="pmr_docker_client",
)


def get_pytest_flag(config, name, *, default=None):
value = getattr(config.option, name, default)
if value:
return True
return value

config_value = config.getini(name)
return config_value


def use_multiprocess_safe_mode(config):
return get_pytest_flag(config, "pmr_multiprocess_safe")
return bool(get_pytest_flag(config, "pmr_multiprocess_safe"))


def get_docker_client_name(config) -> str:
pmr_docker_client = os.getenv("PMR_DOCKER_CLIENT")
if pmr_docker_client:
return pmr_docker_client

docker_client = get_pytest_flag(config, "pmr_docker_client")
if docker_client:
return docker_client

import shutil

for client_name in ["docker", "podman", "nerdctl"]:
if shutil.which(client_name):
break
else:
client_name = "docker"

config.option.pmr_docker_client = client_name
return client_name


def get_docker_client(config):
from python_on_whales.docker_client import DockerClient

client_name = get_docker_client_name(config)
return DockerClient(client_call=[client_name])


def pytest_configure(config):
Expand Down Expand Up @@ -84,9 +125,7 @@ def pytest_sessionfinish(session, exitstatus):

# docker-based fixtures should be optional based on the selected extras.
try:
from .whales import get_docker_client

docker = get_docker_client()
docker = get_docker_client(config)
except ImportError:
return

Expand Down
14 changes: 0 additions & 14 deletions src/pytest_mock_resources/whales.py

This file was deleted.

0 comments on commit 22ee3b4

Please sign in to comment.