anikolaienko / py-automapper

Python object auto mapper
https://anikolaienko.github.io/py-automapper/
MIT License
56 stars 10 forks source link

Custom mapping estrange behavior when creating object in SQL Alchemy #18

Closed Carlo1911 closed 5 months ago

Carlo1911 commented 1 year ago

I'm trying to add a field mapping, but when it has more than one level it is not getting the value. The value I want to map is inside "TodoDomain.user.id". I want this to work with preregistered mapping (which I cannot make it work)

from dataclasses import dataclass
from typing import Optional
from uuid import uuid4

from automapper import mapper

@dataclass
class UserDomain:
    id: int
    name: str
    email: str

@dataclass
class UserModel:
    id: int
    name: str
    email: str

@dataclass
class TodoDomain:
    description: str
    user: UserDomain

@dataclass
class TodoModel:
    description: str
    user: UserDomain
    user_id: Optional[int] = None

user = UserDomain(id=1, name="carlo", email="mail_carlo")
todo = TodoDomain(description="todo_carlo", user=user)

mapper.add(UserDomain, UserModel)
# This doesn't work. user_id is None
# mapper.add(TodoDomain, TodoModel)

# This works properly. user_id is 1
todo = mapper.to(TodoModel).map(todo, fields_mapping={"user_id": todo.user.id})

print(todo.__dict__)
nerlijman commented 1 year ago

I am also interested on this. @anikolaienko can you help on this issue?

anikolaienko commented 5 months ago

This is by design. Implicit mapping of fields between different levels can be very confusing and produce unpredictable behaviour. Use explicit mapping as described in your example. E.g.

from dataclasses import dataclass

@dataclass
class UserDomain:
    id: int
    name: str
    email: str

@dataclass
class TodoDomain:
    description: str
    user: UserDomain

@dataclass
class TodoModel:
    description: str
    user: UserDomain
    user_id: Optional[int] = None

user = UserDomain(id=1, name="carlo", email="mail_carlo")
todo = TodoDomain(description="todo_carlo", user=user)

mapper.add(TodoDomain, TodoModel)
todo_model = mapper.map(todo)

# Implicit field mapping between parent and child objects is not supported
# TodoDomain.user.user_id should not map to TodoModel.user_id implicitly
assert todo_model.user_id is None    # True

# Workaround: use explicit mapping
todo_model = mapper.map(todo, fields_mapping={"user_id": todo.user.id})

assert todo_model.user_id == 1     # True

Added a test case: https://github.com/anikolaienko/py-automapper/blob/feature/upgrade-dependencies-and-fix-ci/tests/test_issue_18_implicit_child_field_mapping.py