Skip to content

Commit

Permalink
BREAK: rename RequestPreparer into ParameterInfo for better clari…
Browse files Browse the repository at this point in the history
…ty 💥
  • Loading branch information
eigenein committed Nov 28, 2024
1 parent 72edee1 commit f471933
Showing 1 changed file with 28 additions and 29 deletions.
57 changes: 28 additions & 29 deletions combadge/core/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from pydantic import BaseModel, TypeAdapter

from combadge._helpers.dataclasses import SLOTS
from combadge.core.markers.method import MethodMarker
from combadge.core.markers.parameter import ParameterMarker
from combadge.core.markers.response import ResponseMarker
Expand All @@ -20,12 +21,12 @@
from get_annotations import get_annotations # type: ignore[no-redef, import-not-found, import-untyped]


@dataclass
@dataclass(**SLOTS)
class Signature:
"""Extracted information about a service method."""

request_preparers: Iterable[RequestPreparer]
"""Request preparers constructed from the parameter markers."""
parameters_infos: Iterable[ParameterInfo]
"""All method parameters with the needed information."""

method_markers: list[MethodMarker]
"""Extracted method markers."""
Expand All @@ -39,16 +40,14 @@ class Signature:
bind_arguments: Callable[..., BoundArguments]
"""A callable that binds the method's arguments, it is cached here to improve performance."""

__slots__ = ("bind_arguments", "method_markers", "request_preparers", "return_type", "response_markers")

@classmethod
def from_method(cls, method: Any) -> Signature:
"""Create a signature from the specified method."""
annotations_ = get_annotations(method, eval_str=True)
return_type = Signature._extract_return_type(annotations_)
return Signature(
return_type = cls._extract_return_type(annotations_)
return cls(
bind_arguments=get_signature(method).bind,
request_preparers=Signature._build_request_preparers(annotations_),
parameters_infos=cls._extract_parameter_infos(annotations_),
method_markers=MethodMarker.ensure_markers(method),
return_type=return_type,
response_markers=ResponseMarker.extract(return_type),
Expand Down Expand Up @@ -76,22 +75,23 @@ def build_request(
request = request_type()

# Apply the method markers: they receive all the arguments at once.
for marker in self.method_markers:
marker.prepare_request(request, bound_arguments)
for method_marker in self.method_markers:
method_marker.prepare_request(request, bound_arguments)

# Apply the parameter markers: they receive their respective values.
all_arguments = bound_arguments.arguments
for preparer in self.request_preparers:
for parameter_info in self.parameters_infos:
try:
value = all_arguments[preparer.parameter_name]
value = all_arguments[parameter_info.name]
except KeyError:
# The parameter is not provided, skip the marker.
# The parameter is not provided – skipping it.
continue
if callable(value):
# Allow for lazy loaded default parameters.
# TODO: we could potentially support async here.
value = value()
for prepare_request in preparer.prepare_request:
prepare_request(request, value)
for parameter_marker in parameter_info.markers:
parameter_marker(request, value)

return request

Expand All @@ -106,19 +106,19 @@ def apply_response_markers(self, response: Any, payload: Any, response_type: Typ
"""
for marker in self.response_markers:
payload = marker(response, payload)
if not isinstance(payload, BaseModel):
if not isinstance(payload, BaseModel): # TODO: this `if` may no needed anymore.
# Implicitly parse a Pydantic model.
# Need to come up with something smarter to uncouple Combadge from Pydantic.
# TODO: come up with something smarter to better uncouple Combadge from Pydantic.
payload = response_type.validate_python(payload)
return payload

@staticmethod
def _build_request_preparers(annotations_: dict[str, Any]) -> Iterable[RequestPreparer]:
"""Extract the parameter descriptors: separate item for each parameter ⨯ marker combination."""
def _extract_parameter_infos(annotations_: dict[str, Any]) -> Iterable[ParameterInfo]:
"""Extract all the parameters with the needed associated information."""
return tuple(
RequestPreparer(
parameter_name=name,
prepare_request=ParameterMarker.extract(annotation),
ParameterInfo(
name=name,
markers=ParameterMarker.extract(annotation),
)
for name, annotation in annotations_.items()
)
Expand All @@ -131,11 +131,10 @@ def _extract_return_type(annotations_: dict[str, Any]) -> type[Any] | None:
return None


@dataclass
class RequestPreparer(Generic[BackendRequestT]): # noqa: D101
__slots__ = ("parameter_name", "prepare_request")

parameter_name: str
@dataclass(**SLOTS)
class ParameterInfo(Generic[BackendRequestT]): # noqa: D101
name: str
"""The parameter's name."""

prepare_request: Iterable[Callable[[BackendRequestT, Any], None]]
"""Collection of callables, which modify the request based on the parameter's value."""
markers: Iterable[ParameterMarker[BackendRequestT]]
"""The parameter's markers used to build request with the runtime parameter value."""

0 comments on commit f471933

Please sign in to comment.