From 102d56aa601fdc6ced89101f92bfc4dcabd05d4c Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Wed, 8 Nov 2023 07:18:03 +0100 Subject: [PATCH] Add mapping support in Encoder and Decoder --- README.md | 9 +++++++++ chili/decoder.py | 10 +++++----- chili/encoder.py | 6 ++++-- tests/test_decoder.py | 34 +++++++++++++++++++++++++++++++++- tests/test_encoder.py | 33 +++++++++++++++++++++++++++++---- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dbb0719..8d67f57 100644 --- a/README.md +++ b/README.md @@ -461,6 +461,15 @@ assert encoded == { } ``` +Alternatively you can set mapper in `Encoder` and `Decoder` classes: + +```python +encoder = Encoder[Pet](mapper=mapper) + +pet = Pet("Max", 3, ["cute", "furry"]) +encoded = encoder.encode(pet) +``` + ## Error handling The library raises errors if an invalid type is passed to the Encoder or Decoder, or if an invalid dictionary is passed to the Decoder. diff --git a/chili/decoder.py b/chili/decoder.py index 53dbfa7..4edaa80 100644 --- a/chili/decoder.py +++ b/chili/decoder.py @@ -536,9 +536,10 @@ class Decoder(Generic[T]): __generic__: Type[T] _decoders: Dict[str, TypeDecoder] - def __init__(self, decoders: Union[TypeDecoders, Dict[Any, TypeDecoder]] = None): + def __init__(self, decoders: Union[TypeDecoders, Dict[Any, TypeDecoder]] = None, mapper: Optional[Mapper] = None): if decoders and not isinstance(decoders, TypeDecoders): decoders = TypeDecoders(decoders) + self.mapper = mapper self.type_decoders = decoders @property @@ -554,6 +555,8 @@ def decode(self, obj: Dict[str, StateObject]) -> T: if hasattr(self.__generic__, _DECODE_MAPPER): mapper = getattr(self.__generic__, _DECODE_MAPPER) obj = mapper.map(obj) + elif self.mapper: + obj = self.mapper.map(obj) for key, prop in self.schema.items(): if key not in obj: @@ -600,10 +603,7 @@ def __class_getitem__(cls, item: Type) -> Type[Decoder]: # noqa: E501 def decode( - obj: StateObject, - a_type: Type[T], - decoders: Union[TypeDecoders, Dict[Any, TypeDecoder]] = None, - force: bool = False + obj: StateObject, a_type: Type[T], decoders: Union[TypeDecoders, Dict[Any, TypeDecoder]] = None, force: bool = False ) -> T: if decoders and not isinstance(decoders, TypeDecoders): decoders = TypeDecoders(decoders) diff --git a/chili/encoder.py b/chili/encoder.py index 5107c2e..a08b70b 100644 --- a/chili/encoder.py +++ b/chili/encoder.py @@ -443,10 +443,10 @@ class Encoder(Generic[T]): __generic__: Type[T] _encoders: Dict[str, TypeEncoder] - def __init__(self, encoders: Union[Dict, TypeEncoders] = None): + def __init__(self, encoders: Union[Dict, TypeEncoders] = None, mapper: Optional[Mapper] = None): if encoders and not isinstance(encoders, TypeEncoders): encoders = TypeEncoders(encoders) - + self.mapper = mapper self.type_encoders = encoders def encode(self, obj: T) -> StateObject: @@ -466,6 +466,8 @@ def encode(self, obj: T) -> StateObject: if hasattr(self.__generic__, _ENCODE_MAPPER): mapper = getattr(self.__generic__, _ENCODE_MAPPER) return mapper.map(result) + elif self.mapper: + return self.mapper.map(result) return result diff --git a/tests/test_decoder.py b/tests/test_decoder.py index fe24d18..7643561 100644 --- a/tests/test_decoder.py +++ b/tests/test_decoder.py @@ -1,4 +1,4 @@ -from chili import Decoder, decodable +from chili import Decoder, Mapper, decodable def test_can_instantiate() -> None: @@ -13,3 +13,35 @@ class Example: # then assert isinstance(instance, Decoder) assert instance.__generic__ == Example + + +def test_dencode_and_map() -> None: + # given + class Example: + name: str + age: int + + def __init__(self, name: str, age: int): + self.name = name + self.age = age + + mapper = Mapper( + { + "name": "_name", + "age": "_age", + } + ) + encoder = Decoder[Example](mapper=mapper) + + # when + data = encoder.decode( + { + "_name": "Bobik", + "_age": 11, + } + ) + + # then + assert isinstance(data, Example) + assert data.name == "Bobik" + assert data.age == 11 diff --git a/tests/test_encoder.py b/tests/test_encoder.py index 54ecdee..afc9166 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -1,9 +1,6 @@ from collections import UserString -import pytest - -from chili import Encoder, encodable -from chili.error import EncoderError +from chili import Encoder, Mapper, encodable def test_can_instantiate() -> None: @@ -63,3 +60,31 @@ def __init__(self, name: ExampleName, age: int): "name": "bob", "age": 33, } + + +def test_encode_and_map() -> None: + # given + class Example: + name: str + age: int + + def __init__(self, name: str, age: int): + self.name = name + self.age = age + + mapper = Mapper( + { + "_name": "name", + "_age": "age", + } + ) + encoder = Encoder[Example](mapper=mapper) + + # when + data = encoder.encode(Example("Bobik", 11)) + + # then + assert data == { + "_name": "Bobik", + "_age": 11, + }