2011-09-25 14 views
7

kurtarmaya çalışırken sonra modelin benzersiz alanını yeniden kaydetmek, aşağıdaki modeli vardır:SQLAlchemy: Benim SQLAlchemy uygulamasında olmayan benzersiz bir değer

from sqlalchemy import Column, String 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import scoped_session, sessionmaker 
from zope.sqlalchemy import ZopeTransactionExtension 

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) 

class MyModel(declarative_base()): 
    # ... 
    label = Column(String(20), unique=True) 

    def save(self, force=False): 
     DBSession.add(self) 
     if force: 
      DBSession.flush() 

Daha sonra kodda ben rastgele label oluşturmak istediğiniz her yeni MyModel nesneler için ve oluşturulan değer zaten DB'de mevcutsa, yalnızca yeniden oluşturun. İlk label benzersiz değil oluşturulur ve save() ikinci (veya üstü) yineleme üzerinde çağrıldığında durumunda bu hatayı

# my_model is an object of MyModel 
while True: 
    my_model.label = generate_label() 
    try: 
     my_model.save(force=True) 
    except IntegrityError: 
     # label is not unique - will do one more iteration 
     # (*) 
     pass 
    else: 
     # my_model saved successfully - exit the loop 
     break 

ama olsun:

InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (IntegrityError) column url_label is not unique... 


aşağıdaki yapmaya çalışıyorum Ben pozisyon (*) içinde DBSession.rollback() eklediğinizde bu alın:

ResourceClosedError: The transaction is closed 

ne yapmalıyım Bu durumu doğru şekilde ele almak için?
Teşekkür

+0

"declarative_base()" işlevinin dönüş değerini bir değişkene atamalısınız. Aksi takdirde, onlar için farklı temel sınıflara sahip olabileceğinizden birden fazla model oluştururken sorun yaşayacaksınız. – ThiefMaster

cevap

5

session nesnesiniz geri dönüyorsa, yeniden başlatabilmeniz için yeni bir oturum oluşturmanız ve modellerinizi yenilemeniz gerekir. Ve eğer zope.sqlalchemy kullanıyorsan, kontrol etmek için transaction.commit() ve transaction.abort() kullanıyor olmalısın. Burada nesnenin save yöntemin dışına oturum nesnesi kullanımını çekti ettik

# you'll also need this import after your zope.sqlalchemy import statement 
import transaction 

while True: 
    my_model.label = generate_label() 
    try: 
     transaction.commit() 
    except IntegrityError: 
     # need to use zope.sqlalchemy to clean things up 
     transaction.abort() 
     # recreate the session and re-add your object 
     session = DBSession() 
     session.add(my_model) 
    else: 
     break 

: Yani döngü böyle bir şey olmazdı. Yaptığınız gibi sınıf seviyesinde kullanıldığında, ScopedSession'un kendini nasıl yenilediğinden tam olarak emin değilim. Şahsen, ben senin modelleri içinde SqlAlchemy şeyler gömme sanırım gerçekten herhangi bir şey SqlAlchemy'nin unit of work yaklaşımı ile iyi çalışmıyor.

Etiket nesnesinin gerçekten üretilmiş ve benzersiz bir değeri varsa, TokenMacGuy ile hemfikir olurum ve sadece uuid değerini kullanın.

Bu yardımcı olur umarım.

+0

bir göz atacak ScopedSession bir iş parçacığı yerel depolama modeli kullanır; oturum açıkça (ScopedSession.reset() 'aracılığıyla) geçersiz kılınır, ancak bu genellikle isteğin çerçeveye kontrolünü döndürdüğünüzde size oturum sağlayan çerçeve tarafından halledilir. Bu tür bir iplik modelini kullanamıyorsanız, çerçeve yardımcı olduğunda, ancak gerçek bir baş ağrısı olduğunda kolaylık olur. Mulithreaded bir çerçeve tasarlamadıkça, scopedesssion istediğiniz gibi değildir. – SingleNegationElimination

+0

@TokenMacGuy - Sanırım, ScopedSession'ın aslında iş parçacığı üzerinde genel bir nesne olması, yani istek döngüsünün sonunda açıkça temizlemesi (btw = ScopedSession.remove() 'da 0.6)/7) ek bir endişe haline gelir. –

+0

Sağ; Bir iş biriminde bir iş parçacığınız varsa, ScopedSession * herhangi bir şekilde kolayca çiftleştirilemeyen bileşenler için işleri basitleştirebilir; Ancak birçok durumda bir seansı global TLS konteynırdan başka bir yöntemle enjekte etmek ya da uow başına iplik mümkün değildir ve ScopedSession hiç size yardımcı olmaz. Bu yeni başlayanlar için ortak bir karışıklık noktası gibi görünüyor; Kapsamlı oturum UOW biraz büyülü yapar ve uygulama çerçeve geliştiricilerin beklentisi ile aynı hizaya gelmediğinde uygulama hata ayıklama zor hale gelir – SingleNegationElimination

2

Veritabanları bir işlem neden başarısız otomasyon erişebileceği bir formda, size söylemek tutarlı bir yol yoktur. İşlemi genellikle denemeyin ve belirli bir nedenle için başarısız olduğundan yeniden deneyin.

Etrafında çalışmak istediğiniz bir durumu biliyorsanız (benzersiz bir kısıtlama gibi), yapmanız gereken kısıtlamayı kendiniz kontrol etmektir. sqlalchemy olarak, bu şuna benzer olacak:

# Find a unique label 
label = generate_label() 
while DBsession.query(
     sqlalchemy.exists(sqlalchemy.orm.Query(Model) 
        .filter(Model.lable == label) 
        .statement)).scalar(): 
    label = generate_label() 

# add that label to the model 
my_model.label = label 
DBSession.add(my_model) 
DBSession.flush() 

düzenleme: Bu otomatik işlem yeniden gerektiğidir cevaplamak için başka bir yolu; Bunun yerine, işlemin gerçekten başlatılmaya başlaması için 307 Temporary Redirect (Yönlendirilmiş URL'de bir miktar tuz ile) bir HTTP durum kodu döndürebilirsiniz.

+0

Evet, kısıtlamayı kendim kontrol etmeyi düşündüm, ama sorun şu ki, DB'de yeni oluşturduğum ve depolayacağım aynı değerin o DB'de üretme ve depolama anları arasında görünmeyeceğinin bir garantisi yok. İşlemin neden başarısız olduğunu nasıl anlayacağımı sormadım, oturumu doğru şekilde nasıl "onaracağımı" soruyorum. –

+0

Bir atom dizisi veya genel olarak benzersiz bir anahtar kullanmayı düşünmelisiniz; Çoğu veri tabanı bir çeşit sırayı destekler (Örneğin, MySQL'in AUTOINCREMENT vardır). Bu sizin için mantıklı bir seçenek değilse, benzersiz bir kimlik için yüksek olasılıkla 'uuid' modülü tarafından oluşturulan bir kimlik kullanabilirsiniz. – SingleNegationElimination

+0

Teşekkürler, uuid modülü –

2

Webramda Pyramid çerçevesinde yazılmış benzer bir sorunla karşılaştım. Bu problem için biraz farklı bir çözüm buldum.my_model önce depolanmış ise

while True: 
    try: 
     my_model.label = generate_label() 
     DBSession.flush() 
     break 
    except IntegrityError: 
     # Rollback will recreate session: 
     DBSession.rollback() 
     # if my_model was in db it must be merged: 
     my_model = DBSession.merge(my_model) 

birleştirme parçası çok önemlidir. Birleştirme oturumu boş bırakılmadan, bu yüzden herhangi bir işlem yapmaz.

+1

Sadece bir not: pyramid_tm kullanan bir Pyramid uygulamanız varsa DBSession.rollback() yerine transaction.abort() kullanmak daha iyidir – Joril