5
\$\begingroup\$

I implemented an enum field class EnumField inheriting from peewee.CharField.
The reason that I use a character-based field is that I require human-readability of the enumeration values on the database level.

I currently use the funcion is_enum to detemine whether the provided enum constraint is an enum.Enum. Knowing this is important, since enum.Enum instances need to be translated, unlinke sets of strings, back and forth between the enum value and string value.
I'd especially like to improve this translation.

class InvalidEnumerationValue(ValueError):
    """Indicates that an invalid enumeration value has been specified."""

    def __init__(self, value):
        super().__init__('Invalid enum value: "{}".'.format(value))

def is_enum(obj):
    """Determines whether the object is an enum.Enum."""

    try:
        return issubclass(obj, Enum)
    except TypeError:
        return False

class EnumField(peewee.CharField):
    """CharField-based enumeration field."""

    def __init__(self, enum, *args, max_length=None, null=None, **kwargs):
        """Initializes the enumeration field with the possible values.

        :enum: The respective enumeration.
        :max_length: Ignored.
        :null: Ignored.
        """
        super().__init__(*args, max_length=max_length, null=null, **kwargs)
        self.enum = enum

    @property
    def enum(self):
        """Returns the enumeration values."""
        return self._enum

    @enum.setter
    def enum(self, enum):
        """Sets the enumeration values."""
        if is_enum(enum):
            self._enum = enum
        else:
            self._enum = set(enum)

    @property
    def values(self):
        """Yields appropriate database values."""
        if is_enum(self.enum):
            for item in self.enum:
                yield item.value
        else:
            for value in self.enum:
                yield value

    @property
    def max_length(self):
        """Derives the required field size from the enumeration values."""
        return max(len(value) for value in self.values if value is not None)

    @max_length.setter
    def max_length(self, max_length):
        """Mockup to comply with super class' __init__."""
        if max_length is not None:
            LOGGER.warning(
                'Parameter max_length=%s will be ignored since it '
                'is derived from enumeration values.', str(max_length))

    @property
    def null(self):
        """Determines nullability by enum values."""
        return any(value is None for value in self.values)

    @null.setter
    def null(self, null):
        """Mockup to comply with super class' __init__."""
        if null is not None:
            LOGGER.warning(
                'Parameter null=%s will be ignored since it '
                'is derived from enumeration values.', str(null))

    def db_value(self, value):
        """Coerce enumeration value for database."""
        if is_enum(value):
            if value in self.enum:
                return value.value
        elif value in self.values:
            return value

        raise InvalidEnumerationValue(value)

    def python_value(self, value):
        """Coerce enumeration value for python."""
        if is_enum(self.enum):
            for item in self.enum:
                if item.value == value:
                    return item
        elif value in self.values:
            return value

        raise InvalidEnumerationValue(value)
\$\endgroup\$
1
  • \$\begingroup\$ A previous version sadly had some undetected bugs that made the actual code to be considered broken. \$\endgroup\$ – Richard Neumann Oct 12 '17 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.