vltr / middle

Flexible, extensible Python data structures for general usage
https://middle.readthedocs.io/en/latest/
MIT License
10 stars 1 forks source link

Does not work with EnumMeta #30

Closed ahopkins closed 5 years ago

ahopkins commented 5 years ago

Description

I tried to implement middle with an Enum that uses EnumMeta to implement a custom metaclass.

What I Did

Traceback (most recent call last):
  File "/tmp/p.py", line 28, in <module>
    class Item(middle.Model):
  File "/home/adam/.virtualenvs/TEMP37/lib/python3.7/site-packages/middle/model.py", line 95, in __new__
    _implement_converters(f, k, annotations)
  File "/home/adam/.virtualenvs/TEMP37/lib/python3.7/site-packages/middle/model.py", line 200, in _implement_converters
    converter_fn = converter(annotations.get(key))
  File "/home/adam/.virtualenvs/TEMP37/lib/python3.7/site-packages/middle/dispatch.py", line 25, in __call__
    return fn(*args)
  File "/home/adam/.virtualenvs/TEMP37/lib/python3.7/site-packages/middle/converters.py", line 154, in converter
    raise InvalidType()
middle.exceptions.InvalidType: The required type is not supported by ``middle``. Please, see https://middle.readthedocs.io/en/latest/extending.html for more information on how to add your own types.

Snippets

Here is the code that produced the exception.

from enum import Enum
from enum import EnumMeta
from enum import auto

import random
import middle

class RandomMetaclass(EnumMeta):
    @property
    def RANDOM(self):
        member_map = [x for x in self._member_map_.values()]
        return random.choice(member_map)

class BaseEnum(Enum, metaclass=RandomMetaclass):
    pass

class Status(BaseEnum):
    DRAFT = auto()
    PROPOSED = auto()
    ACCEPTED = auto()
    REJECTED = auto()
    ABANDONED = auto()

class Item(middle.Model):
    title: str
    status: Status

item = Item(title="FooBar", status=Status.RANDOM)
print(item)

I confirmed that it works if I do not use EnumMeta.

class Status(Enum):
    ...

item = Item(title="FooBar", status=Status.DRAFT)

The snippet provided (if any) can be used to create unit tests for this issue.

vltr commented 5 years ago

I thought this was a bug, but in fact it isn't. The problem is that, with the mro, type(Status) returns RandomMetaclass instead of EnumMeta (of course). To make it work, you simply need to add your type (after declaring it and before instantiating with middle.Model) in middle's TypeRegistry:

from enum import Enum
from enum import EnumMeta
from enum import auto

import random
import middle

from middle import TypeRegistry

class RandomMetaclass(EnumMeta):
    @property
    def RANDOM(self):
        member_map = [x for x in self._member_map_.values()]
        return random.choice(member_map)

class BaseEnum(Enum, metaclass=RandomMetaclass):
    pass

class Status(BaseEnum):
    DRAFT = auto()
    PROPOSED = auto()
    ACCEPTED = auto()
    REJECTED = auto()
    ABANDONED = auto()

TypeRegistry[RandomMetaclass] = EnumMeta

class Item(middle.Model):
    title: str
    status: Status

item = Item(title="FooBar", status=Status.RANDOM)
print(item)

I know, this is not far from perfect - I can see if I can find a __base__ attribute inside the given type (RandomMetaclass.__base__ == enum.EnumMeta), but I don't know if it's good to go that far (into guessing the type). Ideas?

ahopkins commented 5 years ago

Nice. Thanks for the heads up. That seems a pretty simple solution to an outlier of a use case.