From d1724bfca8a7e1ff00881dea95e3a3c8bee3acde Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 3 Nov 2023 14:46:23 +1100 Subject: [PATCH] chore: fix ruff lints Due to an upstream bug in `ruff`, all Python files were inadvertently ignored instead of just the `v2` code. As a result, lint errors have been accummulating. Signed-off-by: JP-Ellis --- pact/v3/ffi.py | 29 +++++++++++------- pact/v3/pact.py | 46 +++++++++++++++++----------- pyproject.toml | 39 +++++++++++++++++++++++- tests/v3/__init__.py | 3 ++ tests/v3/test_ffi.py | 2 +- tests/v3/test_http_interaction.py | 50 ++++++++----------------------- tests/v3/test_pact.py | 17 ++++++----- 7 files changed, 110 insertions(+), 76 deletions(-) diff --git a/pact/v3/ffi.py b/pact/v3/ffi.py index b33b30c0e6..88d0f0e571 100644 --- a/pact/v3/ffi.py +++ b/pact/v3/ffi.py @@ -90,8 +90,9 @@ from ._ffi import ffi, lib # type: ignore[import] if TYPE_CHECKING: - import cffi from pathlib import Path + + import cffi from typing_extensions import Self # The follow types are classes defined in the Rust code. Ultimately, a Python @@ -814,6 +815,8 @@ class OwnedString(str): in most cases. """ + __slots__ = ("_ptr", "_string") + def __new__(cls, ptr: cffi.FFI.CData) -> Self: """ Create a new Owned String. @@ -3000,7 +3003,7 @@ def pact_message_iter_next(iter: PactMessageIterator) -> Message: ptr = lib.pactffi_pact_message_iter_next(iter._ptr) if ptr == ffi.NULL: raise StopIteration - raise NotImplementedError() + raise NotImplementedError return Message(ptr) @@ -3014,7 +3017,7 @@ def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMes ptr = lib.pactffi_pact_sync_message_iter_next(iter._ptr) if ptr == ffi.NULL: raise StopIteration - raise NotImplementedError() + raise NotImplementedError return SynchronousMessage(ptr) @@ -3038,7 +3041,7 @@ def pact_sync_http_iter_next(iter: PactSyncHttpIterator) -> SynchronousHttp: ptr = lib.pactffi_pact_sync_http_iter_next(iter._ptr) if ptr == ffi.NULL: raise StopIteration - raise NotImplementedError() + raise NotImplementedError return SynchronousHttp(ptr) @@ -3062,7 +3065,7 @@ def pact_interaction_iter_next(iter: PactInteractionIterator) -> PactInteraction ptr = lib.pactffi_pact_interaction_iter_next(iter._ptr) if ptr == ffi.NULL: raise StopIteration - raise NotImplementedError() + raise NotImplementedError return PactInteraction(ptr) @@ -5693,7 +5696,7 @@ def pact_handle_get_sync_message_iter(pact: PactHandle) -> PactSyncMessageIterat ('\0') bytes. """ return PactSyncMessageIterator( - lib.pactffi_pact_handle_get_sync_message_iter(pact._ref) + lib.pactffi_pact_handle_get_sync_message_iter(pact._ref), ) @@ -5929,6 +5932,9 @@ def pact_handle_write_file( This function should be called if all the consumer tests have passed. Args: + pact: + Handle to a Pact model. + directory: The directory to write the file to. If `None`, the current working directory is used. @@ -5947,9 +5953,9 @@ def pact_handle_write_file( return if ret == 1: msg = f"The function panicked while writing {pact} to {directory}." - elif ret == 2: + elif ret == 2: # noqa: PLR2004 msg = f"The pact file was not able to be written for {pact}." - elif ret == 3: + elif ret == 3: # noqa: PLR2004 msg = f"The pact for {pact} was not found." else: msg = f"Unknown error writing {pact} to {directory}." @@ -6616,6 +6622,9 @@ def using_plugin( `pactffi_using_plugin`](https://docs.rs/pact_ffi/0.4.9/pact_ffi/?search=pactffi_using_plugin) Args: + pact: + Handle to a Pact model. + plugin_name: Name of the plugin to use. @@ -6632,9 +6641,9 @@ def using_plugin( return if ret == 1: msg = f"A general panic was caught: {get_error_message()}" - elif ret == 2: + elif ret == 2: # noqa: PLR2004 msg = f"Failed to load the plugin {plugin_name}." - elif ret == 3: + elif ret == 3: # noqa: PLR2004 msg = f"The Pact handle {pact} is invalid." else: msg = f"There was an unknown error loading the plugin {plugin_name}." diff --git a/pact/v3/pact.py b/pact/v3/pact.py index 19222d5b23..a2536ca54b 100644 --- a/pact/v3/pact.py +++ b/pact/v3/pact.py @@ -1108,6 +1108,13 @@ def serve( transport_config: Configuration for the transport. This is specific to the transport being used and should be a JSON string. + + raises: Whether to raise an exception if there are mismatches + between the Pact and the server. If set to `False`, then the + mismatches must be handled manually. + + Returns: + A [`PactServer`][pact.v3.pact.PactServer] instance. """ return PactServer( self._handle, @@ -1139,21 +1146,23 @@ def messages(self) -> pact.v3.ffi.PactMessageIterator: return pact.v3.ffi.pact_handle_get_message_iter(self._handle) @overload - def interactions(self, type: Literal["HTTP"]) -> pact.v3.ffi.PactSyncHttpIterator: + def interactions(self, kind: Literal["HTTP"]) -> pact.v3.ffi.PactSyncHttpIterator: ... @overload def interactions( - self, type: Literal["Sync"] + self, + kind: Literal["Sync"], ) -> pact.v3.ffi.PactSyncMessageIterator: ... @overload - def interactions(self, type: Literal["Async"]) -> pact.v3.ffi.PactMessageIterator: + def interactions(self, kind: Literal["Async"]) -> pact.v3.ffi.PactMessageIterator: ... def interactions( - self, type: str = "HTTP" + self, + kind: str = "HTTP", ) -> ( pact.v3.ffi.PactSyncHttpIterator | pact.v3.ffi.PactSyncMessageIterator @@ -1162,30 +1171,26 @@ def interactions( """ Return an iterator over the Pact's interactions. - The type is used to specify the kind of interactions that will be - iterated over. If `"All"` is specified (the default), then all - interactions will be iterated over. + The kind is used to specify the type of interactions that will be + iterated over. """ - # TODO: The FFI does not have a way to iterate over all interactions, unless - # you have a pointer to the pact. See - # pact-foundation/pact-reference#333. - # if type == "All": - # return pact.v3.ffi.pact_model_interaction_iterator(self._handle) - if type == "HTTP": + # TODO(JP-Ellis): Add an iterator for `All` interactions. + # https://github.com/pact-foundation/pact-python/issues/451 + if kind == "HTTP": return pact.v3.ffi.pact_handle_get_sync_http_iter(self._handle) - if type == "Sync": + if kind == "Sync": return pact.v3.ffi.pact_handle_get_sync_message_iter(self._handle) - if type == "Async": + if kind == "Async": return pact.v3.ffi.pact_handle_get_message_iter(self._handle) - msg = f"Unknown interaction type: {type}" + msg = f"Unknown interaction type: {kind}" raise ValueError(msg) def write_file( self, - directory: Path | str = Path.cwd(), + directory: Path | str | None = None, *, overwrite: bool = False, - ): + ) -> None: """ Write out the pact to a file. @@ -1204,6 +1209,8 @@ def write_file( exists. Otherwise, the contents of the file will be merged with the existing file. """ + if directory is None: + directory = Path.cwd() pact.v3.ffi.pact_handle_write_file( self._handle, directory, @@ -1259,6 +1266,9 @@ def __init__( # noqa: PLR0913 transport_config: Configuration for the transport. This is specific to the transport being used and should be a JSON string. + + raises: Whether or not to raise an exception if the server is not + matched upon exit. """ self._host = host self._port = port diff --git a/pyproject.toml b/pyproject.toml index 80b9769593..d2b18c90d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,9 +175,46 @@ ignore = [ "D212", # Multi-line docstring summary must start at the first line "ANN101", # `self` must be typed "ANN102", # `cls` must be typed + "FIX002", # Forbid TODO in comments ] -extend-exclude = ["tests/*.py", "pact/*.py"] +# TODO: Remove the explicity extend-exclude once astral-sh/ruff#6262 is fixed. +# https://github.com/pact-foundation/pact-python/issues/458 +extend-exclude = [ + # "pact/*.py", + # "pact/cli/*.py", + # "tests/*.py", + # "tests/cli/*.py", + "pact/__init__.py", + "pact/__version__.py", + "pact/broker.py", + "pact/cli/*.py", + "pact/constants.py", + "pact/consumer.py", + "pact/http_proxy.py", + "pact/matchers.py", + "pact/message_consumer.py", + "pact/message_pact.py", + "pact/message_provider.py", + "pact/pact.py", + "pact/provider.py", + "pact/verifier.py", + "pact/verify_wrapper.py", + "tests/__init__.py", + "tests/cli/*.py", + "tests/conftest.py", + "tests/test_broker.py", + "tests/test_constants.py", + "tests/test_consumer.py", + "tests/test_http_proxy.py", + "tests/test_matchers.py", + "tests/test_message_consumer.py", + "tests/test_message_pact.py", + "tests/test_message_provider.py", + "tests/test_pact.py", + "tests/test_verifier.py", + "tests/test_verify_wrapper.py", +] [tool.ruff.pyupgrade] keep-runtime-typing = true diff --git a/tests/v3/__init__.py b/tests/v3/__init__.py index e69de29bb2..bcfbefdf79 100644 --- a/tests/v3/__init__.py +++ b/tests/v3/__init__.py @@ -0,0 +1,3 @@ +""" +Pact Python v3 tests. +""" diff --git a/tests/v3/test_ffi.py b/tests/v3/test_ffi.py index 912411c53d..53e00361da 100644 --- a/tests/v3/test_ffi.py +++ b/tests/v3/test_ffi.py @@ -65,5 +65,5 @@ def test_owned_string() -> None: ( "-----END CERTIFICATE-----\n", "-----END CERTIFICATE-----\r\n", - ) + ), ) diff --git a/tests/v3/test_http_interaction.py b/tests/v3/test_http_interaction.py index 5894c04895..fa5d6cbe09 100644 --- a/tests/v3/test_http_interaction.py +++ b/tests/v3/test_http_interaction.py @@ -105,11 +105,7 @@ async def test_with_header_request( ) with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: - async with session.request( - "GET", - "/", - headers=headers, - ) as resp: + async with session.request("GET", "/", headers=headers) as resp: assert resp.status == 200 @@ -134,10 +130,7 @@ async def test_with_header_response( ) with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: - async with session.request( - "GET", - "/", - ) as resp: + async with session.request("GET", "/") as resp: assert resp.status == 200 response_headers = [(h.lower(), v) for h, v in resp.headers.items()] for header, value in headers: @@ -182,11 +175,7 @@ async def test_set_header_request( ) with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: - async with session.request( - "GET", - "/", - headers=headers, - ) as resp: + async with session.request("GET", "/", headers=headers) as resp: assert resp.status == 200 @@ -205,11 +194,7 @@ async def test_set_header_request_repeat( ) with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: - async with session.request( - "GET", - "/", - headers=headers, - ) as resp: + async with session.request("GET", "/", headers=headers) as resp: assert resp.status == 500 @@ -233,10 +218,7 @@ async def test_set_header_response( ) with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: - async with session.request( - "GET", - "/", - ) as resp: + async with session.request("GET", "/") as resp: assert resp.status == 200 response_headers = [(h.lower(), v) for h, v in resp.headers.items()] for header, value in headers: @@ -258,11 +240,7 @@ async def test_set_header_response_repeat( ) with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: - async with session.request( - "GET", - "/", - headers=headers, - ) as resp: + async with session.request("GET", "/", headers=headers) as resp: assert resp.status == 200 response_headers = [(h.lower(), v) for h, v in resp.headers.items()] assert ("x-test", "2") in response_headers @@ -309,10 +287,7 @@ async def test_with_query_parameter_request( with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: url = srv.url.with_query(query) - async with session.request( - "GET", - url.path_qs, - ) as resp: + async with session.request("GET", url.path_qs) as resp: assert resp.status == 200 @@ -327,10 +302,7 @@ async def test_with_query_parameter_dict(pact: Pact) -> None: with pact.serve() as srv: async with aiohttp.ClientSession(srv.url) as session: url = srv.url.with_query({"test": "true", "foo": "bar"}) - async with session.request( - "GET", - url.path_qs, - ) as resp: + async with session.request("GET", url.path_qs) as resp: assert resp.status == 200 @@ -508,12 +480,14 @@ async def test_multipart_file_request(pact: Pact, temp_dir: Path) -> None: with pact.serve() as srv, aiohttp.MultipartWriter() as mpwriter: mpwriter.append( fpy.open("rb"), - # TODO: Remove type ignore once aio-libs/aiohttp#7741 is resolved + # TODO(JP-Ellis): Remove type ignore once aio-libs/aiohttp#7741 is resolved + # https://github.com/pact-foundation/pact-python/issues/450 {"Content-Type": "text/x-python"}, # type: ignore[arg-type] ) mpwriter.append( fpng.open("rb"), - # TODO: Remove type ignore once aio-libs/aiohttp#7741 is resolved + # TODO(JP-Ellis): Remove type ignore once aio-libs/aiohttp#7741 is resolved + # https://github.com/pact-foundation/pact-python/issues/450 {"Content-Type": "image/png"}, # type: ignore[arg-type] ) diff --git a/tests/v3/test_pact.py b/tests/v3/test_pact.py index 4759be6153..64fd520a60 100644 --- a/tests/v3/test_pact.py +++ b/tests/v3/test_pact.py @@ -3,13 +3,16 @@ """ from __future__ import annotations + import json -from pathlib import Path -from typing import Literal +from typing import TYPE_CHECKING, Literal import pytest from pact.v3 import Pact +if TYPE_CHECKING: + from pathlib import Path + @pytest.fixture() def pact() -> Pact: @@ -84,9 +87,8 @@ def test_interactions_iter( for _interaction in interactions: # This should be an empty list and therefore the error should never be # raised. - raise RuntimeError("Should not be reached") - else: - print("Ok") + msg = "Should not be reached" + raise RuntimeError(msg) def test_messages(pact: Pact) -> None: @@ -95,9 +97,8 @@ def test_messages(pact: Pact) -> None: for _message in messages: # This should be an empty list and therefore the error should never be # raised. - raise RuntimeError("Should not be reached") - else: - print("Ok") + msg = "Should not be reached" + raise RuntimeError(msg) def test_write_file(pact: Pact, temp_dir: Path) -> None: