【问题标题】:SQLAlchemy enum in external file外部文件中的 SQLAlchemy 枚举
【发布时间】:2017-10-18 20:38:50
【问题描述】:

我有一个带有待处理操作表的 Postgres DB。枚举中操作中的一列,其中包含枚举的状态。我使用了标准的 python (2.7) 枚举,带有 AutoNumber (myenum.py):

class AutoNumber(enum.Enum):
    def __new__(cls):
        value = len(cls.__members__) + 1
        obj = object.__new__(cls)
        obj._value_ = value
        return obj

class MyStatus(AutoNumber):

    INITIAL = ()
    ACCEPTED = ()
    DENIED = ()
    ACK_PENDING = ()
    AUTHORIZED = ()
    ACTIVE = ()
    END = ()
    DELETED = ()
# end enum

表格看起来像(也在 myenum.py 中):

Base = declarative_base()

class MyOperation(Base):

    __tablename__ = 'operations'

    id  = Column( Integer, primary_key=True )

    status = Column( Enum(MyStatus) )
    status_message = Column( String )
    status_time = Column( DateTime )

    def __repr__(self):
        return "<MyOperation(%s, %s, %s, %s)>" % \
            ( self.id, self.status, self.status_time, self.status_message )
# end class

通常这工作正常。在定义 MyStatus (myoper.py) 的 SAME FILE 中,我可以更改状态并将其保存回数据库,它工作正常:

def checkOper( oper ):
    oper.status = MyStatus.DENIED
    oper.status_message = "failed check (internal)"
    oper.status_time = datetime.datetime.utcnow()

我是这样称呼它的(在 myoper.py 中)

    checkOper( oper )
    session.add(oper)
    session.commit()

这些都在同一个文件 (myoper.py) 中。

但是,如果我将 oper 对象传递给外部函数,并且 IT 更改了状态,则会收到 sqlalchemy.exc.StatementError。

这是外部函数(myoper_test.py):

import datetime
from myoper import MyStatus

def extCheckOper( oper ):
    oper.status = MyStatus.DENIED
    oper.status_message = "failed check (external)"
    oper.status_time = datetime.datetime.utcnow()

我是这样称呼它的(来自 myoper.py):

    from myoper_test import extCheckOper
    extCheckOper( oper )
    session.add(oper)
    session.commit()

这是堆栈跟踪:

Traceback (most recent call last):
  File "./myoper.py", line 120, in <module>
    session.commit()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 906, in commit
    self.transaction.commit()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 461, in commit
    self._prepare_impl()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 441, in _prepare_impl
    self.session.flush()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 2177, in flush
    self._flush(objects)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 2297, in _flush
    transaction.rollback(_capture_exception=True)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/util/langhelpers.py", line 66, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 2261, in _flush
    flush_context.execute()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/unitofwork.py", line 389, in execute
    rec.execute(self)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/unitofwork.py", line 548, in execute
    uow
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/persistence.py", line 177, in save_obj
    mapper, table, update)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/persistence.py", line 737, in _emit_update_statements
    execute(statement, multiparams)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 945, in execute
    return meth(self, multiparams, params)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/elements.py", line 263, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1053, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1121, in _execute_context
    None, None)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
    exc_info
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1116, in _execute_context
    context = constructor(dialect, self, conn, *args)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/default.py", line 639, in _init_compiled
    for key in compiled_params
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/default.py", line 639, in <genexpr>
    for key in compiled_params
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/sqltypes.py", line 1446, in process
    value = self._db_value_for_elem(value)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/sqltypes.py", line 1354, in _db_value_for_elem
    '"%s" is not among the defined enum values' % elem)
sqlalchemy.exc.StatementError: (exceptions.LookupError) "MyStatus.DENIED" is not among the defined enum values [SQL: u'UPDATE operations SET status=%(status)s, status_message=%(status_message)s, status_time=%(status_time)s WHERE operations.id = %(operations_id)s'] [parameters: [{'status': <MyStatus.DENIED: 6>, 'status_time': datetime.datetime(2017, 10, 18, 20, 22, 44, 350035), 'status_message': 'failed check (external)', 'operations_id': 3}]]

我已经尝试检查内部文件和外部文件中的类型,但它的方式被列为&lt;enum 'MyStatus'&gt;

我发现,如果我将 oper.status 分配给枚举 .name,那么它确实有效:

def extCheckOper( oper ):
    oper.status = MyStatus.AUTHORIZED.name
    oper.status_message = "authorized check (external)"
    oper.status_time = datetime.datetime.utcnow()

但这显然很丑。

那么 - 我做错了什么?定义 MyStatus 的文件与破坏 SQL Alchemy 的外部文件有何不同?

【问题讨论】:

  • python2 没有内置枚举类型,你使用的是 enum34 包吗?
  • 是的,这是 enum34。

标签: python postgresql enums sqlalchemy


【解决方案1】:

我将此问题发布到 SQL Alchemy 邮件列表并得到了答案。 Link to thread

原来这是关于 python 的那些“陷阱”之一,与 SQL Alchemy 没有任何关系。这是一个参考:Executing Main Module Twice

在这种特殊情况下,当我执行我的脚本时,MyStatus 是使用特定 id(类型上的 python 句柄)创建的。但是当 myoper_test 从 myoper 导入 MyStatus 时,它又被创建了一个不同的 id。

因此,当 extCheckOper 将 MyStatus 值分配给状态字段时,它是一个 不同 MyStatus,而不是 SQL Alchemy 创建数据库映射时使用的,所以当 SQL Alchemy 尝试将其保存到数据库时, “is”运算符失败,因为(外部)MyStatus 与(原始)MyStatus 不同。

有几种不同的解决方法。一种方法是不将代码作为 main 运行(在将退出 ma​​in 代码移动到 main() 函数之后):

$ python -c "from myoper import main; import sys; main(*sys.argv[1:])" ext_check 1

更好的解决方案是完全避免此问题 - 将外部调用的代码移至内部测试脚本。 ma​​in 中的代码主要保留在主脚本中(抱歉,无法抗拒... :-))。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-09
    • 2017-08-17
    • 2010-12-30
    • 1970-01-01
    相关资源
    最近更新 更多