异常中的 SQLAlchemy Unicode 问题

SQLAlchemy Unicode Problems in Exceptions

我正在使用 postgres/SQLAlchemy/Flask-Admin 开发 Flask 应用程序。但是,在 Admin 界面中,由于 unicode(exc) 引发 UnicodeDecodeError,因此无法报告任何包含 Unicode 字母的数据库错误。

我能够将问题定位到 sqlalchemy.exc

class StatementError(SQLAlchemyError):
    ...
    def __unicode__(self):
        return self.__str__()

并重现问题:

class A(Base):
    __tablename__="a"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    name2 = Column(String, nullable=False)

session = Session()
a = A(name=u"עברית")
session.add(a)

try:
    session.commit()
except Exception as e:
    print(repr(e))
    print("------------------")
    print(unicode(e))

哪个returns:

ProgrammingError('(psycopg2.ProgrammingError) column "name" of relation "a" does not exist\nLINE 1: INSERT INTO a (name, name2) VALUES (\'\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa\', NULL) RETURNING...\n                       ^\n',)
------------------
Traceback (most recent call last):
  File "test.py", line 27, in <module>
    print(unicode(e))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd7 in position 118: ordinal not in range(128)

我目前通过用从 utf-8 解码的 类 替换相关异常来解决它。然而,这是一个糟糕的 hack,我正在寻找一个合适的解决方案:

(问题仅与 Python2 有关。在 Python3 中,上面的代码有效。我相信这是因为默认编码是 utf-8

我实际上认为从您的应用程序中修补 SQLAlchemy 是正确的合理干净的解决方案。原因如下:

  • 您发现了一些通常被认为是 SQLAlchemy 中的错误的东西。

  • 您可以编写一个补丁,该补丁在 SQLAlchemy 当前使用的所有情况下都具有相同的行为。也就是说,您的补丁不会破坏现有代码

  • 即使 SQLAlchemy 已修复,您的补丁也无害的可能性非常高。

  • 进行此更改可以减少整个代码中 SQLAlchemy 错误对解决方案的影响,例如更改可能打印异常的每个地方。

  • 将 PostGres 更改为 return latin1 编码实际上无济于事,因为 python 使用的是 ascii 编码,这在给定 latin1 字符串时会产生相同的错误。此外,将 PostGres 更改为 return latin1 错误可能会涉及更改连接编码;这可能会给 unicode 数据带来问题。

这是一个简单的程序,可以修补 sqlalchemy.exc.StatementError 并测试修补程序。如果您愿意,您甚至可以尝试生成一个包含 unicode 的异常,将其转换为 unicode,并且仅在引发 UnicodeDecodeError 时才应用补丁。如果你这样做,当 sqlalchemy 修复问题时,你的补丁将自动停止应用。

# -*- coding: utf-8 -*-
from sqlalchemy.exc import StatementError

def statement_error_unicode(self):
    return unicode(str(self), 'utf-8')
# See <link to sqlalchemy issue>; can be removed once we require a
# version of sqlalchemy with a fix to that issue
StatementError.__unicode__ = statement_error_unicode

message = u'Sqlalchemy unicode '
message_str = message.encode('utf-8')
error = StatementError(message_str, 'select * from users', tuple(), '')
print unicode(error)