Skip to content

Using a TypeDecorator and Getting ModelConversionError: Could not find field converter #148

Description

@msouth

I have a TypeDecorator that I'm using in a SQLAlchemy model. I have it defined like this:

class WebBoolean(TypeDecorator):
    """Coerce 'y' and '' to 1 and 0, before passing off to the database."""

    impl = sqlalchemy.Boolean

    def process_bind_param(self, value, dialect):
        if isinstance(value,str):
            if value == '':
                return 0
            elif value == 'y':
                return 1
        else:
            return value

I called it WebBoolean because I have Boolean from SQLAlchemy imported into my namespace and didn't want to stomp on that. But attempting to run model_form with a model using that gave this error:

wtforms.ext.sqlalchemy.orm.ModelConversionError: Could not find field converter for enter_time_manually (<class 'myapp.model.task_tracker.WebBoolean'>).

I put some debugging lines in, and I think the problem is that it thinks it will see sqlalchemy.Boolean in the type ancestry, but what it sees is sqlalchemy.sql.type_api.TypeDecorator--it seems to me that the fact that it's implemented with sqlalchemy.Boolean is hidden from your detection code.

Here is the "types" list--the first sqlalchemy.* type is TypeDecorator:

(<class 'myapp.model.task_tracker.WebBoolean'>, <class 'sqlalchemy.sql.type_api.TypeDecorator'>, <class 'sqlalchemy.sql.base.SchemaEventTarget'>, <class 'sqlalchemy.sql.type_api.TypeEngine'>, <class 'sqlalchemy.sql.visitors.Visitable'>, <class 'object'>)

Here's where the code is looking for sqlalchemy.Boolean, I think (my debugging line included)

          for col_type in types:
               type_string = '%s.%s' % (col_type.__module__, col_type.__name__)
               logging.debug(f"type string is {type_string}, from dotting module with {col_type.__name__}")
               if type_string.startswith('sqlalchemy'):
                   type_string = type_string[11:]


The workaround of using Boolean instead of WebBoolean for the name is successful because of the else: clause that follows the loop I excerpted above (my debugging line included):

else:
                for col_type in types:
                    logging.debug(f"seeing if col_type's name {col_type.__name__} is in self.converters")
                    if col_type.__name__ in self.converters:
                        converter = self.converters[col_type.__name__]

It's a general workaround, I think, but it requires people to write the original SQLAlchemy names into their namespace, which they can do as long as they have the SQLAlchemy names addressed in another way to avoid collisions.

I'm wondering if maybe something has changed since the documentation (I was reading https://wtforms-alchemy.readthedocs.io/en/latest/column_conversion.html) was written? Here are the versions I'm using:

WTForms-Alchemy==0.16.9
SQLAlchemy==1.3.3
SQLAlchemy-Utils==0.33.11

Path to a solution (in wtforms/ext/sqlalchemy/orm.py ):

             for col_type in types:
                 type_string = '%s.%s' % (col_type.__module__, col_type.__name__)
+                if type_string == 'sqlalchemy.sql.type_api.TypeDecorator':
+                    type_string = str(column.type.impl)
+                    logging.debug(f"I made type_string into [{type_string}], good luck")
                 if type_string.startswith('sqlalchemy'):
                     type_string = type_string[11:]

When I tried this, I got DEBUG:root:I made type_string into [BOOLEAN], good luck--I don't know how likely this is to be a sound solution or how long-term it will work, but you could add that all-caps version to your mapping, potentially, and it would do the right thing here. Would have to be tested for other stuff, I guess.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions