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.
I have a TypeDecorator that I'm using in a SQLAlchemy model. I have it defined like this:
I called it
WebBooleanbecause I haveBooleanfrom 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:I put some debugging lines in, and I think the problem is that it thinks it will see
sqlalchemy.Booleanin the type ancestry, but what it sees issqlalchemy.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:Here's where the code is looking for
sqlalchemy.Boolean, I think (my debugging line included)The workaround of using
Booleaninstead ofWebBooleanfor the name is successful because of theelse:clause that follows the loop I excerpted above (my debugging line included):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:
Path to a solution (in
wtforms/ext/sqlalchemy/orm.py):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.