Search code examples
pythonmysqlencryptionsqlalchemyalembic

VARCHAR length error when using StringEncryptedType, SQLAlchemy


I have the following model:

from enum import Enum
from sqlalchemy_utils.types.encrypted.encrypted_type import StringEncryptedType
from sqlalchemy import (
    Column,
    Integer,
    Float,
    Enum as SQAEnum,
    String
)
from backend.db import Base
from backend.config import Config


class AuthProviders(Enum):
    google = "Google"
    facebook = "Facebook"
    vest = "Vest"


class Users(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, index=True)
    hashed_password = Column(StringEncryptedType(String(255), Config.MYSQL_ENCRYPT_KEY))
    auth_provider = Column(SQAEnum(AuthProviders))
    timestamp = Column(Float(precision=32))

which alembic uses to generate the following migration script:

from alembic import op
import sqlalchemy as sa
from sqlalchemy_utils.types.encrypted.encrypted_type import StringEncryptedType

from backend.config import Config

# revision identifiers, used by Alembic.
revision = 'b919cf558155'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('users',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('email', sa.String(length=255), nullable=True),
    sa.Column('hashed_password', StringEncryptedType(sa.String(255), Config.MYSQL_ENCRYPT_KEY), nullable=True),
    sa.Column('auth_provider', sa.Enum('google', 'facebook', 'vest', name='authproviders'), nullable=True),
    sa.Column('timestamp', sa.Float(precision=32), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f('ix_users_email'), table_name='users')
    op.drop_table('users')
    # ### end Alembic commands ###

I added the line from sqlalchemy_utils.types.encrypted.encrypted_type import StringEncryptedType and edited the hashed_password column entry so that it matches the models file.

When I run the DB upgrade I'm thrown the following error:

sqlalchemy.exc.CompileError: (in table 'users', column 'hashed_password'): VARCHAR requires a length on dialect mysql

I'm confused by this, because I've defined the length of the String that's being encrypted. Replacing String with VARCHAR doesn't solve the problem.

The __init__ for for StringEncryptedType is pretty straight-forward:

def __init__(self, type_in=None, key=None, engine=None, padding=None, **kwargs):

Apparently EncryptedType for strings is deprecated in favor of StringEncryptedType, but having a hard time making the switch. What else needs to happen here?


Solution

  • Passing the String column type and a separate length keyword argument seems to work.

    This model

    class Users(Base):
        __tablename__ = "users20201212a"
    
        id = Column(Integer, primary_key=True)
        email = Column(String(255), unique=True, index=True)
        hashed_password = Column(StringEncryptedType(String, length=255, key='abc'))
        auth_provider = Column(SQAEnum(AuthProviders))
        timestamp = Column(Float(precision=32))
    

    generates this DDL

    CREATE TABLE users20201212a (
            id INTEGER NOT NULL AUTO_INCREMENT, 
            email VARCHAR(255), 
            hashed_password VARCHAR(255), 
            auth_provider ENUM('google','facebook','vest'), 
            timestamp FLOAT(32), 
            PRIMARY KEY (id)