-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
655 additions
and
334 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[tool.poetry] | ||
name = "BrokRPC" | ||
version = "0.2.3" | ||
version = "0.2.4" | ||
description = "framework for gRPC like server-client communication over message brokers" | ||
authors = ["zerlok <[email protected]>"] | ||
readme = "README.md" | ||
|
@@ -33,11 +33,13 @@ protobuf = {version = "^5.26.1", optional = true} | |
googleapis-common-protos = {version = "^1.65.0", optional = true} | ||
aiormq = {version = "^6.8.1", optional = true} | ||
aiofiles = {version = "^24.1.0", optional = true} | ||
pydantic = {version = "^2.10.4", optional = true} | ||
|
||
[tool.poetry.extras] | ||
cli = ["aiofiles"] | ||
protobuf = ["protobuf", "googleapis-common-protos"] | ||
aiormq = ["aiormq"] | ||
pydantic = ["pydantic"] | ||
|
||
[tool.poetry.scripts] | ||
brokrpc = "brokrpc.cli:main" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from pydantic import BaseModel | ||
from pydantic.main import IncEx | ||
from pydantic_core import PydanticSerializationError | ||
|
||
from brokrpc.abc import Serializer | ||
from brokrpc.message import BinaryMessage, Message, PackedMessage, UnpackedMessage | ||
from brokrpc.model import SerializerDumpError, SerializerLoadError | ||
|
||
|
||
class PydanticSerializer[T: BaseModel](Serializer[Message[T], BinaryMessage]): | ||
# NOTE: allow `model_dump_json` & `model_validate_json` pydantic model methods customization | ||
def __init__( # noqa: PLR0913 | ||
self, | ||
model: type[T], | ||
*, | ||
indent: int | None = None, | ||
include: IncEx | None = None, | ||
exclude: IncEx | None = None, | ||
context: object = None, | ||
by_alias: bool = True, | ||
exclude_unset: bool = True, | ||
exclude_defaults: bool = False, | ||
exclude_none: bool = True, | ||
strict: bool | None = False, | ||
) -> None: | ||
self.__model = model | ||
self.__indent = indent | ||
self.__include = include | ||
self.__exclude = exclude | ||
self.__context = context | ||
self.__by_alias = by_alias | ||
self.__exclude_unset = exclude_unset | ||
self.__exclude_defaults = exclude_defaults | ||
self.__exclude_none = exclude_none | ||
self.__strict = strict | ||
|
||
def dump_message(self, message: Message[T]) -> BinaryMessage: | ||
assert isinstance(message.body, self.__model) | ||
|
||
try: | ||
body = message.body.model_dump_json( | ||
indent=self.__indent, | ||
include=self.__include, | ||
exclude=self.__exclude, | ||
context=self.__context, | ||
by_alias=self.__by_alias, | ||
exclude_unset=self.__exclude_unset, | ||
exclude_defaults=self.__exclude_defaults, | ||
exclude_none=self.__exclude_none, | ||
).encode() | ||
|
||
except PydanticSerializationError as err: | ||
details = "can't dump pydantic model" | ||
raise SerializerDumpError(details, message) from err | ||
|
||
return PackedMessage( | ||
body=body, | ||
content_type="application/json", | ||
content_encoding="utf-8", | ||
original=message, | ||
) | ||
|
||
def load_message(self, message: BinaryMessage) -> Message[T]: | ||
try: | ||
body = self.__model.model_validate_json(message.body, strict=self.__strict) | ||
|
||
except ValueError as err: | ||
details = "can't load pydantic model" | ||
raise SerializerLoadError(details, message) from err | ||
|
||
return UnpackedMessage( | ||
original=message, | ||
body=body, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import pytest | ||
from pydantic import BaseModel | ||
|
||
from brokrpc.message import AppMessage, Message | ||
from brokrpc.serializer.json import JSONSerializer | ||
from brokrpc.serializer.pydantic import PydanticSerializer | ||
from tests.stub.pydantic import FooModel | ||
|
||
|
||
@pytest.mark.parametrize("obj", [FooModel(num=42, s="spam", bar=FooModel.Bar(str2int={"eggs": 59}))]) | ||
def test_pydantic_dump_json_load_ok( | ||
pydantic_serializer: PydanticSerializer, | ||
json_serializer: JSONSerializer, | ||
message: Message[object], | ||
obj: BaseModel, | ||
) -> None: | ||
loaded = json_serializer.load_message(pydantic_serializer.dump_message(message)) | ||
|
||
assert loaded.body == obj.model_dump(mode="json", by_alias=True, exclude_unset=True, exclude_none=True) | ||
|
||
|
||
@pytest.fixture | ||
def pydantic_serializer(obj: BaseModel) -> PydanticSerializer: | ||
return PydanticSerializer(type(obj)) | ||
|
||
|
||
@pytest.fixture | ||
def message(obj: object, stub_routing_key: str) -> Message[object]: | ||
return AppMessage(body=obj, routing_key=stub_routing_key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import typing as t | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class FooModel(BaseModel): | ||
class Bar(BaseModel): | ||
str2int: t.Mapping[str, int] | ||
|
||
num: int | ||
s: str | ||
bar: Bar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import pytest | ||
from pydantic import BaseModel | ||
|
||
from brokrpc.message import AppMessage, Message | ||
from brokrpc.serializer.pydantic import PydanticSerializer | ||
from tests.stub.pydantic import FooModel | ||
|
||
|
||
@pytest.mark.parametrize("obj", [FooModel(num=42, s="spam", bar=FooModel.Bar(str2int={"eggs": 59}))]) | ||
def test_dump_load_ok(serializer: PydanticSerializer, message: Message[object]) -> None: | ||
loaded = serializer.load_message(serializer.dump_message(message)) | ||
|
||
assert loaded.body == message.body | ||
|
||
|
||
@pytest.fixture | ||
def serializer(obj: BaseModel) -> PydanticSerializer: | ||
return PydanticSerializer(type(obj)) | ||
|
||
|
||
@pytest.fixture | ||
def message(obj: object, stub_routing_key: str) -> Message[object]: | ||
return AppMessage(body=obj, routing_key=stub_routing_key) |