From 3fdd9fe2a5a294eedab54780eeb5ae78577b71b7 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 3 Oct 2025 13:46:30 +1000 Subject: [PATCH 1/2] chore!: drop python 3.9 add 3.14 Add support for Python 3.14, and drop support for Python 3.9 as it has reached its end-of-life. BREAKING CHANGE: Python 3.9 is no longer supported. Signed-off-by: JP-Ellis --- .github/workflows/build-cli.yml | 2 +- .github/workflows/build-ffi.yml | 2 +- .github/workflows/test.yml | 18 ++--------------- conftest.py | 2 ++ docs/scripts/other.py | 4 +++- examples/http/aiohttp_and_flask/README.md | 2 +- .../http/aiohttp_and_flask/pyproject.toml | 2 +- .../http/aiohttp_and_flask/test_provider.py | 3 +-- examples/http/requests_and_fastapi/README.md | 2 +- .../http/requests_and_fastapi/provider.py | 6 +++--- .../http/requests_and_fastapi/pyproject.toml | 2 +- .../requests_and_fastapi/test_provider.py | 3 +-- examples/plugins/proto/person_pb2.py | 2 ++ examples/plugins/proto/person_pb2_grpc.py | 2 ++ examples/plugins/protobuf/__init__.py | 2 ++ pact-python-cli/pyproject.toml | 6 +++--- pact-python-ffi/pyproject.toml | 6 +++--- pact-python-ffi/tests/test_init.py | 2 ++ pyproject.toml | 15 +++++++------- src/pact/__init__.py | 2 ++ src/pact/__version__.pyi | 2 +- src/pact/_server.py | 4 ++-- src/pact/_util.py | 8 ++++++-- src/pact/interaction/__init__.py | 2 ++ src/pact/interaction/_base.py | 11 ++++------ src/pact/interaction/_http_interaction.py | 5 +---- .../interaction/_sync_message_interaction.py | 5 ++++- src/pact/pact.py | 10 +++------- src/pact/types.py | 5 ++--- src/pact/types.pyi | 3 +-- src/pact/verifier.py | 20 +++++++++++-------- tests/compatibility_suite/conftest.py | 8 ++++++-- .../compatibility_suite/test_v3_generators.py | 7 ++++++- .../test_v3_http_matching.py | 12 ++++++++--- tests/compatibility_suite/util/__init__.py | 2 +- tests/compatibility_suite/util/consumer.py | 3 +-- .../util/interaction_definition.py | 3 ++- tests/compatibility_suite/util/provider.py | 3 ++- tests/conftest.py | 2 ++ tests/test_error.py | 2 ++ tests/test_match.py | 8 ++++++-- tests/test_server.py | 2 ++ tests/test_verifier.py | 2 ++ 43 files changed, 121 insertions(+), 93 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 8be36ea14..475d979c0 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -19,7 +19,7 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: - STABLE_PYTHON_VERSION: '313' + STABLE_PYTHON_VERSION: '310' HATCH_VERBOSE: '1' FORCE_COLOR: '1' CIBW_BUILD_FRONTEND: build diff --git a/.github/workflows/build-ffi.yml b/.github/workflows/build-ffi.yml index 9da331d75..ac81f07fd 100644 --- a/.github/workflows/build-ffi.yml +++ b/.github/workflows/build-ffi.yml @@ -19,7 +19,7 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: - STABLE_PYTHON_VERSION: '39' + STABLE_PYTHON_VERSION: '310' HATCH_VERBOSE: '1' FORCE_COLOR: '1' CIBW_BUILD_FRONTEND: build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7b80b3db..ddcb1fdd7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,18 +62,11 @@ jobs: - ubuntu-latest - windows-latest 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.9' - include: - - os: macos-13 - python-version: '3.9' + - '3.14' steps: - name: Checkout code @@ -144,18 +137,11 @@ jobs: - ubuntu-latest - windows-latest 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.9' - include: - - os: macos-13 - python-version: '3.9' + - '3.14' steps: - name: Checkout code diff --git a/conftest.py b/conftest.py index 359b5e916..371bd108d 100644 --- a/conftest.py +++ b/conftest.py @@ -6,6 +6,8 @@ only be defined in this file. """ +from __future__ import annotations + import pytest diff --git a/docs/scripts/other.py b/docs/scripts/other.py index 7a309b231..eece99d5d 100644 --- a/docs/scripts/other.py +++ b/docs/scripts/other.py @@ -14,6 +14,8 @@ continue silently. """ +from __future__ import annotations + import subprocess from pathlib import Path from typing import TYPE_CHECKING @@ -77,7 +79,7 @@ def is_binary(buffer: bytes) -> bool: if str(dest_path) in EDITOR.files: continue - fi: "io.IOBase" + fi: io.IOBase with Path(source_path).open("rb") as fi: buf = fi.read(2048) diff --git a/examples/http/aiohttp_and_flask/README.md b/examples/http/aiohttp_and_flask/README.md index 0fb792f79..1a6671a16 100644 --- a/examples/http/aiohttp_and_flask/README.md +++ b/examples/http/aiohttp_and_flask/README.md @@ -36,7 +36,7 @@ Use the above links to view additional documentation within. ## Prerequisites -- Python 3.9 or higher +- Python 3.10 or higher - A dependency manager ([uv](https://docs.astral.sh/uv/) recommended, [pip](https://pip.pypa.io/en/stable/) also works) ## Running the Example diff --git a/examples/http/aiohttp_and_flask/pyproject.toml b/examples/http/aiohttp_and_flask/pyproject.toml index 6205fe030..b68d34d7c 100644 --- a/examples/http/aiohttp_and_flask/pyproject.toml +++ b/examples/http/aiohttp_and_flask/pyproject.toml @@ -5,7 +5,7 @@ name = "example-aiohttp-and-flask" description = "Example of using an aiohttp client and Flask server with Pact Python" dependencies = ["aiohttp~=3.0", "flask~=3.0", "typing-extensions~=4.0"] -requires-python = ">=3.9" +requires-python = ">=3.10" version = "1.0.0" [dependency-groups] diff --git a/examples/http/aiohttp_and_flask/test_provider.py b/examples/http/aiohttp_and_flask/test_provider.py index 22ded9c63..8c99e6f39 100644 --- a/examples/http/aiohttp_and_flask/test_provider.py +++ b/examples/http/aiohttp_and_flask/test_provider.py @@ -31,8 +31,7 @@ if TYPE_CHECKING: from pathlib import Path - - from typing_extensions import TypeAlias + from typing import TypeAlias ACTION_TYPE: TypeAlias = Literal["setup", "teardown"] diff --git a/examples/http/requests_and_fastapi/README.md b/examples/http/requests_and_fastapi/README.md index 547ae14f3..1b8a4e08a 100644 --- a/examples/http/requests_and_fastapi/README.md +++ b/examples/http/requests_and_fastapi/README.md @@ -45,7 +45,7 @@ This example is intended for software engineers and engineering managers who wan ## Prerequisites -- Python 3.9 or higher +- Python 3.10 or higher - A dependency manager ([uv](https://docs.astral.sh/uv/) recommended, [pip](https://pip.pypa.io/en/stable/) also works) ## Running the Example diff --git a/examples/http/requests_and_fastapi/provider.py b/examples/http/requests_and_fastapi/provider.py index 3e925afbf..1275a3806 100644 --- a/examples/http/requests_and_fastapi/provider.py +++ b/examples/http/requests_and_fastapi/provider.py @@ -30,7 +30,7 @@ import logging from datetime import datetime, timezone -from typing import Any, ClassVar, Optional +from typing import Any, ClassVar from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel, Field, field_validator @@ -53,8 +53,8 @@ class User(BaseModel): id: int name: str created_on: datetime = Field(default_factory=lambda: datetime.now(tz=timezone.utc)) - email: Optional[str] = None - ip_address: Optional[str] = None + email: str | None = None + ip_address: str | None = None hobbies: list[str] = Field(default_factory=list) admin: bool = False diff --git a/examples/http/requests_and_fastapi/pyproject.toml b/examples/http/requests_and_fastapi/pyproject.toml index c03a74f95..9b03fc883 100644 --- a/examples/http/requests_and_fastapi/pyproject.toml +++ b/examples/http/requests_and_fastapi/pyproject.toml @@ -5,7 +5,7 @@ name = "example-requests-and-fastapi" description = "Example of using a requests client and FastAPI server with Pact Python" dependencies = ["requests~=2.0", "fastapi~=0.0", "typing-extensions~=4.0"] -requires-python = ">=3.9" +requires-python = ">=3.10" version = "1.0.0" [dependency-groups] diff --git a/examples/http/requests_and_fastapi/test_provider.py b/examples/http/requests_and_fastapi/test_provider.py index a5e1cee9a..6c6fe9e6d 100644 --- a/examples/http/requests_and_fastapi/test_provider.py +++ b/examples/http/requests_and_fastapi/test_provider.py @@ -32,8 +32,7 @@ if TYPE_CHECKING: from pathlib import Path - - from typing_extensions import TypeAlias + from typing import TypeAlias ACTION_TYPE: TypeAlias = Literal["setup", "teardown"] diff --git a/examples/plugins/proto/person_pb2.py b/examples/plugins/proto/person_pb2.py index 3bf786c1f..fd2f14a03 100644 --- a/examples/plugins/proto/person_pb2.py +++ b/examples/plugins/proto/person_pb2.py @@ -12,6 +12,8 @@ This file is generated code. Manual changes (except for documentation improvements) will be overwritten if the file is regenerated. """ +from __future__ import annotations + from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import runtime_version as _runtime_version diff --git a/examples/plugins/proto/person_pb2_grpc.py b/examples/plugins/proto/person_pb2_grpc.py index d613482c0..1e0ebc4e1 100644 --- a/examples/plugins/proto/person_pb2_grpc.py +++ b/examples/plugins/proto/person_pb2_grpc.py @@ -11,6 +11,8 @@ This file is generated and should not be modified manually, except for documentation improvements. """ +from __future__ import annotations + from typing import Any import grpc diff --git a/examples/plugins/protobuf/__init__.py b/examples/plugins/protobuf/__init__.py index f256825a4..c230692cb 100644 --- a/examples/plugins/protobuf/__init__.py +++ b/examples/plugins/protobuf/__init__.py @@ -34,6 +34,8 @@ have a basic understanding of Pact and Protocol Buffers. """ +from __future__ import annotations + from examples.plugins.proto.person_pb2 import AddressBook, Person diff --git a/pact-python-cli/pyproject.toml b/pact-python-cli/pyproject.toml index 86932c6ee..4489c0653 100644 --- a/pact-python-cli/pyproject.toml +++ b/pact-python-cli/pyproject.toml @@ -24,12 +24,12 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.14", "Programming Language :: Python", "Topic :: Software Development :: Testing", ] -requires-python = ">=3.9" +requires-python = ">=3.10" [project.urls] "Bug Tracker" = "https://github.com/pact-foundation/pact-python/issues" @@ -128,7 +128,7 @@ requires = ["hatch-vcs", "hatchling", "packaging"] pre-install-commands = ["uv pip install --group test -e ."] [[tool.hatch.envs.test.matrix]] - python = ["3.10", "3.11", "3.12", "3.13", "3.9"] + python = ["3.10", "3.11", "3.12", "3.13", "3.14"] ################################################################################ ## PyTest Configuration diff --git a/pact-python-ffi/pyproject.toml b/pact-python-ffi/pyproject.toml index a802f8294..bc0374cd8 100644 --- a/pact-python-ffi/pyproject.toml +++ b/pact-python-ffi/pyproject.toml @@ -24,12 +24,12 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.14", "Programming Language :: Python", "Topic :: Software Development :: Testing", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = ["cffi~=2.0"] @@ -125,7 +125,7 @@ requires = ["hatch-vcs", "hatchling", "packaging", "cffi"] # platform.windows.env-vars = { PATH = "{root}/src/pact_ffi;{env:PATH}" } [[tool.hatch.envs.test.matrix]] - python = ["3.10", "3.11", "3.12", "3.13", "3.9"] + python = ["3.10", "3.11", "3.12", "3.13", "3.14"] ################################################################################ ## PyTest Configuration diff --git a/pact-python-ffi/tests/test_init.py b/pact-python-ffi/tests/test_init.py index 941658bb4..3faf3ff9f 100644 --- a/pact-python-ffi/tests/test_init.py +++ b/pact-python-ffi/tests/test_init.py @@ -11,6 +11,8 @@ are functioning as expected. """ +from __future__ import annotations + import re import pytest diff --git a/pyproject.toml b/pyproject.toml index 456936867..00d3dac85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,12 +27,12 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.14", "Programming Language :: Python", "Topic :: Software Development :: Testing", ] -requires-python = ">=3.9" +requires-python = ">=3.10" # Dependencies of Pact Python should be specified using the broadest range # compatible version unless: @@ -45,7 +45,6 @@ dependencies = [ # External dependencies "cffi~=2.0", "yarl~=1.0", - "typing-extensions~=4.0 ; python_version < '3.10'", ] [project.urls] @@ -123,6 +122,8 @@ types = [ "types-grpcio~=1.0", "types-protobuf~=6.0", "types-requests~=2.0", + # This is required for Python 3.10 support + "typing-extensions~=4.0", ] # Dependencies for v2 example and test environments @@ -224,7 +225,7 @@ requires = ["hatch-vcs", "hatchling"] pre-install-commands = ["uv pip install --group test -e ."] [[tool.hatch.envs.test.matrix]] - python = ["3.10", "3.11", "3.12", "3.13", "3.9"] + python = ["3.10", "3.11", "3.12", "3.13", "3.14"] # Test environment for running unit tests. This automatically tests against all # supported Python versions. @@ -237,7 +238,7 @@ requires = ["hatch-vcs", "hatchling"] all = ["example"] [[tool.hatch.envs.example.matrix]] - python = ["3.10", "3.11", "3.12", "3.13", "3.9"] + python = ["3.10", "3.11", "3.12", "3.13", "3.14"] [tool.hatch.envs.v2-test] features = ["v2"] @@ -250,7 +251,7 @@ requires = ["hatch-vcs", "hatchling"] test = "pytest tests/v2 {args}" [[tool.hatch.envs.v2-test.matrix]] - python = ["3.10", "3.11", "3.12", "3.13", "3.9"] + python = ["3.10", "3.11", "3.12", "3.13", "3.14"] [tool.hatch.envs.v2-example] features = ["v2"] @@ -263,7 +264,7 @@ requires = ["hatch-vcs", "hatchling"] example = "pytest examples/v2 {args}" [[tool.hatch.envs.v2-example.matrix]] - python = ["3.10", "3.11", "3.12", "3.13", "3.9"] + python = ["3.10", "3.11", "3.12", "3.13", "3.14"] ################################################################################ ## UV Workspace diff --git a/src/pact/__init__.py b/src/pact/__init__.py index 1293d1561..e67eaea59 100644 --- a/src/pact/__init__.py +++ b/src/pact/__init__.py @@ -99,6 +99,8 @@ [examples](https://pact-foundation.github.io/pact-python/examples). """ +from __future__ import annotations + from pact.__version__ import __version__, __version_tuple__ from pact.pact import Pact from pact.verifier import Verifier diff --git a/src/pact/__version__.pyi b/src/pact/__version__.pyi index a8b247d06..a019c2c56 100644 --- a/src/pact/__version__.pyi +++ b/src/pact/__version__.pyi @@ -1,4 +1,4 @@ -from typing_extensions import TypeAlias +from typing import TypeAlias __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] diff --git a/src/pact/_server.py b/src/pact/_server.py index 61f9d1a9b..e05c9dfe4 100644 --- a/src/pact/_server.py +++ b/src/pact/_server.py @@ -31,8 +31,6 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar from urllib.parse import urlparse -from typing_extensions import Self - from pact import __version__ from pact._util import find_free_port from pact.types import Message @@ -40,6 +38,8 @@ if TYPE_CHECKING: from types import TracebackType + from typing_extensions import Self + logger = logging.getLogger(__name__) diff --git a/src/pact/_util.py b/src/pact/_util.py index e34020f61..6fe8cc2e4 100644 --- a/src/pact/_util.py +++ b/src/pact/_util.py @@ -7,15 +7,19 @@ notice. """ +from __future__ import annotations + import inspect import logging import socket import warnings -from collections.abc import Callable, Mapping from contextlib import closing from functools import partial from inspect import Parameter, _ParameterKind -from typing import TypeVar +from typing import TYPE_CHECKING, TypeVar + +if TYPE_CHECKING: + from collections.abc import Callable, Mapping logger = logging.getLogger(__name__) diff --git a/src/pact/interaction/__init__.py b/src/pact/interaction/__init__.py index 5d93fa664..e5de3445d 100644 --- a/src/pact/interaction/__init__.py +++ b/src/pact/interaction/__init__.py @@ -70,6 +70,8 @@ in the interaction. """ +from __future__ import annotations + from pact.interaction._async_message_interaction import AsyncMessageInteraction from pact.interaction._base import Interaction from pact.interaction._http_interaction import HttpInteraction diff --git a/src/pact/interaction/_base.py b/src/pact/interaction/_base.py index 410fdfd35..7b0363571 100644 --- a/src/pact/interaction/_base.py +++ b/src/pact/interaction/_base.py @@ -13,7 +13,7 @@ import abc import json -from typing import TYPE_CHECKING, Any, Literal, Optional +from typing import TYPE_CHECKING, Any, Literal import pact_ffi from pact.match.matcher import IntegrationJSONEncoder @@ -21,12 +21,9 @@ if TYPE_CHECKING: from pathlib import Path - from pact.match import AbstractMatcher + from typing_extensions import Self - try: - from typing import Self - except ImportError: - from typing_extensions import Self + from pact.match import AbstractMatcher class Interaction(abc.ABC): @@ -103,7 +100,7 @@ def _interaction_part(self) -> pact_ffi.InteractionPart: def _parse_interaction_part( self, - part: Optional[Literal["Request", "Response"]], + part: Literal["Request", "Response"] | None, ) -> pact_ffi.InteractionPart: """ Convert the input into an InteractionPart. diff --git a/src/pact/interaction/_http_interaction.py b/src/pact/interaction/_http_interaction.py index 5c9496c4f..15b8a8e93 100644 --- a/src/pact/interaction/_http_interaction.py +++ b/src/pact/interaction/_http_interaction.py @@ -17,10 +17,7 @@ if TYPE_CHECKING: from collections.abc import Iterable - try: - from typing import Self - except ImportError: - from typing_extensions import Self + from typing_extensions import Self class HttpInteraction(Interaction): diff --git a/src/pact/interaction/_sync_message_interaction.py b/src/pact/interaction/_sync_message_interaction.py index 42a8453e4..92258d7b1 100644 --- a/src/pact/interaction/_sync_message_interaction.py +++ b/src/pact/interaction/_sync_message_interaction.py @@ -4,11 +4,14 @@ from __future__ import annotations -from typing_extensions import Self +from typing import TYPE_CHECKING import pact_ffi from pact.interaction._base import Interaction +if TYPE_CHECKING: + from typing_extensions import Self + class SyncMessageInteraction(Interaction): """ diff --git a/src/pact/pact.py b/src/pact/pact.py index 5efb3608b..f8aff745c 100644 --- a/src/pact/pact.py +++ b/src/pact/pact.py @@ -68,7 +68,6 @@ from pathlib import Path from typing import ( TYPE_CHECKING, - Callable, Literal, overload, ) @@ -88,15 +87,12 @@ from pact.interaction._sync_message_interaction import SyncMessageInteraction if TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Callable, Generator from types import TracebackType - from pact.interaction import Interaction + from typing_extensions import Self - try: - from typing import Self - except ImportError: - from typing_extensions import Self + from pact.interaction import Interaction logger = logging.getLogger(__name__) diff --git a/src/pact/types.py b/src/pact/types.py index 6fb083ab2..bc2f021f4 100644 --- a/src/pact/types.py +++ b/src/pact/types.py @@ -8,9 +8,8 @@ from __future__ import annotations -from typing import Any, Literal, TypedDict, Union +from typing import Any, Literal, TypeAlias, TypedDict -from typing_extensions import TypeAlias from yarl import URL Matchable: TypeAlias = Any @@ -133,7 +132,7 @@ class StateHandlerArgs(TypedDict, total=False): """ -StateHandlerUrl: TypeAlias = Union[str, URL] +StateHandlerUrl: TypeAlias = str | URL """ State handler URL signature. diff --git a/src/pact/types.pyi b/src/pact/types.pyi index f5e1ae880..7f4fc61aa 100644 --- a/src/pact/types.pyi +++ b/src/pact/types.pyi @@ -9,10 +9,9 @@ from collections.abc import Set as AbstractSet from datetime import date, datetime, time from decimal import Decimal from fractions import Fraction -from typing import Any, Literal, TypedDict +from typing import Any, Literal, TypeAlias, TypedDict from pydantic import BaseModel -from typing_extensions import TypeAlias from yarl import URL _BaseMatchable: TypeAlias = ( diff --git a/src/pact/verifier.py b/src/pact/verifier.py index 4be20dcb7..50487ca69 100644 --- a/src/pact/verifier.py +++ b/src/pact/verifier.py @@ -76,24 +76,30 @@ import json import logging import os -from collections.abc import Mapping +from collections.abc import Callable, Mapping from contextlib import nullcontext from datetime import date from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Literal, TypedDict, overload +from typing import TYPE_CHECKING, Any, Literal, TypedDict, overload -from typing_extensions import Self from yarl import URL import pact_ffi from pact._server import MessageProducer, StateCallback from pact._util import apply_args -from pact.types import UNSET, Message, MessageProducerArgs, StateHandlerArgs, Unset +from pact.types import ( + UNSET, + Message, + MessageProducerArgs, + StateHandlerArgs, + StateHandlerUrl, + Unset, +) if TYPE_CHECKING: from collections.abc import Iterable - from pact.types import StateHandlerUrl + from typing_extensions import Self logger = logging.getLogger(__name__) @@ -582,9 +588,7 @@ def state_handler( TypeError: If the handler type is invalid. """ - # A tuple is required instead of `StateHandlerUrl` for support for - # Python 3.9. This should be changed to `StateHandlerUrl` in the future. - if isinstance(handler, (str, URL)): + if isinstance(handler, StateHandlerUrl): if body is None: msg = "The `body` parameter must be a boolean when providing a URL" raise ValueError(msg) diff --git a/tests/compatibility_suite/conftest.py b/tests/compatibility_suite/conftest.py index fd85d878b..26b910c3b 100644 --- a/tests/compatibility_suite/conftest.py +++ b/tests/compatibility_suite/conftest.py @@ -5,11 +5,12 @@ submodule has been initialized before running the tests. """ +from __future__ import annotations + import shutil import subprocess -from collections.abc import Generator from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from testcontainers.compose import DockerCompose # type: ignore[import-untyped] @@ -17,6 +18,9 @@ from pact.verifier import Verifier +if TYPE_CHECKING: + from collections.abc import Generator + @pytest.fixture(scope="session", autouse=True) def _submodule_init() -> None: diff --git a/tests/compatibility_suite/test_v3_generators.py b/tests/compatibility_suite/test_v3_generators.py index 3e5ec82ef..d02aee036 100644 --- a/tests/compatibility_suite/test_v3_generators.py +++ b/tests/compatibility_suite/test_v3_generators.py @@ -1,8 +1,10 @@ """Test of V3 generators.""" +from __future__ import annotations + import logging import re -from collections.abc import Callable +from typing import TYPE_CHECKING import pytest import requests @@ -18,6 +20,9 @@ from tests.compatibility_suite.util import parse_horizontal_table from tests.compatibility_suite.util.interaction_definition import InteractionDefinition +if TYPE_CHECKING: + from collections.abc import Callable + logger = logging.getLogger(__name__) diff --git a/tests/compatibility_suite/test_v3_http_matching.py b/tests/compatibility_suite/test_v3_http_matching.py index 1d664b598..6de96aa2b 100644 --- a/tests/compatibility_suite/test_v3_http_matching.py +++ b/tests/compatibility_suite/test_v3_http_matching.py @@ -1,9 +1,10 @@ """Matching HTTP parts (request or response) feature tests.""" +from __future__ import annotations + import re import sys -from collections.abc import Generator -from pathlib import Path +from typing import TYPE_CHECKING import pytest from pytest_bdd import ( @@ -15,12 +16,17 @@ ) from pact import Pact -from pact.verifier import Verifier from tests.compatibility_suite.util.interaction_definition import ( InteractionDefinition, ) from tests.compatibility_suite.util.provider import Provider +if TYPE_CHECKING: + from collections.abc import Generator + from pathlib import Path + + from pact.verifier import Verifier + ################################################################################ ## Scenarios ################################################################################ diff --git a/tests/compatibility_suite/util/__init__.py b/tests/compatibility_suite/util/__init__.py index 440cfc262..e227e9186 100644 --- a/tests/compatibility_suite/util/__init__.py +++ b/tests/compatibility_suite/util/__init__.py @@ -177,7 +177,7 @@ def parse_horizontal_table(content: list[list[str]]) -> list[dict[str, str]]: msg = f"Expected at least two rows in the table, got {len(content)}" raise ValueError(msg) - return [dict(zip(content[0], row)) for row in content[1:]] + return [dict(zip(content[0], row, strict=True)) for row in content[1:]] def parse_vertical_table(content: list[list[str]]) -> dict[str, str]: diff --git a/tests/compatibility_suite/util/consumer.py b/tests/compatibility_suite/util/consumer.py index a5fa26b80..237a9b804 100644 --- a/tests/compatibility_suite/util/consumer.py +++ b/tests/compatibility_suite/util/consumer.py @@ -7,12 +7,11 @@ import json import logging import re -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypeGuard import pytest import requests from pytest_bdd import given, parsers, then, when -from typing_extensions import TypeGuard from yarl import URL from pact import Pact diff --git a/tests/compatibility_suite/util/interaction_definition.py b/tests/compatibility_suite/util/interaction_definition.py index 1b91e89a0..5b4e69b2c 100644 --- a/tests/compatibility_suite/util/interaction_definition.py +++ b/tests/compatibility_suite/util/interaction_definition.py @@ -17,7 +17,6 @@ from xml.etree import ElementTree as ET from multidict import MultiDict -from typing_extensions import Self from yarl import URL from pact.interaction import HttpInteraction, Interaction @@ -32,6 +31,8 @@ from http.server import SimpleHTTPRequestHandler from pathlib import Path + from typing_extensions import Self + from pact.interaction import Interaction from pact.pact import Pact from pact.types import Message diff --git a/tests/compatibility_suite/util/provider.py b/tests/compatibility_suite/util/provider.py index bc78fb4b4..9c42c4940 100644 --- a/tests/compatibility_suite/util/provider.py +++ b/tests/compatibility_suite/util/provider.py @@ -31,7 +31,6 @@ import requests from multidict import CIMultiDict from pytest_bdd import given, parsers, then, when -from typing_extensions import Self from yarl import URL import pact_cli @@ -53,6 +52,8 @@ from pathlib import Path from types import TracebackType + from typing_extensions import Self + from pact.types import Message from pact.verifier import Verifier diff --git a/tests/conftest.py b/tests/conftest.py index 9e99b3a75..f41ae4845 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,8 @@ directory. """ +from __future__ import annotations + import json import tempfile from pathlib import Path diff --git a/tests/test_error.py b/tests/test_error.py index a49ea5cb1..b945a7b26 100644 --- a/tests/test_error.py +++ b/tests/test_error.py @@ -2,6 +2,8 @@ Error handling and mismatch tests. """ +from __future__ import annotations + import re import aiohttp diff --git a/tests/test_match.py b/tests/test_match.py index a2bfb03cc..f533fd6ab 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -2,18 +2,19 @@ Example test to show usage of matchers (and generators by extension). """ +from __future__ import annotations + import logging import re 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 NoReturn +from typing import TYPE_CHECKING, NoReturn import requests from flask import Flask, Response, make_response @@ -21,6 +22,9 @@ from pact import Pact, Verifier, generate, match +if TYPE_CHECKING: + from collections.abc import Generator + logger = logging.getLogger(__name__) diff --git a/tests/test_server.py b/tests/test_server.py index dab2fb5ae..3526ac1d5 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -2,6 +2,8 @@ Tests for `pact._server` module. """ +from __future__ import annotations + import json from unittest.mock import MagicMock diff --git a/tests/test_verifier.py b/tests/test_verifier.py index 18580eb42..0de481b0a 100644 --- a/tests/test_verifier.py +++ b/tests/test_verifier.py @@ -6,6 +6,8 @@ that is handled by the compatibility suite. """ +from __future__ import annotations + import re from pathlib import Path From 05baf4d3be4c16fd2c226e7a0691ba0c1e323c2b Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 6 Oct 2025 12:07:36 +1100 Subject: [PATCH 2/2] chore(ci): disable 3.14 tests using pydantic Pydantic 3.12 is going to be released shortly, but until then, any test that depends on Pydantic will not pass CI. Signed-off-by: JP-Ellis --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ddcb1fdd7..5ff75fff9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,6 +99,8 @@ jobs: run: hatch run test.py${{ matrix.python-version }}:test --junit-xml=junit.xml - name: Run tests (v2) + # Temporary workaround until Pydantic 3.12 is released with Python 3.14 support + if: matrix.python-version != '3.14' run: hatch run v2-test.py${{ matrix.python-version }}:test --junit-xml=v2-junit.xml - name: Run tests (CLI) @@ -141,7 +143,9 @@ jobs: - '3.11' - '3.12' - '3.13' - - '3.14' + # Temporarily excluded until Pydantic 3.12 is released with Python + # 3.14 support + # - '3.14' steps: - name: Checkout code