Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add Python 3.13, drop 3.8 #796

Merged
merged 1 commit into from
Oct 9, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- master

env:
STABLE_PYTHON_VERSION: "3.12"
STABLE_PYTHON_VERSION: "3.13"
FORCE_COLOR: "1"
HATCH_VERBOSE: "1"

Expand Down
14 changes: 5 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
24 changes: 15 additions & 9 deletions docs/scripts/other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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())
7 changes: 5 additions & 2 deletions examples/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions examples/src/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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}")
Expand Down
10 changes: 5 additions & 5 deletions examples/src/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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/<int:uid>")
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.

Expand All @@ -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,
Expand All @@ -129,7 +129,7 @@ def create_user() -> Response:


@app.route("/users/<int:uid>", 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]
Expand Down
6 changes: 3 additions & 3 deletions examples/src/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, Union
from typing import Any


class Filesystem:
Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand Down
7 changes: 4 additions & 3 deletions examples/tests/test_00_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
9 changes: 6 additions & 3 deletions examples/tests/test_01_provider_fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")


Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions examples/tests/test_01_provider_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion examples/tests/test_02_message_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions examples/tests/test_03_message_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,15 +38,15 @@
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",
"contents": "Hello world!",
}


def generate_read_message() -> Dict[str, str]:
def generate_read_message() -> dict[str, str]:
return {
"action": "READ",
"path": "test.txt",
Expand Down
3 changes: 2 additions & 1 deletion examples/tests/v3/basic_flask_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading