Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
Change Log
==========

0.7.0 (2026-08-13)
------------------

- Drop support for Python < 3.9.
- Add support for Python 3.12 and pytest >= 8.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pyproject.toml says pytest >= 7

- Require docker >= 7.0.0 and pluggy >= 1.0.0; remove urllib3 < 2 pin.
- Replace deprecated ``inspect.getcallargs`` with ``inspect.Signature.bind``.
- Switch build backend from setuptools to poetry-core.
- Add ``container_env`` parameter to ``LocalstackSession``.
- Default the LocalStack image to ``4.13`` instead of ``latest``. This is the
last free community release published before LocalStack consolidated to a
single image requiring an auth token (v2026.03.0, March 2026).
- Fix the ``RunningSession`` start retry loop so it always sleeps and increments
the retry counter; previously these only ran when the max delay was exceeded,
causing a busy loop.
- Increase the botocore service-check connect/read timeouts from 1s to 5s to
reduce flaky readiness checks on slower hosts.

0.6.1 (2023-06-06)
------------------

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ clean: ## remove all build, test, coverage and Python artifacts
lint: $(INSTALL_STAMP) ## check code style
$(POETRY) run isort --check-only ./tests/ $(NAME)
$(POETRY) run black --check ./tests/ $(NAME) --diff
$(POETRY) run pflake8 ./tests/ $(NAME)
$(POETRY) run flake8 ./tests/ $(NAME)
$(POETRY) run mypy ./tests/ $(NAME) --ignore-missing-imports
$(POETRY) run bandit -r $(NAME) -s B608

Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ via a Localstack_ Docker container.

**Requires:**

- pytest >= 3.3.0
- pytest >= 7
- Docker

Tested against Python >= 3.6.
Tested against Python >= 3.9.

.. _pytest: http://docs.pytest.org/
.. _AWS: https://aws.amazon.com/
Expand Down
1,626 changes: 805 additions & 821 deletions poetry.lock

Large diffs are not rendered by default.

46 changes: 24 additions & 22 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pytest-localstack"
version = "0.6.1"
version = "0.7.0"
description = "Pytest plugin for AWS integration tests"
authors = ["Mintel Group Ltd."]
license = "MIT"
Expand All @@ -15,9 +15,10 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX",
"Operating System :: Unix",
"Programming Language :: Python :: 3.6",
"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",
"Programming Language :: Python :: 3",
"Programming Language :: Python",
"Topic :: Software Development :: Libraries",
Expand All @@ -30,31 +31,32 @@ include = ["CHANGELOG.rst", "LICENSE"]
localstack = "pytest_localstack"

[tool.poetry.dependencies]
python = "^3.7.0"
python = "^3.9"
botocore = "!=1.4.45"
urllib3 = "<2" # https://github.com/orgs/python-poetry/discussions/7937#discussioncomment-5921842
docker = "^6.0.0"
pluggy = "^0.12.0"
pytest = "^6.0.0" # need caplog (+ warnings for tests)
docker = ">=7.0.0"
pluggy = ">=1.0.0"
pytest = ">=7"

[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
boto3 = "*"
hypothesis = "*"
black = "*"
Comment on lines 41 to 43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we get versioning on these dependencies as well instead of just having them as * .

isort = "^5.8.0"
mypy = "^0.812"
flake8 = "^3.9.2"
bandit = "^1.7.0"
pyproject-flake8 = "^0.0.1-alpha.2"
coverage = {extras = ["toml"], version = "^5.5"}
codecov = "^2.1.11"
pytest-cov = "^2.12.1"
pytest-xdist = "^2.2.1"
Sphinx = "^4.0.2"
isort = ">=5.8.0"
mypy = ">=1.0"
flake8 = ">=6.0"
bandit = ">=1.7.5"
pyyaml = ">=6.0.1"
coverage = {extras = ["toml"], version = ">=7.0"}
codecov = ">=2.1.11"
pytest-cov = ">=4.0"
pytest-timeout = ">=2.1"
pytest-xdist = ">=3.0"
Sphinx = ">=4.0.2"
flake8-pyproject = "^1.2.4"

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta:__legacy__"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
norecursedirs = ".* build dist *.egg tmp*"
Expand Down
6 changes: 3 additions & 3 deletions pytest_localstack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pytest

from pytest_localstack import plugin, session, utils
from pytest_localstack import constants, plugin, session, utils


_start_timeout = None
Expand Down Expand Up @@ -47,7 +47,7 @@ def session_fixture(
kinesis_error_probability=0.0,
dynamodb_error_probability=0.0,
container_log_level=logging.DEBUG,
localstack_version="latest",
localstack_version=constants.DEFAULT_LOCALSTACK_VERSION,
auto_remove=True,
pull_image=True,
container_name=None,
Expand Down Expand Up @@ -93,7 +93,7 @@ def session_fixture(
container_log_level (int, optional): The logging level to use
for Localstack container logs. Defaults to :data:`logging.DEBUG`.
localstack_version (str, optional): The version of the Localstack
image to use. Defaults to :const:`"latest"`.
image to use. Defaults to :const:`.constants.DEFAULT_LOCALSTACK_VERSION`.
auto_remove (bool, optional): If :obj:`True`, delete the Localstack
container when it stops. Default: :obj:`True`
pull_image (bool, optional): If :obj:`True`, pull the Localstack
Expand Down
6 changes: 6 additions & 0 deletions pytest_localstack/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@
DEFAULT_CONTAINER_START_TIMEOUT = 60
DEFAULT_CONTAINER_STOP_TIMEOUT = 10

# The default Localstack image version to use.
# Pinned to the last free/community release (v4.13) published before
# LocalStack consolidated to a single image requiring an auth token
# (starting with v2026.03.0, March 2026).
DEFAULT_LOCALSTACK_VERSION = "4.13"

BOTOCORE_VERSION = utils.get_version_tuple(botocore.__version__)
9 changes: 6 additions & 3 deletions pytest_localstack/contrib/botocore.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def patch_fixture(
kinesis_error_probability=0.0,
dynamodb_error_probability=0.0,
container_log_level=logging.DEBUG,
localstack_version="latest",
localstack_version=constants.DEFAULT_LOCALSTACK_VERSION,
auto_remove=True,
pull_image=True,
container_name=None,
Expand Down Expand Up @@ -383,7 +383,7 @@ def patch_fixture(
container_log_level (int, optional): The logging level to use
for Localstack container logs. Defaults to :data:`logging.DEBUG`.
localstack_version (str, optional): The version of the Localstack
image to use. Defaults to :const:`"latest"`.
image to use. Defaults to :const:`.constants.DEFAULT_LOCALSTACK_VERSION`.
auto_remove (bool, optional): If :obj:`True`, delete the Localstack
container when it stops. Default: :obj:`True`
pull_image (bool, optional): If :obj:`True`, pull the Localstack
Expand Down Expand Up @@ -456,7 +456,10 @@ def create_client(self, *args, **kwargs):
"""Create a botocore client."""
# Localstack doesn't use the virtual host addressing style.
config = botocore.config.Config(s3={"addressing_style": "path"})
callargs = inspect.getcallargs(_original_create_client, self, *args, **kwargs)
sig = inspect.signature(inspect.unwrap(_original_create_client))
bound = sig.bind(self, *args, **kwargs)
bound.apply_defaults()
callargs = bound.arguments
if callargs.get("config"):
config = callargs["config"].merge(config)
callargs["config"] = config
Expand Down
4 changes: 2 additions & 2 deletions pytest_localstack/service_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def _wrapped(localstack_session):
if not is_port_open(url):
raise exceptions.ServiceError(service_name=service_name)
config_kwargs = {
"connect_timeout": 1,
"read_timeout": 1,
"connect_timeout": 5,
"read_timeout": 5,
"s3": {"addressing_style": "path"},
}
if constants.BOTOCORE_VERSION >= (1, 6, 0):
Expand Down
17 changes: 12 additions & 5 deletions pytest_localstack/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(
services=None,
region_name=None,
use_ssl=False,
localstack_version="latest",
localstack_version=constants.DEFAULT_LOCALSTACK_VERSION,
**kwargs,
):
self.kwargs = kwargs
Expand Down Expand Up @@ -138,8 +138,8 @@ def _check_services(self, timeout, initial_retry_delay=0.01, max_delay=1):
delay = (2**num_retries) * initial_retry_delay
if delay > max_delay:
delay = max_delay
time.sleep(delay)
num_retries += 1
time.sleep(delay)
num_retries += 1

def stop(self, timeout=10):
"""Stops Localstack."""
Expand Down Expand Up @@ -220,13 +220,17 @@ class LocalstackSession(RunningSession):
container_log_level (int, optional): The logging level to use
for Localstack container logs. Defaults to :attr:`logging.DEBUG`.
localstack_version (str, optional): The version of the Localstack
image to use. Defaults to `latest`.
image to use. Defaults to :const:`.constants.DEFAULT_LOCALSTACK_VERSION`.
auto_remove (bool, optional): If True, delete the Localstack
container when it stops.
container_name (str, optional): The name for the Localstack
container. Defaults to a randomly generated id.
use_ssl (bool, optional): If True use SSL to connect to Localstack.
Default is False.
container_env (dict, optional): A dictionary of environment variables
which will be set in the Localstack container. see
https://docs.localstack.cloud/references/configuration/ for useful
settings.
**kwargs: Additional kwargs will be stored in a `kwargs` attribute
in case test resource factories want to access them.

Expand All @@ -242,12 +246,13 @@ def __init__(
kinesis_error_probability=0.0,
dynamodb_error_probability=0.0,
container_log_level=logging.DEBUG,
localstack_version="latest",
localstack_version=constants.DEFAULT_LOCALSTACK_VERSION,
auto_remove=True,
pull_image=True,
container_name=None,
use_ssl=False,
hostname=None,
container_env=None,
**kwargs,
):
self._container = None
Expand All @@ -273,6 +278,7 @@ def __init__(
self.container_log_level = container_log_level
self.localstack_version = localstack_version
self.container_name = container_name or generate_container_name()
self.container_env = container_env or {}

def start(self, timeout=60):
"""Start the Localstack container.
Expand Down Expand Up @@ -318,6 +324,7 @@ def start(self, timeout=60):
"KINESIS_ERROR_PROBABILITY": kinesis_error_probability,
"DYNAMODB_ERROR_PROBABILITY": dynamodb_error_probability,
"USE_SSL": use_ssl,
**self.container_env,
},
ports={port: None for port in self.services.values()},
)
Expand Down
4 changes: 3 additions & 1 deletion tests/functional/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

@pytest.mark.parametrize("test_service", sorted(constants.SERVICE_PORTS))
def test_RunningSession_individual_services(test_service, docker_client):
localstack_imagename = "localstack/localstack:latest"
localstack_imagename = (
"localstack/localstack:" + constants.DEFAULT_LOCALSTACK_VERSION
)

docker_client.images.pull(localstack_imagename)
localstack_container = None
Expand Down