Issue: Enum field support
Author: foxx
State: closed
Created: 2015-06-18 14:12:43

I've had a look around but can't seem to find any reference to Peewee supporting `enum` fields, other than a short mailing list [thread](https://groups.google.com/forum/#!topic/peewee-orm/jev6II7sN4Q) with no outcome, and a user contributed [recipe](https://gist.github.com/b1naryth1ef/607e92dc8c1748a06b5d) for Postgres which is untested. There are also [arguments](http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/) for _not_ using enum.

All the supported Peewee databases have support for enum fields, and a quick look at the source indicates it would be trivial to add it in.

Is there any reason why enum fields have not been added yet? Would you consider accepting a PR for this?


I have not added enums because I personally have always used an `IntegerField` with a set of defined values. My blog, for instance, stores an entry's status as an integer such that 1=published, 2=draft, 9=deleted.

Python does not natively support an enum data-type until 3.4, either, so there is another reason against it. For Python versions 2.x and less than 3.4, how would Peewee users represent enums in their application code?

Lastly, adding a custom field is pretty trivial and [there are docs describing how](http://docs.peewee-orm.com/en/latest/peewee/models.html#creating-a-custom-field).

> All the supported Peewee databases have support for enum fields, and a quick look at the source indicates it would be trivial to add it in.

SQLite does not natively support enums as far as I know. Yeah you can use a convention, or create a special table and use foreign keys, but I wouldn't go so far as to say SQLite supports enums.

> Would you consider accepting a PR for this?

How would you go about implementing this? What would the Python side look like?


I'll just repeat:

> Would you consider accepting a PR for this?

How would you go about implementing this? What would the Python side look like?


I have to admit, normally I opt for the same `IntegerField` approach. The only real advantage to enum seems to be string representation and value enforcement, however I'd argue that the majority of applications _should_ be doing validation in the app code rather than the database (with the exception being applications which enforce everything in the DB directly via stored procedures etc).

Given that even sqlite3 doesn't support enum, and the additional code complexity involved, I think it can be argued that there is little value in peewee supporting it either. I'll close this issue out for now, but at least there is some discussion and reasoning behind not adding it, for anyone who comes up against this in future.

Thank you again for your input, much appreciated


A bit late on this - but here is one option that will convert the values on their way into the db, and back on their way out.

```
# Define a mapping of database values : output 
FRUIT_CHOICES = [(1, "apple"),
                 (2, "orange"),
                 (3, "banana")]


class enum_like_field(IntegerField):
    def __init__(self, choices, *args, **kwargs):
        self.to_db = {v:k for k, v in choices}
        self.from_db = {k:v for k, v in choices}
        super(IntegerField, self).__init__(*args, **kwargs)

    def db_value(self, value):
        return self.to_db[value]

    def python_value(self, value):
        return self.from_db[value]

class my_table(Model):
    fruit = enum_like_field(null=False, choices=FRUIT_CHOICES)
```

This code works good for Python 3.4+ enum for MySQL

```
class enum_field(CharField):
    """
    This class enable a Enum like field for Peewee
    """
    def __init__(self, choices, *args, **kwargs):
        self.choices = choices
        super(CharField, self).__init__(*args, **kwargs)

    def db_value(self, value):
        return value.name

    def python_value(self, value):
        return self.choices(value)

class my_table(Model):
    class StatusChoices(Enum):
        WAITING = 1
        READY_TO_SEND = 2
        UPLOADING = 3
        OCR = 4
        DONE = 5

    status = enum_like_field(null=False, choices=StatusChoices)

```



I think there is a mistake in the above snippet, this:

```python
    def db_value(self, value):
        return value.name
```

Should be:

```python
    def db_value(self, value):
        return value.value
```

I also found some mistake. 
The Problem is that the top base class `Field` has in `__init__` set default value for the `choices=None` so calling the super after setting the instance self to some value will because of this base class reset it to None. 

One of the solution can be calling the super before setting the `choices` or using another member name.  


 

Hey sorry for the silly question but is this available in peewee 3.8.0? I can't find the class `EnumField` and there is no reference of it inside the Changelog nor the official documentation.

No, `EnumField` is currently something you'll need to implement.

the above code has some bugs and I created this and this works properly:

```
class EnumField(IntegerField):
	"""
	This class enable an Enum like field for Peewee
	"""
	def __init__(self, choices, *args, **kwargs):
		super(IntegerField, self).__init__(*args, **kwargs)
		self.choices = choices

	def db_value(self, value):
		return value.value

	def python_value(self, value):
		return self.choices(value)
```

@alistvt which kind of issues? What if my Enum is using strings instead of integers, like this?

```python
class OverclockProfileType(Enum):
    DEFAULT = 'default'
    OFFSET = 'offset'
```

Yet another version of the `EnumField`. This version stores the enum value as `CharField` but restores the correct Enum type when reading it:

```python
class EnumField(CharField):
    """
    This class enable an Enum like field for Peewee
    """

    def __init__(self, choices: Callable, *args: Any, **kwargs: Any) -> None:
        super(CharField, self).__init__(*args, **kwargs)
        self.choices = choices
        self.max_length = 255

    def db_value(self, value: Any) -> Any:
        return value.value

    def python_value(self, value: Any) -> Any:
        return self.choices(type(list(self.choices)[0].value)(value))
``` 

Here is my solution for peewee 3 Postgres https://gist.github.com/novaleksey/187e43660c4e2e56f7b972cf982751e1

@leinardi Thank you for your implementation. What do you think about even simpler version?
```
class EnumField(peewee.CharField):
    """
    This class enable an Enum like field for Peewee
    """

    def __init__(self, enum: type[Enum], *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.enum = enum

    def db_value(self, value: Any) -> Any:
        return value.value

    def python_value(self, value: Any) -> Any:
        return self.enum(value)
```