diff --git a/mocket/__init__.py b/mocket/__init__.py index cd9437e9..e4bc0084 100644 --- a/mocket/__init__.py +++ b/mocket/__init__.py @@ -31,4 +31,4 @@ "FakeSSLContext", ) -__version__ = "3.13.5" +__version__ = "3.13.6" diff --git a/mocket/mocket.py b/mocket/mocket.py index a01a7b46..c9e6e204 100644 --- a/mocket/mocket.py +++ b/mocket/mocket.py @@ -4,7 +4,7 @@ import itertools import os from pathlib import Path -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING, Any, ClassVar import mocket.inject from mocket.recording import MocketRecordStorage @@ -99,12 +99,12 @@ def reset(cls) -> None: cls._record_storage = None @classmethod - def last_request(cls): + def last_request(cls) -> Any: if cls.has_requests(): return cls._requests[-1] @classmethod - def request_list(cls): + def request_list(cls) -> list[Any]: return cls._requests @classmethod diff --git a/mocket/mocks/mockhttp.py b/mocket/mocks/mockhttp.py index 245a11af..3db6a65d 100644 --- a/mocket/mocks/mockhttp.py +++ b/mocket/mocks/mockhttp.py @@ -88,7 +88,7 @@ def __init__(self, body="", status=200, headers=None): self.data = self.get_protocol_data() + self.body - def get_protocol_data(self, str_format_fun_name="capitalize"): + def get_protocol_data(self, str_format_fun_name: str = "capitalize") -> bytes: status_line = f"HTTP/1.1 {self.status} {STATUS[self.status]}" header_lines = CRLF.join( ( diff --git a/mocket/plugins/httpretty/__init__.py b/mocket/plugins/httpretty/__init__.py index fac61840..97a2c3a4 100644 --- a/mocket/plugins/httpretty/__init__.py +++ b/mocket/plugins/httpretty/__init__.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + from mocket import mocketize from mocket.async_mocket import async_mocketize from mocket.compat import ENCODING @@ -7,33 +9,35 @@ from mocket.mockhttp import Response as MocketHttpResponse -def httprettifier_headers(headers): +def httprettifier_headers(headers: Dict[str, str]) -> Dict[str, str]: return {k.lower().replace("_", "-"): v for k, v in headers.items()} class Request(MocketHttpRequest): @property - def body(self): - return super().body.encode(ENCODING) + def body(self) -> bytes: + return super().body.encode(ENCODING) # type: ignore[no-any-return] @property - def headers(self): + def headers(self) -> Dict[str, str]: return httprettifier_headers(super().headers) class Response(MocketHttpResponse): - def get_protocol_data(self, str_format_fun_name="lower"): + headers: Dict[str, str] + + def get_protocol_data(self, str_format_fun_name: str = "lower") -> bytes: if "server" in self.headers and self.headers["server"] == "Python/Mocket": self.headers["server"] = "Python/HTTPretty" - return super().get_protocol_data(str_format_fun_name=str_format_fun_name) + return super().get_protocol_data(str_format_fun_name=str_format_fun_name) # type: ignore[no-any-return] - def set_base_headers(self): + def set_base_headers(self) -> None: super().set_base_headers() self.headers = httprettifier_headers(self.headers) original_set_base_headers = set_base_headers - def set_extra_headers(self, headers): + def set_extra_headers(self, headers: Dict[str, str]) -> None: self.headers.update(headers) @@ -60,17 +64,17 @@ class Entry(MocketHttpEntry): def register_uri( - method, - uri, - body="HTTPretty :)", - adding_headers=None, - forcing_headers=None, - status=200, - responses=None, - match_querystring=False, - priority=0, - **headers, -): + method: str, + uri: str, + body: str = "HTTPretty :)", + adding_headers: Optional[Dict[str, str]] = None, + forcing_headers: Optional[Dict[str, str]] = None, + status: int = 200, + responses: Any = None, + match_querystring: bool = False, + priority: int = 0, + **headers: str, +) -> None: headers = httprettifier_headers(headers) if adding_headers is not None: @@ -81,9 +85,9 @@ def register_uri( def force_headers(self): self.headers = httprettifier_headers(forcing_headers) - Response.set_base_headers = force_headers + Response.set_base_headers = force_headers # type: ignore[method-assign] else: - Response.set_base_headers = Response.original_set_base_headers + Response.set_base_headers = Response.original_set_base_headers # type: ignore[method-assign] if responses: Entry.register(method, uri, *responses) @@ -110,7 +114,7 @@ def __getattr__(self, name): HTTPretty = MocketHTTPretty() -HTTPretty.register_uri = register_uri +HTTPretty.register_uri = register_uri # type: ignore[attr-defined] httpretty = HTTPretty __all__ = ( diff --git a/mocket/utils.py b/mocket/utils.py index 60ddd9f2..6180ae3f 100644 --- a/mocket/utils.py +++ b/mocket/utils.py @@ -2,12 +2,34 @@ import binascii import contextlib -from typing import Callable +from typing import Any, Callable, Protocol, TypeVar, overload import decorator +from typing_extensions import ParamSpec from mocket.compat import decode_from_bytes, encode_to_bytes +_P = ParamSpec("_P") +_R = TypeVar("_R") + + +class MocketizeDecorator(Protocol): + """ + This is a generic decorator signature, currently applicable to get_mocketize. + + Decorators can be used as: + 1. A function that transforms func (the parameter) into func1 (the returned object). + 2. A function that takes keyword arguments and returns 1. + """ + + @overload + def __call__(self, func: Callable[_P, _R], /) -> Callable[_P, _R]: ... + + @overload + def __call__( + self, **kwargs: Any + ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... + def hexdump(binary_string: bytes) -> str: r""" @@ -30,11 +52,11 @@ def hexload(string: str) -> bytes: raise ValueError from e -def get_mocketize(wrapper_: Callable) -> Callable: +def get_mocketize(wrapper_: Callable) -> MocketizeDecorator: # trying to support different versions of `decorator` with contextlib.suppress(TypeError): - return decorator.decorator(wrapper_, kwsyntax=True) # type: ignore[call-arg,unused-ignore] - return decorator.decorator(wrapper_) + return decorator.decorator(wrapper_, kwsyntax=True) # type: ignore[return-value, call-arg, unused-ignore] + return decorator.decorator(wrapper_) # type: ignore[return-value] __all__ = ( diff --git a/pyproject.toml b/pyproject.toml index 6872741f..5349ad6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ description = "Socket Mock Framework - for all kinds of socket animals, web-clie readme = { file = "README.rst", content-type = "text/x-rst" } license = { file = "LICENSE" } authors = [{ name = "Giorgio Salluzzo", email = "giorgio.salluzzo@gmail.com" }] -urls = { github = "https://github.com/mindflayer/python-mocket" } classifiers = [ "Development Status :: 6 - Mature", "Intended Audience :: Developers", @@ -35,6 +34,10 @@ dependencies = [ ] dynamic = ["version"] +[project.urls] +Homepage = "https://github.com/mindflayer/python-mocket" +Repository = "https://github.com/mindflayer/python-mocket" + [project.optional-dependencies] test = [ "pre-commit", @@ -57,6 +60,7 @@ test = [ "wait-for-it", "mypy", "types-decorator", + "types-requests", ] speedups = [ "xxhash;platform_python_implementation=='CPython'", @@ -123,6 +127,9 @@ files = [ "mocket/exceptions.py", "mocket/compat.py", "mocket/utils.py", + "mocket/plugins/httpretty/__init__.py", + "tests/test_httpretty.py", + "tests/test_utils.py", # "tests/" ] strict = true @@ -140,3 +147,11 @@ disable_error_code = ["no-untyped-def"] # enable this once full type-coverage is [[tool.mypy.overrides]] module = "tests.*" disable_error_code = ['type-arg', 'no-untyped-def'] + +[[tool.mypy.overrides]] +module = "mocket.plugins.*" +disallow_subclassing_any = false # mypy doesn't support dynamic imports + +[[tool.mypy.overrides]] +module = "tests.test_httpretty" +disallow_untyped_decorators = true diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..d3b5eba7 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,31 @@ +from typing import Callable +from unittest import TestCase +from unittest.mock import NonCallableMock, patch + +import decorator + +from mocket.utils import get_mocketize + + +def mock_decorator(func: Callable[[], None]) -> None: + return func() + + +class GetMocketizeTestCase(TestCase): + @patch.object(decorator, "decorator") + def test_get_mocketize_with_kwsyntax(self, dec: NonCallableMock) -> None: + get_mocketize(mock_decorator) + dec.assert_called_once_with(mock_decorator, kwsyntax=True) + + @patch.object(decorator, "decorator") + def test_get_mocketize_without_kwsyntax(self, dec: NonCallableMock) -> None: + dec.side_effect = [ + TypeError("kwsyntax is not supported in this version of decorator"), + mock_decorator, + ] + + get_mocketize(mock_decorator) + # First time called with kwsyntax=True, which failed with TypeError + dec.call_args_list[0].assert_compare_to((mock_decorator,), {"kwsyntax": True}) + # Second time without kwsyntax, which succeeds + dec.call_args_list[1].assert_compare_to((mock_decorator,)) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy