2017-10-18 32 views
3

J'ai DB Postgres avec une table des opérations en attente. Une colonne dans l'opération dans une énumération avec le statut de l'énumération. J'ai utilisé le python standard (2.7) enum, avec NuméroAuto (myenum.py):SQLAlchemy enum dans le fichier externe

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 

Le tableau ressemble (aussi 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 

En général, cela fonctionne très bien. Dans le même fichier qui définit MyStatus (myoper.py), je peux changer le statut et enregistrez-le revenir à la DB et il fonctionne très bien:

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

Voilà comment je l'appelle (au sein myoper.py)

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

Tout cela se trouve dans le même fichier (myoper.py). Cependant, si je passe un objet Oper à une fonction externe et que IT change l'état, j'obtiens un sqlalchemy.exc.StatementError.

est ici la fonction externe (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() 

Voilà comment je l'appelle (de myoper.py):

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

Voici la trace de la pile:

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}]] 

J'ai essayé d'inspecter le type à la fois dans le fichier interne et dans le fichier externe, mais ses manières sont répertoriées sous la forme <enum 'MyStatus'>.

j'ai trouvé, que si je cède la oper.status au .name enum, puis qui fonctionne:

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

Mais c'est évidemment assez laid.

Alors, qu'est-ce que je fais de mal? Qu'est-ce qui différencie MyStatus dans le fichier dans lequel il est défini, par rapport à un fichier externe qui bousille SQL Alchemy?

+0

python2 ne possède pas de type enum intégré, utilisez-vous enum34 package? – georgexsh

+0

oui, c'est enum34. – TazMainiac

Répondre

1

J'ai posté cette question dans la liste de diffusion SQL Alchemy et j'ai obtenu une réponse. Link to thread

Il s'avère que c'est l'un de ces "trucs" sur python et n'a rien à voir avec SQL Alchemy. Voici une référence: Executing Main Module Twice.

Dans ce cas particulier, lorsque j'ai exécuté mon script, MyStatus a été créé avec un identifiant particulier (handle python sur le type). Mais quand myoper_test a importé MyStatus de myoper, il a été créé à nouveau avec un identifiant différent.

Ainsi, lorsque le extCheckOper attribué une valeur MyStatus au champ d'état, il était un différents MyStatus que SQL Alchemy a créé le mappage DB avec, donc quand SQL Alchemy a essayé de l'enregistrer dans la base de données, le « est » opérateur Échec, car MyStatus (externe) était différent de MyStatus (original).

Il existe plusieurs solutions de contournement.Une façon est de ne pas exécuter le code comme principal (après le déplacement sortant code principal dans la fonction principale()):

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

La meilleure solution est d'éviter ce problème tout à fait - déplacer le code qui appelle l'extérieur à un script de test interne. Le code principal reste principalement dans le script principal (désolé, ne pouvait pas résister ... :-)).

+0

c'est génial d'apprendre ça. – georgexsh