Skip to content

Commit

Permalink
NEW: markers GetItem, Drop, ReasonPhraseMixin, TextMixin, and…
Browse files Browse the repository at this point in the history
… `StatusCode`
  • Loading branch information
eigenein committed Nov 4, 2023
1 parent 9cf3992 commit 2a4547e
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 20 deletions.
3 changes: 3 additions & 0 deletions combadge/core/markers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .method import * # noqa: F403
from .parameter import * # noqa: F403
from .response import * # noqa: F403
46 changes: 40 additions & 6 deletions combadge/core/markers/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Dict, TypeVar
from typing import Any, Dict, Mapping, TypeVar

from combadge.core.markers.base import AnnotatedMarker

Expand All @@ -20,15 +20,49 @@ def transform(self, response: Any, payload: Any) -> Any:

@dataclass
class Map(ResponseMarker):
"""Map a payload to a dictionary under the specified key."""

key: Any
"""Key under which the response will be mapped."""

def transform(self, response: Any, payload: Any) -> Dict[Any, Any]: # noqa: D102
return {self.key: payload}


@dataclass
class GetItem(ResponseMarker):
"""
Map a payload to a dictionary under the specified key.
Extract a value from the specified key.
Other Args:
_InputPayloadT (type): input payload type
Examples:
>>> def call() -> Annotated[
>>> int, # Status code is an integer
>>> Drop(), # Drop the payload
>>> StatusCodeMixin(), # Mix in the status code from the HTTP response
>>> GetItem("status_code"), # Extract the status code
>>> ]:
>>> ...
"""

key: Any
"""Key under which the response will be mapped."""
"""Key which will be extracted from the payload."""

def transform(self, response: Any, payload: Mapping[Any, Any]) -> Any: # noqa: D102
return payload[self.key]


class Drop(ResponseMarker): # pragma: no cover
"""
Drop the payload.
It might be useful if one is only interested, for example, in an HTTP status code:
Examples:
>>> def call() -> Annotated[..., Drop(), StatusCodeMixin()]
>>> ...
"""

__slots__ = ()

def transform(self, response: Any, payload: Any) -> Dict[Any, Any]: # noqa: D102
return {self.key: payload}
return {}
22 changes: 20 additions & 2 deletions combadge/support/http/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ def get_payload(self) -> dict:


class SupportsStatusCode(Protocol):
"""Supports a status code attribute or property."""
"""Supports a read-only status code attribute or property."""

status_code: int
@property
def status_code(self) -> int: # noqa: D102
raise NotImplementedError


class SupportsReasonPhrase(Protocol):
"""Supports a read-only reason phrase attribute or property."""

@property
def reason_phrase(self) -> str: # noqa: D102
raise NotImplementedError


class SupportsText(Protocol):
"""Supports a read-only text attribute or property."""

@property
def text(self) -> str: # noqa: D102
raise NotImplementedError
8 changes: 3 additions & 5 deletions combadge/support/http/markers/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ class Payload(ParameterMarker[ContainsPayload]):
Mark parameter as a request payload. An argument gets converted to a dictionary and passed over to a backend.
Examples:
>>> class BodyModel(BaseModel):
>>> ...
Simple usage:
>>> def call(body: Payload[BodyModel]) -> ...:
>>> ...
Equivalent expanded usage:
>>> def call(body: Annotated[BodyModel, Payload()]) -> ...:
>>> ...
"""
Expand Down Expand Up @@ -184,9 +185,6 @@ class FormData(ParameterMarker[ContainsFormData]):
An argument gets converted to a dictionary and passed over to a backend.
Examples:
>>> class FormModel(BaseModel):
>>> ...
>>> def call(body: FormData[FormModel]) -> ...:
>>> ...
Expand Down
58 changes: 55 additions & 3 deletions combadge/support/http/markers/response.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

from dataclasses import dataclass
from http import HTTPStatus
from typing import Any, MutableMapping, TypeVar

from combadge.core.markers.response import ResponseMarker
from combadge.support.http.abc import SupportsStatusCode
from typing_extensions import Annotated, TypeAlias

from combadge.core.markers.response import Drop, GetItem, ResponseMarker
from combadge.support.http.abc import SupportsReasonPhrase, SupportsStatusCode, SupportsText

_MutableMappingT = TypeVar("_MutableMappingT", bound=MutableMapping[Any, Any])

Expand All @@ -23,5 +26,54 @@ class StatusCodeMixin(ResponseMarker):
"""Key under which the status code should assigned in the payload."""

def transform(self, response: SupportsStatusCode, input_: _MutableMappingT) -> _MutableMappingT: # noqa: D102
input_[self.key] = response.status_code
input_[self.key] = HTTPStatus(response.status_code)
return input_


@dataclass
class ReasonPhraseMixin(ResponseMarker):
"""Update payload with HTTP reason message."""

key: Any = "reason"
"""Key under which the reason message should assigned in the payload."""

def transform(self, response: SupportsReasonPhrase, input_: _MutableMappingT) -> _MutableMappingT: # noqa: D102
input_[self.key] = response.reason_phrase
return input_


@dataclass
class TextMixin(ResponseMarker):
"""
Update payload with HTTP response text.
Examples:
>>> class MyResponse(BaseModel):
>>> my_text: str
>>>
>>> class MyService(Protocol):
>>> @http_method("GET")
>>> @path(...)
>>> def get_text(self) -> Annotated[MyResponse, TextMixin("my_text")]:
>>> ...
"""

key: Any = "text"
"""Key under which the text contents should assigned in the payload."""

def transform(self, response: SupportsText, input_: _MutableMappingT) -> _MutableMappingT: # noqa: D102
input_[self.key] = response.text
return input_


StatusCode: TypeAlias = Annotated[HTTPStatus, Drop(), StatusCodeMixin(), GetItem("status_code")]
"""
Shortcut to retrieve just a response status code.
Examples:
>>> def call(...) -> StatusCode:
>>> ...
"""


__all__ = ("StatusCodeMixin", "ReasonPhraseMixin", "TextMixin", "StatusCode")
6 changes: 5 additions & 1 deletion docs/support/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ simplify the binding process:
options:
heading_level: 3

<hr>

## Method markers

::: combadge.core.markers.method
options:
heading_level: 3
members: ["wrap_with"]

<hr>

## Response markers

::: combadge.core.markers.response
options:
heading_level: 3
members: ["Map"]
members: ["Drop", "Map", "GetItem"]
2 changes: 2 additions & 0 deletions docs/support/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
options:
heading_level: 3

<hr>

## Response markers

::: combadge.support.http.markers.response
Expand Down
15 changes: 12 additions & 3 deletions tests/support/http/test_markers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import inspect
from types import SimpleNamespace
from http import HTTPStatus
from typing import Any, Dict, Tuple

import pytest
from httpx import Response

from combadge.support.http.abc import ContainsUrlPath
from combadge.support.http.markers import Path, StatusCodeMixin
from combadge.support.http.markers import Path, ReasonPhraseMixin, StatusCodeMixin, TextMixin


@pytest.mark.parametrize(
Expand All @@ -32,7 +33,15 @@ def test_path_factory() -> None:


def test_status_code_mixin() -> None:
assert StatusCodeMixin("key").transform(SimpleNamespace(status_code=200), {}) == {"key": 200}
assert StatusCodeMixin("key").transform(Response(status_code=200), {}) == {"key": HTTPStatus.OK}


def test_reason_phrase_mixin() -> None:
assert ReasonPhraseMixin("key").transform(Response(status_code=200), {}) == {"key": "OK"}


def test_text_mixin() -> None:
assert TextMixin("key").transform(Response(status_code=200, text="my text"), {}) == {"key": "my text"}


def _example(positional: str, *, keyword: str) -> None:
Expand Down

0 comments on commit 2a4547e

Please sign in to comment.