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

Dialect does not work with inheritance #78

Closed
QSHolzner opened this issue May 9, 2022 · 1 comment
Closed

Dialect does not work with inheritance #78

QSHolzner opened this issue May 9, 2022 · 1 comment
Labels
bug Something isn't working

Comments

@QSHolzner
Copy link

  • mashumaro version: 3.0.1
  • Python version: 3.10
  • Operating System: Linux

Description

If more classes share the same base class the to_dict and from_dict functions get only generated for the class which first called to_dict or from dict. This is because all classes use the __dialect_to_dict_cache__ attribute from the base class. So the first execution of to_dict generates the function and put it into the __dialect_to_dict_cache__ from the base class. Every call of to_dict in a different class uses the fuction from the cache.

What I Did

In the following example the call entity2.to_dict() raises an exception because the to_dict function from Entity1 is used.
This can be seen by the function adress in the __dialect_to_dict_cache__ Dictionary.

from dataclasses import dataclass
from datetime import date, datetime

from mashumaro import DataClassDictMixin
from mashumaro.config import ADD_DIALECT_SUPPORT
from mashumaro.dialect import Dialect
from mashumaro.types import SerializationStrategy


class DateTimeSerializationStrategy(SerializationStrategy):
    def __init__(self, fmt: str):
        self.fmt = fmt

    def serialize(self, value: date) -> str:
        return value.strftime(self.fmt)

    def deserialize(self, value: str) -> date:
        return datetime.strptime(value, self.fmt).date()


class EthiopianDialect(Dialect):
    serialization_strategy = {date: DateTimeSerializationStrategy("%d/%m/%Y")}


@dataclass
class BaseEntity(DataClassDictMixin):
    class Config:
        code_generation_options = [ADD_DIALECT_SUPPORT]


@dataclass
class Entity1(BaseEntity):
    dt1: date


@dataclass
class Entity2(BaseEntity):
    dt2: date


entity1 = Entity1(date(2021, 12, 31))
entity2 = Entity2(date(2021, 12, 31))
try:
    print(entity1.to_dict(dialect=EthiopianDialect))
    print(entity2.to_dict(dialect=EthiopianDialect))
finally:
    print("Entity1", Entity1.__dialect_to_dict_cache__)
    print("Entity2", Entity2.__dialect_to_dict_cache__)

Output:

{'dt1': '31/12/2021'}
Entity1 {<class '__main__.EthiopianDialect'>: <function to_dict at 0x7fa213792dd0>}
Entity2 {<class '__main__.EthiopianDialect'>: <function to_dict at 0x7fa213792dd0>}
Traceback (most recent call last):
  File "/workspaces/ccn-api-server/scripts/demo/performance/issues/mashumaro_dialiect_issue.py", line 52, in <module>
    print(entity2.to_dict(dialect=EthiopianDialect))
  File "<string>", line 15, in to_dict
  File "<string>", line 3, in to_dict
AttributeError: 'Entity2' object has no attribute 'dt1'. Did you mean: 'dt2'?

Possible solution

In mashamuro/core/meta/builder.py - CodeBuilder the hasattr function is used to check if dialect_to_dict_cache exists in this class. Bus hasattr also searches in the BaseClasses. So instead of hasattr one could check in cls.__dict__ for the existence. So only the actual class is checked.

def add_to_dict(self) -> None:
        self.reset()
        dialects_feature = self.is_code_generation_option_enabled(ADD_DIALECT_SUPPORT)
        if dialects_feature:
            # self.add_line("if not hasattr(cls, '__dialect_to_dict_cache__'):") # CHANGED
            self.add_line("if not '__dialect_to_dict_cache__' in cls.__dict__:")  # CHANGED

            with self.indent():
                self.add_line("cls.__dialect_to_dict_cache__ = {}")
        if not dialects_feature or dialects_feature and self.dialect:
            return self._add_to_dict()

      ...
    
   def add_from_dict(self) -> None:
        self.reset()
        dialects_feature = self.is_code_generation_option_enabled(ADD_DIALECT_SUPPORT)
        if dialects_feature:
            #self.add_line("if not hasattr(cls, '__dialect_from_dict_cache__'):") # CHANGED
            self.add_line("if not '__dialect_from_dict_cache__' in cls.__dict__:") # CHANGED
            
            with self.indent():
                self.add_line("cls.__dialect_from_dict_cache__ = {}")
        if not dialects_feature or dialects_feature and self.dialect:
            return self._add_from_dict()
        ...
@Fatal1ty Fatal1ty added the bug Something isn't working label May 26, 2022
@Fatal1ty
Copy link
Owner

Fatal1ty commented Jun 6, 2022

@QSHolzner

Thanks for spotting this bug! Fixed in 3.0.2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants