如何在sqlalchemy中使用自定义列类型时查找列名称

时间:2016-06-08 08:45:04

标签: python sqlalchemy flask-sqlalchemy

我正在尝试在sqlalchemy中定义自己的列类型:

class NonNegativeInt(TypeDecorator):
    impl = Integer

    def process_bind_param(self, value, dialect):
        if value is not None:
            if not isinstance(value, int) or value < 0:
                raise TypeError('Expect non-negative integer, find %s instead.' % value)
        return value

class Game(Model):
    score = Column(NonNegativeInt, default=0)

当我尝试将负整数绑定到NonNegativeInt列时,例如得分,它会出现预期的错误:

sqlalchemy.exc.StatementError: (exceptions.TypeError) Expect non-negative integer, find -1 instead.

但是,它没有指定列的名称,因此当有很多列时调试并不容易。使用原始的Integer类型时,我得到了更具体的错误消息:

sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1366, u"Incorrect integer value: 'aaaa' for column 'score' at row 1")

如何找到错误列的名称(例如得分)?此外,在分配错误的值时是否可能引发错误:game.score = -1,而不是在提交时?

1 个答案:

答案 0 :(得分:0)

这是使用inspect模块的解决方案。由于它使用了inspect模块,因此对于SA升级(如文档字符串中所述)可能不是很可靠。

此代码假定该值可作为变量keyTypeDecorator.process_bind_param上方两个堆栈级别使用(因此[3]中的索引get_column_name)。如果SA内部的堆栈深度或变量名称发生更改,则此代码将中断,这就是为什么它使用“裸除”的原因。但是,该异常将记录为DEBUG消息(包括堆栈跟踪),因此应保持可调试状态。

但是,我确实认为SA的这一部分不太可能会发生变化。

import logging

LOG = logging.getLogger(__name__)


def get_column_name() -> str:
    """
    Tries to determine the field/column-name from inside a TypeDecorator
    method.

    This is done by inspecting the current Python stack and may not always work
    and is not guaranteed to survive SQLAlchemy upgrades. In that case, it will
    return ``"<unknown>"`` as value so you should not use this for any other
    use-case than debugging or logging!
    """
    from inspect import getmembers, getouterframes, currentframe
    frame = currentframe()
    column_name = '<unknown>'
    try:
        target_frame = getouterframes(frame)[3]
        frame_locals = dict(getmembers(target_frame))['frame'].f_locals
        column_name = frame_locals['key']
    except:  # pylint: disable=bare-except
        LOG.debug('Unable to retrieve the column name', exc_info=True)
    return column_name

然后可以在TypeDecorator内部使用此功能:

class NonNegativeInt(TypeDecorator):
    impl = Integer

    def process_bind_param(self, value, dialect):
        column = get_column_name()
        if value is not None:
            if not isinstance(value, int) or value < 0:
                raise TypeError('Expect non-negative integer in column %r, find %s instead.' % (column, value))
        return value