SqlAlchemy将新的Field添加到类中并在表中创建相应的列

时间:2010-01-20 17:01:53

标签: python sqlalchemy

我想在现有的映射类中添加一个字段,如何自动更新sql表。如果将字段添加到类中,sqlalchemy是否提供了使用新列更新数据库的方法。

6 个答案:

答案 0 :(得分:11)

有时候迁移工作太多了 - 您只想在运行更改的代码时自动添加列。所以这是一个功能。

警告:它在SQLAlchemy内部发现并且每次SQLAlchemy经历重大修订时都需要进行小的更改。 (这可能有更好的方法 - 我不是SQLAlchemy专家)。它也不处理约束。

import logging
import re

import sqlalchemy
from sqlalchemy import MetaData, Table, exceptions
import sqlalchemy.engine.ddl

_new_sa_ddl = sqlalchemy.__version__.startswith('0.7')


def create_and_upgrade(engine, metadata):
    """For each table in metadata, if it is not in the database then create it. 
    If it is in the database then add any missing columns and warn about any columns
    whose spec has changed"""
    db_metadata = MetaData()
    db_metadata.bind = engine

    for model_table in metadata.sorted_tables:
        try:
            db_table = Table(model_table.name, db_metadata, autoload=True)
        except exceptions.NoSuchTableError:
            logging.info('Creating table %s' % model_table.name)
            model_table.create(bind=engine)
        else:
            if _new_sa_ddl:
                ddl_c = engine.dialect.ddl_compiler(engine.dialect, None)
            else:
                # 0.6
                ddl_c = engine.dialect.ddl_compiler(engine.dialect, db_table)
            # else:
                # 0.5
                # ddl_c = engine.dialect.schemagenerator(engine.dialect, engine.contextual_connect())

            logging.debug('Table %s already exists. Checking for missing columns' % model_table.name)

            model_columns = _column_names(model_table)
            db_columns = _column_names(db_table)

            to_create = model_columns - db_columns
            to_remove = db_columns - model_columns
            to_check = db_columns.intersection(model_columns)

            for c in to_create:
                model_column = getattr(model_table.c, c)
                logging.info('Adding column %s.%s' % (model_table.name, model_column.name))
                assert not model_column.constraints, \
                    'Arrrgh! I cannot automatically add columns with constraints to the database'\
                        'Please consider fixing me if you care!'
                model_col_spec = ddl_c.get_column_specification(model_column)
                sql = 'ALTER TABLE %s ADD %s' % (model_table.name, model_col_spec)
                engine.execute(sql)

            # It's difficult to reliably determine if the model has changed 
            # a column definition. E.g. the default precision of columns
            # is None, which means the database decides. Therefore when I look at the model
            # it may give the SQL for the column as INTEGER but when I look at the database
            # I have a definite precision, therefore the returned type is INTEGER(11)

            for c in to_check:
                model_column = model_table.c[c]
                db_column = db_table.c[c]
                x =  model_column == db_column

                logging.debug('Checking column %s.%s' % (model_table.name, model_column.name))
                model_col_spec = ddl_c.get_column_specification(model_column)
                db_col_spec = ddl_c.get_column_specification(db_column)

                model_col_spec = re.sub('[(][\d ,]+[)]', '', model_col_spec)
                db_col_spec = re.sub('[(][\d ,]+[)]', '', db_col_spec)
                db_col_spec = db_col_spec.replace('DECIMAL', 'NUMERIC')
                db_col_spec = db_col_spec.replace('TINYINT', 'BOOL')

                if model_col_spec != db_col_spec:
                    logging.warning('Column %s.%s has specification %r in the model but %r in the database' % 
                                       (model_table.name, model_column.name, model_col_spec, db_col_spec))

                if model_column.constraints or db_column.constraints:
                    # TODO, check constraints
                    logging.debug('Column constraints not checked. I am too dumb')

            for c in to_remove:
                model_column = getattr(db_table.c, c)
                logging.warning('Column %s.%s in the database is not in the model' % (model_table.name, model_column.name))


def _column_names(table):
    # Autoloaded columns return unicode column names - make sure we treat all are equal
    return set((unicode(i.name) for i in table.c)) 

答案 1 :(得分:5)

SQLAlchemy本身不支持架构的自动更新,但有一个第三方SQLAlchemy Migrate工具可以自动执行迁移。看看"Database schema versioning workflow" chapter,看它是如何运作的。

答案 2 :(得分:3)

Alembic是提供数据库迁移的最新软件包。

请参阅sqlalchemy docs regarding migration here

答案 3 :(得分:0)

# database.py has definition for engine.
# from sqlalchemy import create_engine
# engine = create_engine('mysql://......', convert_unicode=True)

from database import engine
from sqlalchemy import DDL

add_column = DDL('ALTER TABLE USERS ADD COLUMN city VARCHAR(60) AFTER email')
engine.execute(add_column)

答案 4 :(得分:0)

可以与sqlalchemy-migrate一起使用,但实际上不使用迁移:

sqlalchemy.MetaData(bind=dbinterface.db.engine)

table = sqlalchemy.schema.Table(table_name, meta_data)

try:
    col = sqlalchemy.Column('column_name', sqlalchemy.String)
    col.create(table)
except Exception as e:
    print "Error adding column: {}".format(e)

要与python 3一起使用,我需要sqlalchemy-migrate == 0.12.0。

答案 5 :(得分:0)

您可以安装“数据库浏览器(SQLite)”并在数据库中打开当前数据库文件和简单的添加/编辑表,然后保存并运行您的应用 (保存上述过程后,在模型中添加脚本)