Repository: marshmallow-code/marshmallow-sqlalchemy

Issue:
marhsmallow auto generated field using `enum.Enum` raises error on load
Hi,

When I define SQLAlchemy columns using `enum.Enum` the auto generated marshmallow field fails validation when I try to load:

```python
import enum
import marshmallow as ma
import sqlalchemy as sa
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from sqlalchemy.orm import declarative_base, scoped_session, sessionmaker

engine = sa.create_engine("sqlite:///:memory:")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()

class ColorEnum(str, enum.Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

class Item(Base):
    __tablename__ = 'items'
    id = sa.Column(sa.Integer, primary_key=True)
    color = sa.Column(sa.Enum(ColorEnum))

class ItemSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Item
        load_instance = True

data = {'color': 'RED'}
item = ItemSchema().load(data, session=session) 
```

This raises `marshmallow.exceptions.ValidationError: {'color': ['Must be one of: RED, GREEN, BLUE.']}`

If I define a Enum field explicitly on `ItemSchema`:
```python
color = ma.fields.Enum(ColorEnum)
```
It loads correctly.

Also works if I define the enum using `sa.Enum` with strings on `Item`:
```python
color = sa.Column(sa.Enum("RED", "GREEN", "BLUE"))
```

After #611, if I understood correctly, I shouldn't need to define the field explicitly, or is there something I'm missing?
Thank you!

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 7f0037b..e0089e9 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,6 +8,11 @@ Features:
 
 * Typing: Add type annotations to `fields <marshmallow_sqlalchemy.fields>`.
 
+Bug fixes:
+
+* Fix auto-generation of `marshmallow.fields.Enum` field from `sqlalchemy.Enum` columns (:issue:`615`).
+  Thanks :user:`joaquimvl` for reporting.
+
 Other changes:
 
 * Docs: Add more documentation for `marshmallow_sqlalchemy.fields.Related` (:issue:`162`).
diff --git a/src/marshmallow_sqlalchemy/convert.py b/src/marshmallow_sqlalchemy/convert.py
index 2307789..62ae6b1 100644
--- a/src/marshmallow_sqlalchemy/convert.py
+++ b/src/marshmallow_sqlalchemy/convert.py
@@ -428,11 +428,10 @@ class ModelConverter:
         else:
             kwargs["dump_only"] = True
 
-        if hasattr(column.type, "enums") and not kwargs.get("dump_only"):
-            kwargs["validate"].append(validate.OneOf(choices=column.type.enums))
-
         if hasattr(column.type, "enum_class") and column.type.enum_class is not None:
             kwargs["enum"] = column.type.enum_class
+        elif hasattr(column.type, "enums") and not kwargs.get("dump_only"):
+            kwargs["validate"].append(validate.OneOf(choices=column.type.enums))
 
         # Add a length validator if a max length is set on the column
         # Skip UUID columns


Test patch:
diff --git a/tests/test_conversion.py b/tests/test_conversion.py
index e6af28e..c43fc1b 100644
--- a/tests/test_conversion.py
+++ b/tests/test_conversion.py
@@ -20,7 +20,7 @@ from marshmallow_sqlalchemy import (
 )
 from marshmallow_sqlalchemy.fields import Related, RelatedList
 
-from .conftest import mapped_column
+from .conftest import CourseLevel, mapped_column
 
 
 def contains_validator(field, v_type):
@@ -71,17 +71,18 @@ class TestModelFieldConversion:
         fields_ = fields_for_model(models.Student)
         assert fields_["dob"].allow_none is True
 
-    def test_sets_enum_choices(self, models):
+    def test_enum_with_choices_converted_to_field_with_validator(self, models):
         fields_ = fields_for_model(models.Course)
         validator = contains_validator(fields_["level"], validate.OneOf)
         assert validator
         assert list(validator.choices) == ["Primary", "Secondary"]
 
-    def test_sets_enum_with_class_choices(self, models):
+    def test_enum_with_class_converted_to_enum_field(self, models):
         fields_ = fields_for_model(models.Course)
-        validator = contains_validator(fields_["level_with_enum_class"], validate.OneOf)
-        assert validator
-        assert list(validator.choices) == ["PRIMARY", "SECONDARY"]
+        field = fields_["level_with_enum_class"]
+        assert type(field) is fields.Enum
+        assert contains_validator(field, validate.OneOf) is False
+        assert field.enum is CourseLevel
 
     def test_many_to_many_relationship(self, models):
         student_fields = fields_for_model(models.Student, include_relationships=True)