2012-03-01 23 views
7

Tôi đang sử dụng phần mở rộng khai báo trong SQLAlchemy và tôi nhận thấy một lỗi lạ khi tôi cố gắng lưu một thể hiện của một lớp được ánh xạ với dữ liệu không chính xác (cụ thể là một cột được khai báo với nullable = False với giá trị của None).SQLAlchemy tăng None, gây ra TypeError

Lớp (giản thể):

class User(Base): 
    __tablename__ = 'users' 

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

gây ra lỗi (phiên là một phiên SQLAlchemy):

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

... 

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

Nhìn vào mã gây ra ngoại lệ này, tôi thấy (trong SQLAlchemy. orm.session):

except: 
    transaction.rollback(_capture_exception=True) 
    raise 

Trường hợp ngoại lệ bị bắt trong trường hợp này là sqlalchemy.exc.Oper ationalError. Nếu tôi thay đổi những dòng này thành:

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

thì sự cố sẽ biến mất và OperationalError bị ném thay vì Không. Chẳng phải mã ban đầu có hoạt động trong bất kỳ phiên bản Python nào gần đây không? (Tôi đang sử dụng 2.7.2) Có phải lỗi này bằng cách nào đó cụ thể đối với đơn đăng ký của tôi không?

Python 2.7.2

SQLAlchemy 0.7.5

UPDATE: lỗi dường như là đặc trưng cho ứng dụng của tôi một cách nào đó. Tôi đang gói một eventlet.db_pool với một công cụ SQLAlchemy, mà dường như là nguồn gốc của vấn đề bằng cách nào đó. Chạy thử nghiệm đơn giản của tôi với SQLite trong bộ nhớ hoặc công cụ MySQL cơ bản không có vấn đề này, nhưng với db_pool nó có.

Kiểm tra trường hợp: https://gist.github.com/1980584

Các traceback đầy đủ là:

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

Bạn đang sử dụng phiên bản sqlalchemy nào? – Crast

+0

SQLAlchemy 0.7.5 – robbles

+2

DBAPI là gì (bao gồm cả phiên bản) và bản chất chính xác của lỗi là gì? OperationalError được truyền từ DBAPI. Một bài kiểm tra tái tạo đầy đủ ở đây sẽ là tốt nhất và đính kèm nó như một vé vào http://www.sqlalchemy.org/trac/newticket – zzzeek

Trả lời

5

Dưới đây là những gì tôi đã phát hiện ra:

  • Trường hợp ngoại lệ (một OperationalError) là ok đến khi giao dịch thất bại cuộn lại (trong Session._flush()).
  • Việc cuộn ngược giao dịch được xử lý bởi mysqldb thông qua eventlet.tpool. Cụ thể, gọi là eventlet.tpool.execute, bao gồm việc tạo một số eventlet.Event và gọi phương thức wait của nó.
  • Trong khi chờ đợi, một vài điều liên quan đến chuỗi phức tạp xảy ra, một trong số đó đang kiểm tra ngoại lệ và chuyển nó đến Sự kiện để xử lý. Nó chọn lên các OperationalError mà vẫn còn trong sys.exc_type, và cuối cùng xóa nó trong eventlet.event.hubs.hub.BaseHub.switch.
  • Điều khiển trả về Session._flush và ngoại lệ được nâng lên lại (sử dụng raise), nhưng tại thời điểm này không có ngoại lệ để nó cố gắng raise None.

Hành vi này có thể được sao chép với một ví dụ đơn giản:

from eventlet import tpool 

def m(): 
    pass 

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

của nó một chút không rõ ràng chính xác những gì eventlet nên làm trong tình huống này, vì vậy tôi không biết liệu lỗi nên được báo cáo cho sqlalchemy hoặc eventlet, hoặc cả hai.

Cách đơn giản nhất để khắc phục nó là, như bạn đã lưu ý, để thay đổi một vài dòng cuối cùng của sqlalchemy.orm.session.Session._flush từ

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

để

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

Edit: Tôi đã lớn lên số issue trên trình theo dõi vấn đề của sự kiện. Nó có thể là giá trị nâng nó trên sqlalchemy quá mặc dù.

+1

Làm việc tốt - bây giờ nó có ý nghĩa. Sửa lỗi của tôi đã làm việc vì nó đã ghi lại ngoại lệ, trước khi eventlet xóa nó trong rollback, sau đó nâng nó một cách rõ ràng. Tôi sẽ cố gắng đưa ra một vấn đề về sqlalchemy khi tôi có cơ hội. – robbles

Các vấn đề liên quan