2012-03-01 12 views
7

Używam deklaratywnego rozszerzenia w SQLAlchemy, i zauważyłem dziwny błąd, gdy próbowałem zapisać wystąpienie zmapowanej klasy z niepoprawnymi danymi (konkretnie kolumną zadeklarowaną z wartością zerową = False z wartość Brak).SQLAlchemy podnosi None, powoduje TypeError

Klasa (uproszczony):

class User(Base): 
    __tablename__ = 'users' 

    id = Column(Integer, primary_key=True, autoincrement=True) 
    userid = Column(String(50), unique=True, nullable=False) 

przyczyną błędu (sesja jest sesją SQLAlchemy):

>>> u = User() 
>>> session.add(u) 
>>> session.commit() 

... 

TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType 

Patrząc na kod, który wywołuje ten wyjątek, znalazłem (w SQLAlchemy. orm.session)

except: 
    transaction.rollback(_capture_exception=True) 
    raise 

wyjątek wciągnięcia w tym przypadku jest sqlalchemy.exc.Oper ationalError. Jeśli zmienię te linie na:

except Exception as e: 
    transaction.rollback(_capture_exception=True) 
    raise e 

wtedy problem zniknie, a OperationalError zostanie rzucony zamiast None. Czy jednak oryginalny kod nie powinien działać w żadnej najnowszej wersji Pythona? (Używam 2.7.2) Czy ten błąd jest w jakiś sposób specyficzny dla mojej aplikacji?

Python 2.7.2

SQLAlchemy 0.7.5

UPDATE: błąd wydaje się być specyficzne dla mojego wniosku w jakiś sposób. Zawijam eventlet.db_pool z silnikiem SQLAlchemy, który wydaje się być źródłem problemu. Uruchomienie mojego prostego testu za pomocą SQLite w pamięci lub podstawowego silnika MySQL nie ma tego problemu, ale z db_pool to robi. Sprawa

Test: https://gist.github.com/1980584

Pełne traceback jest:

Traceback (most recent call last): 
    File "test_case_9525220.py", line 41, in <module> 
    session.commit() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 645, in commit 
    self.transaction.commit() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 313, in commit 
    self._prepare_impl() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 297, in _prepare_impl 
    self.session.flush() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1547, in flush 
    self._flush(objects) 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1635, in _flush 
    raise 
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType 
+0

Jaką wersję sqlalchemy używasz? – Crast

+0

SQLAlchemy 0.7.5 – robbles

+2

czym jest DBAPI (w tym wersja) i jaka jest dokładna natura błędu? OperationalError jest propagowany z DBAPI. Pełny test odtwarzania byłby najlepszy i dołącz go jako bilet do http://www.sqlalchemy.org/trac/newticket – zzzeek

Odpowiedz

5

Oto co odkryłem:

  • stanowi wyjątek (OperationalError) jest ok, dopóki nieudanej transakcji wycofuje się (w Session._flush()).
  • Cofanie transakcji jest obsługiwane przez mysqldb przez eventlet.tpool. W szczególności wywoływana jest nazwa eventlet.tpool.execute, która polega na utworzeniu eventlet.Event i wywołaniu metody .
  • Podczas oczekiwania kilka skomplikowanych rzeczy związanych z wątkami się dzieje, jeden z nich sprawdza wyjątek i przekazuje go do zdarzenia do obsługi. Odbiera on OperationalError, który nadal znajduje się w sys.exc_type i ostatecznie usuwa go w eventlet.event.hubs.hub.BaseHub.switch.
  • Kontrola powraca do Session._flush, a wyjątek jest ponownie podniesiony (przy użyciu raise), ale w tym momencie nie ma wyjątku, więc próbuje on raise None.

Takie zachowanie może być powielana z prostym przykładzie:

from eventlet import tpool 

def m(): 
    pass 

try: 
    raise TypeError 
except: 
    tpool.execute(m) 
    raise 

Jest nieco niejasny dokładnie co eventlet należy robić w takiej sytuacji, więc nie wiem, czy błąd powinien zostać zgłoszone sqlalchemy lub eventlet, lub oba.

Najprostszym sposobem, aby naprawić to, jak już wspomniano, aby zmienić kilka ostatnich wierszy sqlalchemy.orm.session.Session._flush z

except Exception: 
     transaction.rollback(_capture_exception=True) 
     raise 

do

except Exception, e: 
     transaction.rollback(_capture_exception=True) 
     raise e 

EDIT: Podniosłem issue w narzędziu do śledzenia zdarzeń. Być może warto jednak je także podnieść na sqlalchemy.

+1

Dobra robota - teraz ma sens. Moja poprawka działała, ponieważ zarejestrowała wyjątek, zanim eventlet wyczyścił go w procesie wycofywania, a następnie podniósł go jawnie. Spróbuję zgłosić problem dotyczący sqlalchemy, gdy dostanę szansę. – robbles

Powiązane problemy