Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I change column type on SQLAlchemy declarative model dynamically?

I am running mysql in production but would like to run a simple tests in a sqlite in memory db.

The legacy mysql db has tables with columns that are mysql specific types, Which are declared in declarative models (subclassing declarative_base). I would like to run some simple tests without going to mysql and so would need to swap out the columns of the model.

How do I do this? I've tried writing a patcher/unpatcher to swap out table in my model, but when I run some tests, I get

OperationalError: (OperationalError) near ")": syntax error u'\nCREATE TABLE my_table (\n)\n\n' ()

Which makes my think that I am not patching the columns properly.

Does anyone know how I can do this? What am I doing wrong?

Currently, I create new columns and attach brand new Table object to __table__ and save the old table.

The DB is created, create_all() is and convert_columns is run in setUp. drop_all() and revert_columns is run during tearDown in my tests

mysql_sqlite_mapping = {INTEGER: Integer,
                        MEDIUMINT: Integer,
                        TEXT: text}    

def convert_columns(self, my_class, mapping):
    for column in my_class.__table__.columns:
        if type(column.type) in mapping:
            replacement_col = Column(column.name,
                                     mapping[type(column.type)],
                                     primary_key=column.primary_key,
                                     nullable=column.nullable,
                                     key=column.key,
                                     unique=column.unique)

            converted_columns.append(replacement_col)

    self.registry[my_class] = my_class.__table__

    my_class.__table__.metadata.remove(my_class.__table__)
    my_class.__table__ = Table(my_class.__table__.name,
                               my_class.__table__.metadata)

    for column in converted_columns:
        my_class.__table__.append_column(column)

    return my_class

def revert_columns(self, my_class):
    saved_table = self.registry[my_class]

    metadata = my_class.__table__.metadata
    my_class.__table__.metadata.remove(my_class.__table__)

    model_class.__table__ = Table(saved_table.name,
                                  metadata)

    for column in saved_table.columns:
        column.table = None
        my_class.__table__.append_column(column)

    self.registry.pop(my_class)
like image 478
sasker Avatar asked May 11 '12 21:05

sasker


People also ask

What is Declarative base SQLAlchemy?

A base class stores a catlog of classes and mapped tables in the Declarative system. This is called as the declarative base class. There will be usually just one instance of this base in a commonly imported module. The declarative_base() function is used to create base class. This function is defined in sqlalchemy.

What is SQLAlchemy selectable?

The term “selectable” refers to any object that rows can be selected from; in SQLAlchemy, these objects descend from FromClause and their distinguishing feature is their FromClause.

Which DB is used in SQLAlchemy?

Supported Databases. SQLAlchemy includes dialects for SQLite, Postgresql, MySQL, Oracle, MS-SQL, Firebird, Sybase and others, most of which support multiple DBAPIs.


1 Answers

I hope the below might help you with a better way of dealing with these kind of modifications. But your error is weird as it has no columns in the CREATE TABLE clause, so I do not think this is a direct answer/solution to your question.

Anyways, instead of completely replacing the tables and columns, why don't you try to use the Custom SQL Constructs and Compilation Extension / Dialect-specific compilation rules. See the sample code that should generate a proper SQL statement for sqlite without changing the table definitions:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.dialects.mysql import MEDIUMINT, INTEGER, TEXT

@compiles(MEDIUMINT, 'sqlite')
def compile_MEDIUMINT(element, compiler, **kw):
    """ Handles mysql MEDIUMINT datatype as Integer in sqlite.  """
    return compiler.visit_integer(element, **kw)

@compiles(INTEGER, 'sqlite')
def compile_INTEGER(element, compiler, **kw):
    """ Handles mysql INTEGER datatype as Integer in sqlite.  """
    return compiler.visit_integer(element, **kw)


@compiles(TEXT, 'sqlite')
def compile_TEXT(element, compiler, **kw):
    """ Handles mysql TEXT datatype as text in sqlite.  """
    return compiler.visit_text(element, **kw)
like image 147
van Avatar answered Nov 15 '22 05:11

van