Skip to content

Commit

Permalink
refactor: rework Entry, Request and Response classes
Browse files Browse the repository at this point in the history
  • Loading branch information
betaboon committed Nov 30, 2024
1 parent 9cfad4c commit a69d07e
Show file tree
Hide file tree
Showing 40 changed files with 1,543 additions and 867 deletions.
53 changes: 42 additions & 11 deletions mocket/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,51 @@
from mocket.async_mocket import async_mocketize
from mocket.entry import MocketEntry
from mocket.mocket import Mocket
from mocket.mocketizer import Mocketizer, mocketize
from mocket.ssl.context import MocketSSLContext

# NOTE this is here for backwards-compat to keep old import-paths working
from mocket.ssl.context import MocketSSLContext as FakeSSLContext
from mocket.bytes import (
MocketBytesEntry,
MocketBytesRequest,
MocketBytesResponse,
)
from mocket.compat import FakeSSLContext, MocketEntry
from mocket.core.async_mocket import async_mocketize
from mocket.core.exceptions import MocketException, StrictMocketException
from mocket.core.mocket import Mocket
from mocket.core.mocketizer import Mocketizer, mocketize
from mocket.core.socket import MocketSocket
from mocket.core.ssl.context import MocketSSLContext
from mocket.core.ssl.socket import MocketSSLSocket
from mocket.http import (
MocketHttpEntry,
MocketHttpMethod,
MocketHttpRequest,
MocketHttpResponse,
)
from mocket.redis import (
MocketRedisEntry,
MocketRedisRequest,
MocketRedisResponse,
)

__all__ = (
__all__ = [
"async_mocketize",
"mocketize",
"Mocket",
"MocketEntry",
"Mocketizer",
"MocketBytesEntry",
"MocketBytesRequest",
"MocketBytesResponse",
"MocketHttpEntry",
"MocketHttpMethod",
"MocketHttpRequest",
"MocketHttpResponse",
"MocketRedisEntry",
"MocketRedisRequest",
"MocketRedisResponse",
"MocketSocket",
"MocketSSLSocket",
"MocketSSLContext",
"MocketException",
"StrictMocketException",
# NOTE this is here for backwards-compat to keep old import-paths working
"FakeSSLContext",
)
"MocketEntry",
]

__version__ = "3.13.2"
26 changes: 5 additions & 21 deletions mocket/async_mocket.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
from mocket.mocketizer import Mocketizer
from mocket.utils import get_mocketize
from mocket.core.async_mocket import async_mocketize


async def wrapper(
test,
truesocket_recording_dir=None,
strict_mode=False,
strict_mode_allowed=None,
*args,
**kwargs,
):
async with Mocketizer.factory(
test, truesocket_recording_dir, strict_mode, strict_mode_allowed, args
):
return await test(*args, **kwargs)


async_mocketize = get_mocketize(wrapper_=wrapper)


__all__ = ("async_mocketize",)
# NOTE this is here for backwards-compat to keep old import-paths working
__all__ = [
"async_mocketize",
]
63 changes: 63 additions & 0 deletions mocket/bytes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

from typing_extensions import Self

from mocket.core.entry import MocketBaseEntry, MocketBaseRequest, MocketBaseResponse
from mocket.core.types import Address


class MocketBytesRequest(MocketBaseRequest):
def __init__(self) -> None:
self._data = b""

@property
def data(self) -> bytes:
return self._data

@classmethod
def from_data(cls: type[Self], data: bytes) -> Self:
request = cls()
request._data = data
return request


class MocketBytesResponse(MocketBaseResponse):
def __init__(self, data: bytes | str | bool) -> None:
if isinstance(data, str):
data = data.encode()
elif isinstance(data, bool):
data = bytes(data)
self._data = data

@property
def data(self) -> bytes:
return self._data


class MocketBytesEntry(MocketBaseEntry):
request_cls = MocketBytesRequest
response_cls = MocketBytesResponse

def __init__(
self,
address: Address,
responses: list[MocketBytesResponse | Exception | bytes | str | bool]
| MocketBytesResponse
| Exception
| bytes
| str
| bool,
) -> None:
if not isinstance(responses, list):
responses = [responses]

if not responses:
responses = [MocketBytesResponse(data=b"")]

_responses = []
for response in responses:
if not isinstance(response, (MocketBytesResponse, Exception)):
response = MocketBytesResponse(response)
_responses.append(response)

super().__init__(address=address, responses=_responses)
11 changes: 11 additions & 0 deletions mocket/compat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import mocket.compat.mockhttp as mockhttp
import mocket.compat.mockredis as mockredis
from mocket.bytes import MocketBytesEntry as MocketEntry
from mocket.core.ssl.context import MocketSSLContext as FakeSSLContext

__all__ = [
"FakeSSLContext",
"MocketEntry",
"mockhttp",
"mockredis",
]
File renamed without changes.
139 changes: 139 additions & 0 deletions mocket/compat/mockhttp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from __future__ import annotations

from io import BufferedReader
from typing import Any

from mocket.http import (
MocketHttpEntry,
MocketHttpMethod,
MocketHttpRequest,
MocketHttpResponse,
)
from mocket.mocket import Mocket


class Response(MocketHttpResponse):
def __init__(
self,
body: str | bytes | BufferedReader = b"",
status: int = 200,
headers: dict[str, str] | None = None,
) -> None:
super().__init__(
status_code=status,
headers=headers,
body=body,
)

@property
def status(self) -> int:
return self.status_code


class Request(MocketHttpRequest):
@property
def body(self) -> str | None: # type: ignore
body = super().body
if body is None:
return None
return body.decode()


class Entry(MocketHttpEntry):
request_cls = Request
response_cls = Response # type: ignore[assignment]

CONNECT = MocketHttpMethod.CONNECT
DELETE = MocketHttpMethod.DELETE
GET = MocketHttpMethod.GET
HEAD = MocketHttpMethod.HEAD
OPTIONS = MocketHttpMethod.OPTIONS
PATCH = MocketHttpMethod.PATCH
POST = MocketHttpMethod.POST
PUT = MocketHttpMethod.PUT
TRACE = MocketHttpMethod.TRACE

METHODS = list(MocketHttpMethod)

def __init__(
self,
uri: str,
method: MocketHttpMethod,
responses: list[Exception | Response],
match_querystring: bool = True,
add_trailing_slash: bool = True,
) -> None:
super().__init__(
method=method,
uri=uri,
responses=responses,
match_querystring=match_querystring,
add_trailing_slash=add_trailing_slash,
)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"method='{self.method.name}', "
f"schema='{self.schema}', "
f"location={self.address}, "
f"path='{self.path}', "
f"query='{self.query}'"
")"
)

@property
def schema(self) -> str:
return self.scheme

@classmethod
def register(
cls,
method: MocketHttpMethod,
uri: str,
*responses: Exception | Response,
**config: Any,
) -> None:
if "body" in config or "status" in config:
raise AttributeError("Did you mean `Entry.single_register(...)`?")

if isinstance(config, dict):
match_querystring = config.get("match_querystring", True)
add_trailing_slash = config.get("add_trailing_slash", True)

entry = cls(
method=method,
uri=uri,
responses=list(responses),
match_querystring=match_querystring,
add_trailing_slash=add_trailing_slash,
)
Mocket.register(entry)

@classmethod
def single_register(
cls,
method: MocketHttpMethod,
uri: str,
body: str | bytes | BufferedReader = b"",
status: int = 200,
headers: dict[str, str] | None = None,
match_querystring: bool = True,
exception: Exception | None = None,
) -> None:
response: Response | Exception
if exception is not None:
response = exception
else:
response = Response(
body=body,
status=status,
headers=headers,
)

cls.register(
method,
uri,
response,
match_querystring=match_querystring,
)
Loading

0 comments on commit a69d07e

Please sign in to comment.