Skip to content
Closed
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
6 changes: 2 additions & 4 deletions .github/workflows/ci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,5 @@ jobs:
uv run pre-commit run end-of-file-fixer
uv run pre-commit run ruff
uv run pre-commit run ruff-format
- name: Execute mypy
run: |
make mypy-core-report
make mypy-core
- name: Execute ty
run: make ty-core
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ repos:

- repo: local
hooks:
- id: mypy
name: mypy
entry: uv run mypy
args: ["--config-file", "pyproject.toml"]
files: "core" # start with the core being type checked
- id: ty
name: ty
entry: uv run ty check
files: "core/testcontainers"
language: system
types: [ python ]
pass_filenames: false
require_serial: true
7 changes: 2 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,8 @@ coverage: ## Target to combine and report coverage.
lint: ## Lint all files in the project, which we also run in pre-commit
uv run pre-commit run --all-files

mypy-core: ## Run mypy on the core package
uv run mypy --config-file pyproject.toml core

mypy-core-report: ## Generate a report for mypy on the core package
uv run mypy --config-file pyproject.toml core | uv run python scripts/mypy_report.py
ty-core: ## Run ty on the core package
uv run ty check core/testcontainers

docs: ## Build the docs for the project
uv run --all-extras sphinx-build -nW . docs/_build
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
[![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)
[![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
![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)
Expand Down
12 changes: 6 additions & 6 deletions core/testcontainers/core/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def start(self) -> Self:
name=self._name,
volumes=self.volumes,
tmpfs=self.tmpfs,
**{**network_kwargs, **self._kwargs},
**{**network_kwargs, **self._kwargs}, # ty: ignore[invalid-argument-type]
)

for t in self._transferable_specs:
Expand All @@ -225,7 +225,7 @@ def start(self) -> Self:
self._wait_strategy.wait_until_ready(self)
except TimeoutError as ex:
if hasattr(ex, "add_note"):
ex.add_note(self._container.logs().decode())
ex.add_note(self._container.logs().decode()) # ty: ignore[call-non-callable]
raise ex

logger.info("Container started: %s", self._container.short_id)
Expand Down Expand Up @@ -256,12 +256,10 @@ def get_container_host_ip(self) -> str:
if connection_mode == ConnectionMode.docker_host:
return self.get_docker_client().host()
elif connection_mode == ConnectionMode.gateway_ip:
# mypy:
container = self._container
assert container is not None
return self.get_docker_client().gateway_ip(container.id)
elif connection_mode == ConnectionMode.bridge_ip:
# mypy:
container = self._container
assert container is not None
return self.get_docker_client().bridge_ip(container.id)
Expand Down Expand Up @@ -305,7 +303,7 @@ def with_tmpfs_mount(self, container_path: str, size: Optional[str] = None) -> S
self.tmpfs[container_path] = size or ""
return self

def get_wrapped_container(self) -> "Container":
def get_wrapped_container(self) -> Optional["Container"]:
return self._container

def get_docker_client(self) -> DockerClient:
Expand Down Expand Up @@ -377,7 +375,9 @@ def with_copy_into_container(
def copy_into_container(self, transferable: Transferable, destination_in_container: str, mode: int = 0o644) -> None:
return self._transfer_into_container(transferable, destination_in_container, mode)

def _transfer_into_container(self, transferable: Transferable, destination_in_container: str, mode: int) -> None:
def _transfer_into_container(
self, transferable: Transferable, destination_in_container: str, mode: int = 0o644
) -> None:
if not self._container:
raise ContainerStartException("Container must be started before transferring files")

Expand Down
2 changes: 1 addition & 1 deletion core/testcontainers/core/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def __exit__(
) -> None:
self.remove()

def get_wrapped_image(self) -> "Image":
def get_wrapped_image(self) -> Optional["Image"]:
return self._image

def get_docker_client(self) -> DockerClient:
Expand Down
5 changes: 4 additions & 1 deletion core/testcontainers/core/wait_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from typing_extensions import Self

from testcontainers.compose import DockerCompose
from testcontainers.core.exceptions import ContainerStartException
from testcontainers.core.utils import setup_logger

# Import base classes from waiting_utils to make them available for tests
Expand Down Expand Up @@ -698,6 +699,8 @@ def get_status(self, container: Any) -> str:
def _get_status_tc_container(container: "DockerContainer") -> str:
logger.debug("fetching status of container %s", container)
wrapped = container.get_wrapped_container()
if wrapped is None:
raise ContainerStartException("Container must be started before fetching status")
wrapped.reload()
return cast("str", wrapped.status)

Expand Down Expand Up @@ -855,7 +858,7 @@ def wait_until_ready(self, container: WaitStrategyTarget) -> None:
)

try:
result = container.exec(self._command)
result = container.exec(self._command) # ty: ignore[call-non-callable]
last_exit_code = result.exit_code
last_output = result.output.decode() if hasattr(result.output, "decode") else str(result.output)

Expand Down
5 changes: 3 additions & 2 deletions core/testcontainers/core/waiting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ def wait_until_ready(self, container: WaitStrategyTarget) -> Any:
except self.transient_exceptions as e:
if time.time() - start_time > self._startup_timeout:
raise TimeoutError(
f"Wait time ({self._startup_timeout}s) exceeded for {self.func.__name__}"
f"Wait time ({self._startup_timeout}s) exceeded for "
f"{getattr(self.func, '__name__', repr(self.func))}"
f"(args: {self.args}, kwargs: {self.kwargs}). Exception: {e}. "
f"Hint: Check if the container is ready, the function parameters are correct, "
f"and the expected conditions are met for the function to succeed."
Expand All @@ -213,7 +214,7 @@ def wrapper(wrapped: Callable[..., Any], instance: Any, args: tuple[Any], kwargs
# Fallback to direct call if we can't identify the container
return wrapped(*args, **kwargs)

return cast("Callable[[F], F]", wrapper)
return cast("Callable[[F], F]", wrapper) # ty: ignore[invalid-return-type]


def wait_for(condition: Callable[..., bool]) -> bool:
Expand Down
45 changes: 12 additions & 33 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ test = [
"tomli", # required for pyproject.toml tests, TODO: remove once we drop py3.10 support
]
lint = [
"mypy>=1",
"types-paramiko>=4",
"ty",
"ruff",
"pre-commit>=4",
]
Expand Down Expand Up @@ -353,39 +352,19 @@ keep-runtime-typing = true
[tool.ruff.lint.flake8-type-checking]
strict = true

[tool.mypy]
python_version = "3.10"
namespace_packages = true
explicit_package_bases = true
pretty = true
show_error_codes = true
warn_return_any = true
strict = true
modules = ["testcontainers.core"]
mypy_path = [
[tool.ty.src]
include = ["core/testcontainers"]

[tool.ty.environment]
python-version = "3.10"
extra-paths = [
"core",
"modules/mailpit",
"modules/sftp",
]
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]

[[tool.mypy.overrides]]
module = ["tests.*"]
check_untyped_defs = true
disable_error_code = ["no-untyped-def"]

[[tool.mypy.overrides]]
module = ["docker.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["wrapt.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["requests.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["testcontainers.registry"]
ignore_missing_imports = true
[tool.ty.rules]
# Suppressed because docker/wrapt/requests/testcontainers.registry are
# not type-stubbed; previously suppressed via ignore-missing-imports overrides.
unresolved-import = "ignore"
possibly-missing-submodule = "ignore"
35 changes: 0 additions & 35 deletions scripts/mypy_report.py

This file was deleted.

Loading
Loading