diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e93ce195a..e6e909fca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - STABLE_PYTHON_VERSION: "3.12" + STABLE_PYTHON_VERSION: "3.13" HATCH_VERBOSE: "1" FORCE_COLOR: "1" CIBW_BUILD_FRONTEND: build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c1ca1c5f7..7a09bc6e3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,7 +11,7 @@ on: - master env: - STABLE_PYTHON_VERSION: "3.12" + STABLE_PYTHON_VERSION: "3.13" FORCE_COLOR: "1" HATCH_VERBOSE: "1" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5859ce1c4..18d09e6ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true env: - STABLE_PYTHON_VERSION: "3.12" + STABLE_PYTHON_VERSION: "3.13" PYTEST_ADDOPTS: --color=yes HATCH_VERBOSE: "1" FORCE_COLOR: "1" @@ -67,12 +67,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] experimental: [false] include: - # Run tests against the next Python version, but no need for the full list of OSes. os: ubuntu-latest - python-version: "3.13.0-alpha.0 - 3.13" + python-version: "3.14" experimental: true steps: @@ -133,16 +133,12 @@ jobs: fail-fast: false matrix: os: [windows-latest, macos-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - # Python 3.8 and 3.9 aren't supported on macos-latest (ARM) + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + # Python 3.9 aren't supported on macos-latest (ARM) exclude: - - os: macos-latest - python-version: "3.8" - os: macos-latest python-version: "3.9" include: - - os: macos-13 - python-version: "3.8" - os: macos-13 python-version: "3.9" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88ae376e1..a30f8afc9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,5 +81,5 @@ repos: entry: hatch run mypy language: system types: [python] - exclude: ^(src/pact|tests|examples/tests)/(?!v3/).*\.py$ + exclude: ^(src/pact|tests|examples|examples/tests)/(?!v3/).*\.py$ stages: [pre-push] diff --git a/docs/scripts/other.py b/docs/scripts/other.py index e6f16148e..7a309b231 100644 --- a/docs/scripts/other.py +++ b/docs/scripts/other.py @@ -84,10 +84,13 @@ def is_binary(buffer: bytes) -> bool: if is_binary(buf): if source_path.stat().st_size < 16 * 2**20: # Copy the file only if it's less than 16MB. - with Path(source_path).open("rb") as fi, mkdocs_gen_files.open( - dest_path, - "wb", - ) as fd: + with ( + Path(source_path).open("rb") as fi, + mkdocs_gen_files.open( + dest_path, + "wb", + ) as fd, + ): fd.write(fi.read()) else: # File is too big, create a redirect. @@ -109,9 +112,12 @@ def is_binary(buffer: bytes) -> bool: ) else: - with Path(source_path).open("r", encoding="utf-8") as fi, mkdocs_gen_files.open( - dest_path, - "w", - encoding="utf-8", - ) as fd: + with ( + Path(source_path).open("r", encoding="utf-8") as fi, + mkdocs_gen_files.open( + dest_path, + "w", + encoding="utf-8", + ) as fd, + ): fd.write(fi.read()) diff --git a/examples/conftest.py b/examples/conftest.py index b8b06bddc..a9c7784f1 100644 --- a/examples/conftest.py +++ b/examples/conftest.py @@ -13,12 +13,15 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Generator, Union +from typing import TYPE_CHECKING, Any import pytest from testcontainers.compose import DockerCompose # type: ignore[import-untyped] from yarl import URL +if TYPE_CHECKING: + from collections.abc import Generator + EXAMPLE_DIR = Path(__file__).parent.resolve() @@ -34,7 +37,7 @@ def broker(request: pytest.FixtureRequest) -> Generator[URL, Any, None]: Otherwise, the Pact broker is started in a container. The URL of the containerised broker is then returned. """ - broker_url: Union[str, None] = request.config.getoption("--broker-url") + broker_url: str | None = request.config.getoption("--broker-url") # If we have been given a broker URL, there's nothing more to do here and we # can return early. diff --git a/examples/src/fastapi.py b/examples/src/fastapi.py index 91558a8df..c9e919c08 100644 --- a/examples/src/fastapi.py +++ b/examples/src/fastapi.py @@ -29,7 +29,7 @@ import logging from datetime import datetime, timezone -from typing import Annotated, Any, Dict, Optional +from typing import Annotated, Any, Optional from pydantic import BaseModel, PlainSerializer @@ -90,7 +90,7 @@ def __repr__(self) -> str: be mocked out to avoid the need for a real database. An example of this can be found in the [test suite][examples.tests.test_01_provider_fastapi]. """ -FAKE_DB: Dict[int, User] = {} +FAKE_DB: dict[int, User] = {} @app.get("/users/{uid}") diff --git a/examples/src/flask.py b/examples/src/flask.py index 256616434..4d3b09a4c 100644 --- a/examples/src/flask.py +++ b/examples/src/flask.py @@ -22,7 +22,7 @@ import logging from dataclasses import dataclass from datetime import datetime, timezone -from typing import Any, Dict, Tuple +from typing import Any from flask import Flask, Response, abort, jsonify, request @@ -89,11 +89,11 @@ def dict(self) -> dict[str, Any]: be mocked out to avoid the need for a real database. An example of this can be found in the [test suite][examples.tests.test_01_provider_flask]. """ -FAKE_DB: Dict[int, User] = {} +FAKE_DB: dict[int, User] = {} @app.route("/users/") -def get_user_by_id(uid: int) -> Response | Tuple[Response, int]: +def get_user_by_id(uid: int) -> Response | tuple[Response, int]: """ Fetch a user by their ID. @@ -114,7 +114,7 @@ def create_user() -> Response: if request.json is None: abort(400, description="Invalid JSON data") - user: Dict[str, Any] = request.json + user: dict[str, Any] = request.json uid = len(FAKE_DB) FAKE_DB[uid] = User( id=uid, @@ -129,7 +129,7 @@ def create_user() -> Response: @app.route("/users/", methods=["DELETE"]) -def delete_user(uid: int) -> Tuple[str | Response, int]: +def delete_user(uid: int) -> tuple[str | Response, int]: if uid not in FAKE_DB: return jsonify({"detail": "User not found"}), 404 del FAKE_DB[uid] diff --git a/examples/src/message.py b/examples/src/message.py index 815719903..13f14c49f 100644 --- a/examples/src/message.py +++ b/examples/src/message.py @@ -10,7 +10,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Dict, Union +from typing import Any class Filesystem: @@ -58,7 +58,7 @@ def __init__(self) -> None: """ self.fs = Filesystem() - def process(self, event: Dict[str, Any]) -> Union[str, None]: + def process(self, event: dict[str, Any]) -> str | None: """ Process an event from the queue. @@ -84,7 +84,7 @@ def process(self, event: Dict[str, Any]) -> Union[str, None]: raise ValueError(msg) @staticmethod - def validate_event(event: Union[Dict[str, Any], Any]) -> None: # noqa: ANN401 + def validate_event(event: dict[str, Any] | Any) -> None: # noqa: ANN401 """ Validates the event received from the queue. diff --git a/examples/tests/test_00_consumer.py b/examples/tests/test_00_consumer.py index 273b4405d..5fed1e449 100644 --- a/examples/tests/test_00_consumer.py +++ b/examples/tests/test_00_consumer.py @@ -17,7 +17,7 @@ import logging from http import HTTPStatus -from typing import TYPE_CHECKING, Any, Dict, Generator +from typing import TYPE_CHECKING, Any import pytest import requests @@ -27,6 +27,7 @@ from pact import Consumer, Format, Like, Provider if TYPE_CHECKING: + from collections.abc import Generator from pathlib import Path from pact.pact import Pact @@ -104,7 +105,7 @@ def test_get_existing_user(pact: Pact, user_consumer: UserConsumer) -> None: # what it needs from the provider (as opposed to the full schema). Should # the provider later decide to add or remove fields, Pact's consumer-driven # approach will ensure that interaction is still valid. - expected: Dict[str, Any] = { + expected: dict[str, Any] = { "id": Format().integer, "name": "Verna Hampton", "created_on": Format().iso_8601_datetime(), @@ -154,7 +155,7 @@ def test_create_user(pact: Pact, user_consumer: UserConsumer) -> None: status code is 200 and the response body matches the expected user data. """ body = {"name": "Verna Hampton"} - expected_response: Dict[str, Any] = { + expected_response: dict[str, Any] = { "id": 124, "name": "Verna Hampton", "created_on": Format().iso_8601_datetime(), diff --git a/examples/tests/test_01_provider_fastapi.py b/examples/tests/test_01_provider_fastapi.py index 1d77f5446..f0328b4f2 100644 --- a/examples/tests/test_01_provider_fastapi.py +++ b/examples/tests/test_01_provider_fastapi.py @@ -27,7 +27,7 @@ import time from datetime import datetime, timezone from multiprocessing import Process -from typing import Any, Dict, Generator, Union +from typing import TYPE_CHECKING, Any, Optional from unittest.mock import MagicMock import pytest @@ -38,6 +38,9 @@ from examples.src.fastapi import User, app from pact import Verifier # type: ignore[import-untyped] +if TYPE_CHECKING: + from collections.abc import Generator + PROVIDER_URL = URL("http://localhost:8080") @@ -51,7 +54,7 @@ class ProviderState(BaseModel): @app.post("/_pact/provider_states") async def mock_pact_provider_states( state: ProviderState, -) -> Dict[str, Union[str, None]]: +) -> dict[str, Optional[str]]: """ Define the provider state. @@ -146,7 +149,7 @@ def mock_post_request_to_create_user() -> None: """ import examples.src.fastapi - local_db: Dict[int, User] = {} + local_db: dict[int, User] = {} def local_setitem(key: int, value: User) -> None: local_db[key] = value diff --git a/examples/tests/test_01_provider_flask.py b/examples/tests/test_01_provider_flask.py index 797e25da8..bc6d2e037 100644 --- a/examples/tests/test_01_provider_flask.py +++ b/examples/tests/test_01_provider_flask.py @@ -27,7 +27,7 @@ import time from datetime import datetime, timezone from multiprocessing import Process -from typing import Any, Dict, Generator, Union +from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock import pytest @@ -37,11 +37,14 @@ from flask import request from pact import Verifier # type: ignore[import-untyped] +if TYPE_CHECKING: + from collections.abc import Generator + PROVIDER_URL = URL("http://localhost:8080") @app.route("/_pact/provider_states", methods=["POST"]) -async def mock_pact_provider_states() -> Dict[str, Union[str, None]]: +async def mock_pact_provider_states() -> dict[str, str | None]: """ Define the provider state. @@ -139,7 +142,7 @@ def mock_post_request_to_create_user() -> None: """ import examples.src.flask - local_db: Dict[int, User] = {} + local_db: dict[int, User] = {} def local_setitem(key: int, value: User) -> None: local_db[key] = value diff --git a/examples/tests/test_02_message_consumer.py b/examples/tests/test_02_message_consumer.py index f5689b839..1498196c7 100644 --- a/examples/tests/test_02_message_consumer.py +++ b/examples/tests/test_02_message_consumer.py @@ -31,7 +31,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any, Generator +from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock import pytest @@ -40,6 +40,7 @@ from pact import MessageConsumer, MessagePact, Provider if TYPE_CHECKING: + from collections.abc import Generator from pathlib import Path from yarl import URL diff --git a/examples/tests/test_03_message_provider.py b/examples/tests/test_03_message_provider.py index 9a9ff3f7d..c657d5d2c 100644 --- a/examples/tests/test_03_message_provider.py +++ b/examples/tests/test_03_message_provider.py @@ -26,7 +26,7 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING from flask import Flask from pact import MessageProvider @@ -38,7 +38,7 @@ PACT_DIR = (Path(__file__).parent / "pacts").resolve() -def generate_write_message() -> Dict[str, str]: +def generate_write_message() -> dict[str, str]: return { "action": "WRITE", "path": "test.txt", @@ -46,7 +46,7 @@ def generate_write_message() -> Dict[str, str]: } -def generate_read_message() -> Dict[str, str]: +def generate_read_message() -> dict[str, str]: return { "action": "READ", "path": "test.txt", diff --git a/examples/tests/v3/basic_flask_server.py b/examples/tests/v3/basic_flask_server.py index 18b3c0c95..1dfbd406f 100644 --- a/examples/tests/v3/basic_flask_server.py +++ b/examples/tests/v3/basic_flask_server.py @@ -8,12 +8,13 @@ import subprocess import sys import time +from collections.abc import Generator from contextlib import contextmanager from datetime import datetime from pathlib import Path from random import randint, uniform from threading import Thread -from typing import Generator, NoReturn +from typing import NoReturn import requests from yarl import URL diff --git a/examples/tests/v3/provider_server.py b/examples/tests/v3/provider_server.py index 23ca0e2ad..9b42ec4e7 100644 --- a/examples/tests/v3/provider_server.py +++ b/examples/tests/v3/provider_server.py @@ -15,7 +15,7 @@ from importlib import import_module from pathlib import Path from threading import Thread -from typing import Generator, NoReturn, Tuple +from typing import TYPE_CHECKING, NoReturn import requests @@ -25,6 +25,9 @@ import flask +if TYPE_CHECKING: + from collections.abc import Generator + logger = logging.getLogger(__name__) @@ -82,7 +85,7 @@ def ping() -> str: return "pong" @self.app.route(self.produce_message_url, methods=["POST"]) - def produce_message() -> flask.Response | Tuple[str, int]: + def produce_message() -> flask.Response | tuple[str, int]: """ Route a message request to the handler function. @@ -101,7 +104,7 @@ def produce_message() -> flask.Response | Tuple[str, int]: return str(e), 500 @self.app.route(self.set_provider_state_url, methods=["POST"]) - def set_provider_state() -> Tuple[str, int]: + def set_provider_state() -> tuple[str, int]: """ Calls the state provider function with the state provided in the request. diff --git a/examples/tests/v3/test_00_consumer.py b/examples/tests/v3/test_00_consumer.py index 523d55d25..7eeda0acd 100644 --- a/examples/tests/v3/test_00_consumer.py +++ b/examples/tests/v3/test_00_consumer.py @@ -18,9 +18,10 @@ """ import json +from collections.abc import Generator from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, Generator +from typing import Any import pytest import requests @@ -72,7 +73,7 @@ def test_get_existing_user(pact: Pact) -> None: code as shown in [`test_get_non_existent_user`](#test_get_non_existent_user). """ - expected: Dict[str, Any] = { + expected: dict[str, Any] = { "id": 123, "name": "Verna Hampton", "created_on": match.datetime( @@ -136,7 +137,7 @@ def test_create_user(pact: Pact) -> None: status code is 200 and the response body matches the expected user data. """ body = {"name": "Verna Hampton"} - expected_response: Dict[str, Any] = { + expected_response: dict[str, Any] = { "id": 124, "name": "Verna Hampton", "created_on": match.datetime( diff --git a/examples/tests/v3/test_01_fastapi_provider.py b/examples/tests/v3/test_01_fastapi_provider.py index 25174173e..1e181a4bb 100644 --- a/examples/tests/v3/test_01_fastapi_provider.py +++ b/examples/tests/v3/test_01_fastapi_provider.py @@ -29,7 +29,7 @@ import time from datetime import datetime, timezone from multiprocessing import Process -from typing import TYPE_CHECKING, Callable, Dict, Literal +from typing import TYPE_CHECKING, Callable, Literal from unittest.mock import MagicMock import uvicorn @@ -45,7 +45,7 @@ async def mock_pact_provider_states( action: Literal["setup", "teardown"], state: str, -) -> Dict[Literal["result"], str]: +) -> dict[Literal["result"], str]: """ Handler for the provider state callback. @@ -226,7 +226,7 @@ def mock_post_request_to_create_user() -> None: """ import examples.src.fastapi - local_db: Dict[int, User] = {} + local_db: dict[int, User] = {} def local_setitem(key: int, value: User) -> None: local_db[key] = value diff --git a/examples/tests/v3/test_02_message_consumer.py b/examples/tests/v3/test_02_message_consumer.py index 58dc405c1..a3d8b9628 100644 --- a/examples/tests/v3/test_02_message_consumer.py +++ b/examples/tests/v3/test_02_message_consumer.py @@ -13,8 +13,6 @@ from typing import ( TYPE_CHECKING, Any, - Dict, - Generator, ) from unittest.mock import MagicMock @@ -24,7 +22,7 @@ from pact.v3.pact import Pact if TYPE_CHECKING: - from collections.abc import Callable + from collections.abc import Callable, Generator log = logging.getLogger(__name__) @@ -90,7 +88,7 @@ def handler() -> Handler: @pytest.fixture def verifier( handler: Handler, -) -> Generator[Callable[[str | bytes | None, Dict[str, Any]], None], Any, None]: +) -> Generator[Callable[[str | bytes | None, dict[str, Any]], None], Any, None]: """ Verifier function for the Pact. @@ -104,7 +102,7 @@ def verifier( """ assert isinstance(handler.fs, MagicMock), "Handler filesystem not mocked" - def _verifier(msg: str | bytes | None, context: Dict[str, Any]) -> None: + def _verifier(msg: str | bytes | None, context: dict[str, Any]) -> None: assert msg is not None, "Message is None" data = json.loads(msg) log.info( @@ -121,7 +119,7 @@ def _verifier(msg: str | bytes | None, context: Dict[str, Any]) -> None: def test_async_message_handler_write( pact: Pact, handler: Handler, - verifier: Callable[[str | bytes | None, Dict[str, Any]], None], + verifier: Callable[[str | bytes | None, dict[str, Any]], None], ) -> None: """ Create a pact between the message handler and the message provider. @@ -161,7 +159,7 @@ def test_async_message_handler_write( def test_async_message_handler_read( pact: Pact, handler: Handler, - verifier: Callable[[str | bytes | None, Dict[str, Any]], None], + verifier: Callable[[str | bytes | None, dict[str, Any]], None], ) -> None: """ Create a pact between the message handler and the message provider. diff --git a/examples/tests/v3/test_03_message_provider.py b/examples/tests/v3/test_03_message_provider.py index 472829c1e..e3b5d6126 100644 --- a/examples/tests/v3/test_03_message_provider.py +++ b/examples/tests/v3/test_03_message_provider.py @@ -8,7 +8,6 @@ from __future__ import annotations from pathlib import Path -from typing import Tuple from unittest.mock import MagicMock from examples.src.message_producer import FileSystemMessageProducer @@ -29,7 +28,7 @@ CURRENT_STATE: str | None = None -def message_producer_function() -> Tuple[str, str]: +def message_producer_function() -> tuple[str, str]: producer = FileSystemMessageProducer() producer.queue = MagicMock() diff --git a/hatch_build.py b/hatch_build.py index dcb65ec1c..7960f5ffa 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -20,7 +20,7 @@ import warnings import zipfile from pathlib import Path -from typing import Any, Dict +from typing import Any import cffi import requests @@ -83,7 +83,7 @@ def clean(self, versions: list[str]) -> None: # noqa: ARG002 def initialize( self, version: str, # noqa: ARG002 - build_data: Dict[str, Any], + build_data: dict[str, Any], ) -> None: """Hook into Hatchling's build process.""" build_data["infer_tag"] = True @@ -338,9 +338,12 @@ def _pact_lib_extract(self, artifact: Path) -> None: msg = f"Unknown artifact type {artifact}" raise ValueError(msg) - with gzip.open(artifact, "rb") as f_in, ( - self.tmpdir / (artifact.name.split("-")[0] + artifact.suffixes[0]) - ).open("wb") as f_out: + with ( + gzip.open(artifact, "rb") as f_in, + (self.tmpdir / (artifact.name.split("-")[0] + artifact.suffixes[0])).open( + "wb" + ) as f_out, + ): shutil.copyfileobj(f_in, f_out) def _pact_lib_header(self, url: str) -> list[str]: @@ -364,9 +367,10 @@ def _pact_lib_header(self, url: str) -> list[str]: url = url.rsplit("/", 1)[0] + "/pact.h" artifact = self._download(url) includes: list[str] = [] - with artifact.open("r", encoding="utf-8") as f_in, ( - self.tmpdir / "pact.h" - ).open("w", encoding="utf-8") as f_out: + with ( + artifact.open("r", encoding="utf-8") as f_in, + (self.tmpdir / "pact.h").open("w", encoding="utf-8") as f_out, + ): for line in f_in: sline = line.strip() if sline.startswith("#include"): diff --git a/pyproject.toml b/pyproject.toml index f64ee9780..4a699bb96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,16 +21,16 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python", - "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.13", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Testing", ] -requires-python = ">=3.8" +requires-python = ">=3.9" # Dependencies of Pact Python should be specified using the broadest range # compatible version unless: @@ -172,7 +172,7 @@ installer = "uv" features = ["devel-test"] [[tool.hatch.envs.test.matrix]] -python = ["3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.9", "3.10", "3.11", "3.12", "3.13"] ################################################################################ ## PyTest Configuration @@ -230,7 +230,6 @@ exclude_lines = [ ################################################################################ [tool.ruff] -target-version = "py38" # TODO: Remove the explicity extend-exclude once astral-sh/ruff#6262 is fixed. # https://github.com/pact-foundation/pact-python/issues/458 @@ -308,7 +307,7 @@ docstring-code-format = true ################################################################################ [tool.mypy] -exclude = '^(src/pact|tests)/(?!v3).+\.py$' +exclude = '^(src/pact|tests|examples|examples/tests)/(?!v3).+\.py$' ################################################################################ ## CI Build Wheel diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index e874aa51f..12b80a376 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -92,14 +92,14 @@ import typing import warnings from enum import Enum -from typing import TYPE_CHECKING, Any, List, Literal, Tuple -from typing import Generator as GeneratorType +from typing import TYPE_CHECKING, Any, Literal from pact.v3._ffi import ffi, lib # type: ignore[import] if TYPE_CHECKING: import datetime from collections.abc import Collection + from collections.abc import Generator as GeneratorType from pathlib import Path import cffi @@ -1169,7 +1169,7 @@ def name(self) -> str: """ return provider_state_get_name(self) or "" - def parameters(self) -> GeneratorType[Tuple[str, str], None, None]: + def parameters(self) -> GeneratorType[tuple[str, str], None, None]: """ Provider State parameters. @@ -7035,7 +7035,7 @@ def verifier_set_publish_options( handle: VerifierHandle, provider_version: str, build_url: str | None, - provider_tags: List[str] | None, + provider_tags: list[str] | None, provider_branch: str | None, ) -> None: """ @@ -7230,10 +7230,10 @@ def verifier_broker_source_with_selectors( # noqa: PLR0913 token: str | None, enable_pending: int, include_wip_pacts_since: datetime.date | None, - provider_tags: List[str], + provider_tags: list[str], provider_branch: str | None, - consumer_version_selectors: List[str], - consumer_version_tags: List[str], + consumer_version_selectors: list[str], + consumer_version_tags: list[str], ) -> None: """ Adds a Pact broker as a source to verify. diff --git a/src/pact/v3/generate/__init__.py b/src/pact/v3/generate/__init__.py index fb1ece3d6..ba94ca5e5 100644 --- a/src/pact/v3/generate/__init__.py +++ b/src/pact/v3/generate/__init__.py @@ -6,7 +6,7 @@ import builtins import warnings -from typing import TYPE_CHECKING, Literal, Mapping, Sequence +from typing import TYPE_CHECKING, Literal from pact.v3.generate.generator import ( Generator, @@ -15,6 +15,7 @@ from pact.v3.util import strftime_to_simple_date_format if TYPE_CHECKING: + from collections.abc import Mapping, Sequence from types import ModuleType # ruff: noqa: A001 diff --git a/src/pact/v3/generate/generator.py b/src/pact/v3/generate/generator.py index 633740be6..c686ac088 100644 --- a/src/pact/v3/generate/generator.py +++ b/src/pact/v3/generate/generator.py @@ -6,9 +6,11 @@ from abc import ABC, abstractmethod from itertools import chain -from typing import TYPE_CHECKING, Any, Mapping +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: + from collections.abc import Mapping + from pact.v3.types import GeneratorType diff --git a/src/pact/v3/interaction/_http_interaction.py b/src/pact/v3/interaction/_http_interaction.py index 08a40a9df..38e4aba52 100644 --- a/src/pact/v3/interaction/_http_interaction.py +++ b/src/pact/v3/interaction/_http_interaction.py @@ -6,7 +6,7 @@ import json from collections import defaultdict -from typing import TYPE_CHECKING, Any, Iterable, Literal +from typing import TYPE_CHECKING, Any, Literal import pact.v3.ffi from pact.v3.interaction._base import Interaction @@ -14,6 +14,8 @@ from pact.v3.match.matcher import IntegrationJSONEncoder if TYPE_CHECKING: + from collections.abc import Iterable + try: from typing import Self except ImportError: diff --git a/src/pact/v3/match/__init__.py b/src/pact/v3/match/__init__.py index 43c9446fa..247dbff62 100644 --- a/src/pact/v3/match/__init__.py +++ b/src/pact/v3/match/__init__.py @@ -48,7 +48,7 @@ import datetime as dt import warnings from decimal import Decimal -from typing import TYPE_CHECKING, Any, Literal, Mapping, Sequence, TypeVar, overload +from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload from pact.v3 import generate from pact.v3.match.matcher import ( @@ -62,6 +62,7 @@ from pact.v3.util import strftime_to_simple_date_format if TYPE_CHECKING: + from collections.abc import Mapping, Sequence from types import ModuleType from pact.v3.generate import Generator diff --git a/src/pact/v3/match/matcher.py b/src/pact/v3/match/matcher.py index 1a2499191..1716a9b5d 100644 --- a/src/pact/v3/match/matcher.py +++ b/src/pact/v3/match/matcher.py @@ -10,10 +10,10 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from itertools import chain from json import JSONEncoder -from typing import Any, Generic, Sequence, TypeVar +from typing import Any, Generic, TypeVar from pact.v3.generate.generator import Generator from pact.v3.types import UNSET, Matchable, MatcherType, Unset diff --git a/src/pact/v3/pact.py b/src/pact/v3/pact.py index a6e298539..9e69c8298 100644 --- a/src/pact/v3/pact.py +++ b/src/pact/v3/pact.py @@ -68,11 +68,7 @@ from typing import ( TYPE_CHECKING, Callable, - Dict, - Generator, - List, Literal, - Set, overload, ) @@ -90,6 +86,7 @@ from pact.v3.interaction._sync_message_interaction import SyncMessageInteraction if TYPE_CHECKING: + from collections.abc import Generator from types import TracebackType from pact.v3.interaction import Interaction @@ -145,7 +142,7 @@ def __init__( self._consumer = consumer self._provider = provider - self._interactions: Set[Interaction] = set() + self._interactions: set[Interaction] = set() self._handle: pact.v3.ffi.PactHandle = pact.v3.ffi.new_pact( consumer, provider, @@ -409,7 +406,7 @@ def interactions( @overload def verify( self, - handler: Callable[[str | bytes | None, Dict[str, str]], None], + handler: Callable[[str | bytes | None, dict[str, str]], None], kind: Literal["Async", "Sync"], *, raises: Literal[True] = True, @@ -417,19 +414,19 @@ def verify( @overload def verify( self, - handler: Callable[[str | bytes | None, Dict[str, str]], None], + handler: Callable[[str | bytes | None, dict[str, str]], None], kind: Literal["Async", "Sync"], *, raises: Literal[False], - ) -> List[InteractionVerificationError]: ... + ) -> list[InteractionVerificationError]: ... def verify( self, - handler: Callable[[str | bytes | None, Dict[str, str]], None], + handler: Callable[[str | bytes | None, dict[str, str]], None], kind: Literal["Async", "Sync"], *, raises: bool = True, - ) -> List[InteractionVerificationError] | None: + ) -> list[InteractionVerificationError] | None: """ Verify message interactions. @@ -463,7 +460,7 @@ def verify( process a message. If set to `False`, then the function will return a list of errors. """ - errors: List[InteractionVerificationError] = [] + errors: list[InteractionVerificationError] = [] for message in self.interactions(kind): request: pact.v3.ffi.MessageContents | None = None if isinstance(message, pact.v3.ffi.SynchronousMessage): diff --git a/tests/v3/compatibility_suite/conftest.py b/tests/v3/compatibility_suite/conftest.py index be72eebb6..bb5fdbab2 100644 --- a/tests/v3/compatibility_suite/conftest.py +++ b/tests/v3/compatibility_suite/conftest.py @@ -7,8 +7,9 @@ import shutil import subprocess +from collections.abc import Generator from pathlib import Path -from typing import Any, Generator, Union +from typing import Any import pytest from testcontainers.compose import DockerCompose # type: ignore[import-untyped] @@ -53,7 +54,7 @@ def broker_url(request: pytest.FixtureRequest) -> Generator[URL, Any, None]: Otherwise, the Pact broker is started in a container. The URL of the containerised broker is then returned. """ - broker_url: Union[str, None] = request.config.getoption("--broker-url") + broker_url: str | None = request.config.getoption("--broker-url") # If we have been given a broker URL, there's nothing more to do here and we # can return early. diff --git a/tests/v3/compatibility_suite/test_v3_consumer.py b/tests/v3/compatibility_suite/test_v3_consumer.py index bfc7de169..0b1e061ff 100644 --- a/tests/v3/compatibility_suite/test_v3_consumer.py +++ b/tests/v3/compatibility_suite/test_v3_consumer.py @@ -5,7 +5,7 @@ import json import logging import re -from typing import Any, Generator +from typing import TYPE_CHECKING, Any from pytest_bdd import given, parsers, scenario, then @@ -15,6 +15,9 @@ the_pact_file_for_the_test_is_generated, ) +if TYPE_CHECKING: + from collections.abc import Generator + logger = logging.getLogger(__name__) ################################################################################ diff --git a/tests/v3/compatibility_suite/test_v3_http_matching.py b/tests/v3/compatibility_suite/test_v3_http_matching.py index c7116bd8a..92b0118be 100644 --- a/tests/v3/compatibility_suite/test_v3_http_matching.py +++ b/tests/v3/compatibility_suite/test_v3_http_matching.py @@ -3,8 +3,8 @@ import pickle import re import sys +from collections.abc import Generator from pathlib import Path -from typing import Generator import pytest from pytest_bdd import ( diff --git a/tests/v3/compatibility_suite/test_v3_message_consumer.py b/tests/v3/compatibility_suite/test_v3_message_consumer.py index aeab8958e..fb39a3c9e 100644 --- a/tests/v3/compatibility_suite/test_v3_message_consumer.py +++ b/tests/v3/compatibility_suite/test_v3_message_consumer.py @@ -6,7 +6,7 @@ import json import logging import re -from typing import TYPE_CHECKING, Any, List, NamedTuple +from typing import TYPE_CHECKING, Any, NamedTuple from pytest_bdd import ( given, @@ -47,9 +47,9 @@ class ReceivedMessage(NamedTuple): class PactResult(NamedTuple): """Holder class for Pact Result objects.""" - messages: List[ReceivedMessage] + messages: list[ReceivedMessage] pact_data: dict[str, Any] | None - errors: List[InteractionVerificationError] + errors: list[InteractionVerificationError] def assert_type(expected_type: str, value: Any) -> None: # noqa: ANN401 diff --git a/tests/v3/compatibility_suite/test_v3_message_producer.py b/tests/v3/compatibility_suite/test_v3_message_producer.py index b4eab3ee5..e8087a4dc 100644 --- a/tests/v3/compatibility_suite/test_v3_message_producer.py +++ b/tests/v3/compatibility_suite/test_v3_message_producer.py @@ -8,7 +8,7 @@ import re import sys from pathlib import Path -from typing import TYPE_CHECKING, Generator +from typing import TYPE_CHECKING import pytest from pytest_bdd import ( @@ -40,6 +40,8 @@ ) if TYPE_CHECKING: + from collections.abc import Generator + from yarl import URL from pact.v3.verifier import Verifier diff --git a/tests/v3/compatibility_suite/test_v4_consumer.py b/tests/v3/compatibility_suite/test_v4_consumer.py index c6e00aef0..9af176a56 100644 --- a/tests/v3/compatibility_suite/test_v4_consumer.py +++ b/tests/v3/compatibility_suite/test_v4_consumer.py @@ -4,7 +4,7 @@ import json import logging -from typing import Any, Generator +from typing import TYPE_CHECKING, Any from pytest_bdd import given, parsers, scenario, then @@ -14,6 +14,9 @@ the_pact_file_for_the_test_is_generated, ) +if TYPE_CHECKING: + from collections.abc import Generator + logger = logging.getLogger(__name__) ################################################################################ diff --git a/tests/v3/test_http_interaction.py b/tests/v3/test_http_interaction.py index a6f170c38..77d181935 100644 --- a/tests/v3/test_http_interaction.py +++ b/tests/v3/test_http_interaction.py @@ -201,11 +201,14 @@ async def test_set_header_request_repeat( .will_respond_with(200) ) with pact.serve(raises=False) as srv: - async with aiohttp.ClientSession(srv.url) as session, session.request( - "GET", - "/", - headers=headers, - ) as resp: + async with ( + aiohttp.ClientSession(srv.url) as session, + session.request( + "GET", + "/", + headers=headers, + ) as resp, + ): assert resp.status == 500 assert len(srv.mismatches) == 1 @@ -537,10 +540,13 @@ async def test_multipart_file_request(pact: Pact, temp_dir: Path) -> None: {"Content-Type": "image/png"}, # type: ignore[arg-type] ) - async with aiohttp.ClientSession(srv.url) as session, session.post( - "/", - data=mpwriter, - ) as resp: + async with ( + aiohttp.ClientSession(srv.url) as session, + session.post( + "/", + data=mpwriter, + ) as resp, + ): assert resp.status == 200 assert await resp.read() == b"" @@ -583,9 +589,10 @@ async def test_pact_server_verbose( .with_request("GET", "/foo") .will_respond_with(200) ) - with caplog.at_level(logging.WARNING, logger="pact.v3.pact"), pact.serve( - raises=False, verbose=True - ) as srv: + with ( + caplog.at_level(logging.WARNING, logger="pact.v3.pact"), + pact.serve(raises=False, verbose=True) as srv, + ): async with aiohttp.ClientSession(srv.url) as session: async with session.get("/bar") as resp: assert resp.status == 500