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

FIX: restore the shortcuts Payload[...], Header[...], and FormData[...] 🧑‍💻 #162

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:

- name: "✨ Install Poetry"
run: |
pipx install poetry
pipx install "poetry<2.0.0"
pipx inject poetry poetry-dynamic-versioning

- name: 🐍 Set up Python ${{ matrix.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
fetch-depth: 0

- name: "✨ Install Poetry"
run: pipx install poetry
run: pipx install "poetry<2.0.0"

- uses: actions/setup-python@v5
name: "✨ Set up Python"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:

- name: "✨ Install Poetry"
run: |
pipx install poetry
pipx install "poetry<2.0.0"
pipx inject poetry poetry-dynamic-versioning

- name: 📦 Build package
Expand Down
2 changes: 2 additions & 0 deletions combadge/core/typevars.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
if TYPE_CHECKING:
from combadge.core.backend import BaseBackend

AnyT = TypeVar("AnyT")

BackendT = TypeVar("BackendT", bound="BaseBackend")
"""Backend type."""

Expand Down
110 changes: 62 additions & 48 deletions combadge/support/http/markers/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
from dataclasses import dataclass
from enum import Enum
from inspect import BoundArguments
from typing import Any, Callable, Generic, cast
from typing import TYPE_CHECKING, Annotated, Any, Callable, Generic, cast

from annotated_types import SLOTS
from typing_extensions import override
from typing_extensions import TypeAlias, override

from combadge._helpers.pydantic import get_type_adapter
from combadge.core.markers.method import MethodMarker
from combadge.core.markers.parameter import ParameterMarker
from combadge.core.typevars import FunctionT
from combadge.core.typevars import AnyT, FunctionT
from combadge.support.http.abc import (
HttpRequestFormData,
HttpRequestHeaders,
Expand Down Expand Up @@ -142,37 +142,44 @@
request.query_params.append((self.name, sub_value.value if isinstance(sub_value, Enum) else sub_value))


@dataclass(**SLOTS)
class Payload(ParameterMarker[HttpRequestPayload]):
"""
Mark parameter as a request payload.
if TYPE_CHECKING:
Payload: TypeAlias = Annotated[AnyT, ...]
else:

An argument gets converted to a dictionary and passed over to a backend.
@dataclass(**SLOTS)
class Payload(ParameterMarker[HttpRequestPayload]):
"""
Mark parameter as a request payload.

Examples:
>>> def call(body: Annotated[BodyModel, Payload()]) -> ...:
>>> ...
"""
An argument gets converted to a dictionary and passed over to a backend.

exclude_unset: bool = False
by_alias: bool = False
Examples:
>>> def call(body: Annotated[BodyModel, Payload()]) -> ...:
>>> ...

@override
def __call__(self, request: HttpRequestPayload, value: Any) -> None: # noqa: D102
value = get_type_adapter(cast(Hashable, type(value))).dump_python(
value,
by_alias=self.by_alias,
exclude_unset=self.exclude_unset,
)
if request.payload is None:
request.payload = value
elif isinstance(request.payload, dict):
request.payload.update(value) # merge into the existing payload
else:
raise ValueError(f"attempting to merge `{type(value)}` into `{type(request.payload)}`")
>>> def call(body: Payload[BodyModel]) -> ...:
>>> ...
"""

exclude_unset: bool = False
by_alias: bool = False

@override
def __call__(self, request: HttpRequestPayload, value: Any) -> None: # noqa: D102
value = get_type_adapter(cast(Hashable, type(value))).dump_python(
value,
by_alias=self.by_alias,
exclude_unset=self.exclude_unset,
)
if request.payload is None:
request.payload = value
elif isinstance(request.payload, dict):
request.payload.update(value) # merge into the existing payload

Check warning on line 177 in combadge/support/http/markers/request.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/http/markers/request.py#L177

Added line #L177 was not covered by tests
else:
raise ValueError(f"attempting to merge `{type(value)}` into `{type(request.payload)}`")

Check warning on line 179 in combadge/support/http/markers/request.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/http/markers/request.py#L179

Added line #L179 was not covered by tests

def __class_getitem__(cls, item: type[Any]) -> Any:
raise NotImplementedError("the shortcut is no longer supported, use `Annotated[..., Payload()]`")
def __class_getitem__(cls, item: type[Any]) -> Any:
return Annotated[item, cls()]


@dataclass(**SLOTS)
Expand All @@ -197,28 +204,35 @@
request.payload[self.name] = value.value if isinstance(value, Enum) else value


@dataclass(**SLOTS)
class FormData(ParameterMarker[HttpRequestFormData]):
"""
Mark parameter as a request form data.
if TYPE_CHECKING:
FormData: TypeAlias = Annotated[AnyT, ...]
else:

An argument gets converted to a dictionary and passed over to a backend.
@dataclass(**SLOTS)
class FormData(ParameterMarker[HttpRequestFormData]):
"""
Mark parameter as a request form data.

Examples:
>>> def call(body: Annotated[FormModel, FormData()]) -> ...:
>>> ...
"""
An argument gets converted to a dictionary and passed over to a backend.

@override
def __call__(self, request: HttpRequestFormData, value: Any) -> None: # noqa: D102
value = get_type_adapter(cast(Hashable, type(value))).dump_python(value, by_alias=True)
if not isinstance(value, dict):
raise TypeError(f"form data requires a dictionary, got {type(value)}")
for item_name, item_value in value.items():
request.append_form_field(item_name, item_value)

def __class_getitem__(cls, item: type[Any]) -> Any:
raise NotImplementedError("the shortcut is no longer supported, use `Annotated[..., FormData()]`")
Examples:
>>> def call(body: Annotated[FormModel, FormData()]) -> ...:
>>> ...

>>> def call(body: FormData[FormModel]) -> ...:
>>> ...
"""

@override
def __call__(self, request: HttpRequestFormData, value: Any) -> None: # noqa: D102
value = get_type_adapter(cast(Hashable, type(value))).dump_python(value, by_alias=True)
if not isinstance(value, dict):
raise TypeError(f"form data requires a dictionary, got {type(value)}")

Check warning on line 230 in combadge/support/http/markers/request.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/http/markers/request.py#L230

Added line #L230 was not covered by tests
for item_name, item_value in value.items():
request.append_form_field(item_name, item_value)

def __class_getitem__(cls, item: type[Any]) -> Any:
return Annotated[item, cls()]


@dataclass(**SLOTS)
Expand Down
75 changes: 41 additions & 34 deletions combadge/support/soap/markers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from collections.abc import Hashable
from dataclasses import dataclass
from inspect import BoundArguments
from typing import Any, Callable, Generic, cast
from typing import TYPE_CHECKING, Annotated, Any, Callable, Generic, cast

from annotated_types import SLOTS
from typing_extensions import override
from typing_extensions import TypeAlias, override

from combadge._helpers.pydantic import get_type_adapter
from combadge.core.markers.method import MethodMarker
from combadge.core.markers.parameter import ParameterMarker
from combadge.core.typevars import FunctionT
from combadge.core.typevars import AnyT, FunctionT
from combadge.support.soap.abc import SoapHeader, SoapOperationName


Expand Down Expand Up @@ -38,34 +38,41 @@
return OperationName[Any](name).mark


@dataclass(**SLOTS)
class Header(ParameterMarker[SoapHeader]):
"""
Mark parameter as a request header.

An argument gets converted to a dictionary and passed over to a backend.

Examples:
>>> def call(body: Annotated[HeaderModel, Header()]) -> ...:
>>> ...
"""

exclude_unset: bool = False
by_alias: bool = False

@override
def __call__(self, request: SoapHeader, value: Any) -> None: # noqa: D102
value = get_type_adapter(cast(Hashable, type(value))).dump_python(
value,
by_alias=self.by_alias,
exclude_unset=self.exclude_unset,
)
if request.soap_header is None:
request.soap_header = value
elif isinstance(request.soap_header, dict):
request.soap_header.update(value) # merge into the existing header
else:
raise ValueError(f"attempting to merge `{type(value)}` into `{type(request.soap_header)}`")

def __class_getitem__(cls, item: type[Any]) -> Any:
raise NotImplementedError("the shortcut is no longer supported, use `Annotated[..., Header()]`")
if TYPE_CHECKING:
Header: TypeAlias = Annotated[AnyT, ...]
else:

@dataclass(**SLOTS)
class Header(ParameterMarker[SoapHeader]):
"""
Mark parameter as a request header.

An argument gets converted to a dictionary and passed over to a backend.

Examples:
>>> def call(body: Annotated[HeaderModel, Header()]) -> ...:
>>> ...

>>> def call(body: Header[HeaderModel]) -> ...:
>>> ...
"""

exclude_unset: bool = False
by_alias: bool = False

@override
def __call__(self, request: SoapHeader, value: Any) -> None: # noqa: D102
value = get_type_adapter(cast(Hashable, type(value))).dump_python(

Check warning on line 65 in combadge/support/soap/markers.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/soap/markers.py#L65

Added line #L65 was not covered by tests
value,
by_alias=self.by_alias,
exclude_unset=self.exclude_unset,
)
if request.soap_header is None:
request.soap_header = value

Check warning on line 71 in combadge/support/soap/markers.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/soap/markers.py#L71

Added line #L71 was not covered by tests
elif isinstance(request.soap_header, dict):
request.soap_header.update(value) # merge into the existing header

Check warning on line 73 in combadge/support/soap/markers.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/soap/markers.py#L73

Added line #L73 was not covered by tests
else:
raise ValueError(f"attempting to merge `{type(value)}` into `{type(request.soap_header)}`")

Check warning on line 75 in combadge/support/soap/markers.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/soap/markers.py#L75

Added line #L75 was not covered by tests

def __class_getitem__(cls, item: type[Any]) -> Any:
return Annotated[item, cls()]

Check warning on line 78 in combadge/support/soap/markers.py

View check run for this annotation

Codecov / codecov/patch

combadge/support/soap/markers.py#L78

Added line #L78 was not covered by tests
6 changes: 3 additions & 3 deletions docs/support/models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ from httpx import Client
class Httpbin(Protocol):
@http_method("POST")
@path("/anything")
def post_anything(self, foo: Annotated[int, Payload()]) -> Annotated[int, Extract("data")]:
def post_anything(self, foo: Payload[int]) -> Annotated[int, Extract("data")]:
...


Expand Down Expand Up @@ -51,7 +51,7 @@ class Response:
class Httpbin(Protocol):
@http_method("POST")
@path("/anything")
def post_anything(self, foo: Annotated[Request, Payload()]) -> Response:
def post_anything(self, foo: Payload[int]) -> Response:
...


Expand Down Expand Up @@ -80,7 +80,7 @@ class Response(TypedDict):
class Httpbin(Protocol):
@http_method("POST")
@path("/anything")
def post_anything(self, foo: Annotated[Request, Payload()]) -> Response:
def post_anything(self, foo: Payload[Request]) -> Response:
...


Expand Down
3 changes: 2 additions & 1 deletion tests/core/markers/test_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import pytest

from combadge.core.markers.parameter import ParameterMarker
from combadge.support.http.markers import CustomHeader
from combadge.support.http.markers import CustomHeader, Payload


@pytest.mark.parametrize(
("type_", "expected"),
[
(int, []),
(Payload[int], [Payload()]),
(Annotated[str, CustomHeader("X-Header")], [CustomHeader("X-Header")]),
],
)
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_httpbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class SupportsHttpbin(Protocol):
@abstractmethod
def post_anything(
self,
data: Annotated[Data, FormData()],
data: FormData[Data],
bar: Annotated[int, FormField("barqux")],
qux: Annotated[int, FormField("barqux")],
) -> Response: ...
Expand Down