Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAK: rename RequestPreparer into ParameterInfo for better clarity 💥 #151

Merged
merged 1 commit into from
Nov 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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."""