Skip to content

Instantly share code, notes, and snippets.

@ahancock1
Created November 20, 2022 21:48
Show Gist options
  • Save ahancock1/5e5e0c665c3e696f1e8085f7b38bd123 to your computer and use it in GitHub Desktop.
Save ahancock1/5e5e0c665c3e696f1e8085f7b38bd123 to your computer and use it in GitHub Desktop.
Simple dataclass Automapper for python 3.10
from typing import Protocol, TypeVar, Callable
from dataclasses import is_dataclass, fields
from dataclasses import MISSING
S = TypeVar("S")
T = TypeVar("T")
class IProfile(Protocol):
mappings: dict[tuple[type[S], type[T]], dict[str, Callable[[S], object]]]
def create_map(self,
source_type: type[S],
target_type: type[T],
**mappings: Callable[[S], object]) -> None:
...
class IMapper(Protocol):
def map(self, data: object, data_type: type[T]) -> T:
...
class Profile:
mappings: dict[tuple[type[S], type[T]], dict[str, Callable[[S], object]]]
def __init__(self) -> None:
self.mappings = {}
def create_map(self,
source_type: type[S],
target_type: type[T],
**mappings: Callable[[S], object]) -> None:
self.mappings[(source_type, target_type)] = dict(mappings)
class Mapper:
_mappings: dict[tuple[type[S], type[T]], dict[str, Callable[[S], object]]]
def __init__(self, profiles: list[IProfile]) -> None:
self._mappings = {}
for profile in profiles:
for key, value in profile.mappings.items():
self._mappings[key] = value
def map(self, data: object, data_type: type[T]) -> T:
if not is_dataclass(data_type):
raise TypeError("type must be a dataclass")
mapping_key = (type(data), data_type,)
data_fields = fields(data_type)
data_params = {}
mappings = self._mappings.get(mapping_key, {})
for field in data_fields:
field_name, field_type = field.name, field.type
field_value = getattr(data, field_name, None)
if is_dataclass(field_type):
field_value = self.map(field_value, field_type)
else:
if field_name in mappings:
field_value = mappings[field_name](field_value)
if not field_value and field.default is not MISSING:
field_value = field.default
data_params[field_name] = field_value
return data_type(**data_params)
from dataclasses import dataclass
@dataclass
class Middle:
value: str
@dataclass
class Source:
a: str
b: int
c: Middle
@dataclass
class Target:
c: Middle
b: int
d: str = "Test"
class TestProfile(Profile):
def __init__(self) -> None:
super().__init__()
self.create_map(
Source, Target,
c=lambda x: x.c,
b=lambda _: 1)
mapper = Mapper([TestProfile()])
x = mapper.map(Source("a", 2, Middle("value")), Target)
print(x)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment