2012-08-19 25 views
14

मेरे पास एक SQLAlchemy मॉडल है जो फ़ाइल का प्रतिनिधित्व करता है और इस प्रकार एक वास्तविक फ़ाइल का पथ होता है।कुछ कोड निष्पादित करें जब एक SQLAlchemy ऑब्जेक्ट का विलोपन वास्तव में किया जाता है

def delete(self): 
    if os.path.exists(self.path): 
     os.remove(self.path) 
    db.session.delete(self) 

यह ठीक काम करता है, लेकिन एक बहुत बड़ा है: मैं अपने मॉडल वर्ग के लिए एक delete() विधि जोड़ा बाद से डेटाबेस पंक्ति और फ़ाइल का विलोपन साथ जाने चाहिए (ताकि कोई अनाथ फ़ाइलें छोड़ दिया जाता है और यदि कोई भी पंक्ति हटाई गई फ़ाइलों को इंगित) नुकसान: डेटाबेस हटाना युक्त लेनदेन से पहले फ़ाइल हटा दी जाती है।

एक विकल्प delete() विधि में काम करेगा - लेकिन मैं ऐसा नहीं करना चाहता क्योंकि मैं वर्तमान लेनदेन के साथ समाप्त नहीं हो सकता। तो मैं भौतिक फ़ाइल को हटाने में देरी करने का एक तरीका ढूंढ रहा हूं जब तक कि पंक्ति को हटाने वाला लेनदेन वास्तव में प्रतिबद्ध नहीं होता है।

SQLAlchemy में after_delete ईवेंट है लेकिन दस्तावेज़ों के मुताबिक एसक्यूएल उत्सर्जित होने पर यह ट्रिगर होता है (यानी फ्लश पर) जो बहुत जल्दी है। इसमें after_commit ईवेंट भी है लेकिन इस बिंदु पर लेनदेन में हटाई गई सब कुछ शायद एसए से हटा दी गई है।

उत्तर

13

Flask-SQLAlchemy के साथ एक बोतल अनुप्रयोग में SQLAlchemy का उपयोग करते समय यह एक models_committed संकेत जो (model, operation) tuples की एक सूची प्राप्त करता है प्रदान करता है। इस संकेत के लिए मैं क्या देख रहा हूँ कर का उपयोग करना बहुत आसान है: यह सामान्य समारोह के साथ हर मॉडल है कि जरूरत

@models_committed.connect_via(app) 
def on_models_committed(sender, changes): 
    for obj, change in changes: 
     if change == 'delete' and hasattr(obj, '__commit_delete__'): 
      obj.__commit_delete__() 

ऑन-हटाना-लिखें कोड अब बस एक विधि __commit_delete__(self) है और यह करने की जरूरत है जो कुछ भी करने की जरूरत है उस विधि में।


यह भी कुप्पी के SQLAlchemy बिना किया जा सकता है, तथापि, इस मामले में यह कुछ और कोड की जरूरत है:

  • एक विलोपन दर्ज किया है जब यह प्रदर्शन किया है की जरूरत है। यह after_delete event का उपयोग करके किया जा सकता है।
  • कोई COMMIT सफल होने पर किसी भी रिकॉर्ड किए गए विलोपन को संभालने की आवश्यकता होती है। यह after_commit event का उपयोग करके किया जाता है।
  • यदि लेनदेन विफल रहता है या रिकॉर्ड किए गए परिवर्तनों को मैन्युअल रूप से घुमाया जाता है तो उसे साफ़ करने की भी आवश्यकता होती है। यह after_rollback() ईवेंट का उपयोग करके किया जाता है।
+0

इसे * हर * मॉडल * ऐप में * हर * प्रतिबद्धता के लिए कहा जाता है। क्या यह एक ओवरकिल नहीं है? – Devi

+0

क्यों? Deletes सबसे आम ऑपरेशन नहीं हैं और एक समारोह कॉल ज्यादा ओवरहेड नहीं जोड़ता है। – ThiefMaster

+1

लेकिन 'models_committed' केवल 'हटाएं' तक ही सीमित नहीं है, लेकिन 'डालने' और 'अपडेट' के लिए कहा जाता है जो सबसे आम हैं। [डॉक्टर] (https://pythonhosted.org/Flask-SQLAlchemy/signals.html#models_committed) से, "यह सिग्नल भेजा जाता है जब डेटाबेस में प्रतिबद्ध मॉडल बदलते हैं" – Devi

1

यह थोड़ा चुनौतीपूर्ण प्रतीत होता है, मैं उत्सुक हूं कि एक एसक्यूएल ट्रिगर AFTER DELETE इसके लिए सबसे अच्छा मार्ग हो सकता है, यह सुनिश्चित नहीं होगा कि यह सूखा नहीं होगा और मुझे यकीन नहीं है कि आप जिस SQL ​​डेटाबेस का उपयोग कर रहे हैं उसका समर्थन करता है, फिर भी AFAIK sqlalchemy डीबी को लेन-देन को धक्का देता है, लेकिन अगर यह इस टिप्पणी को सही तरीके से व्याख्या करता है, तो यह वास्तव में नहीं जानता है:

यह डेटाबेस सर्वर स्वयं है जो चल रहे लेनदेन में सभी "लंबित" डेटा को बनाए रखता है। परिवर्तन स्थायी रूप से डिस्क पर नहीं बने रहते हैं, और अन्य लेन-देन के लिए सार्वजनिक रूप से प्रकट होते हैं, जब तक डेटाबेस को COMMIT कमांड प्राप्त नहीं होता है, जो सत्र सत्र() भेजता है।

SQLAlchemy के निर्माता द्वारा SQLAlchemy: What's the difference between flush() and commit()? से लिया ...

+1

डेटाबेस में ट्रिगर्स चलाते हैं - इसलिए वे वास्तव में एक अच्छा विकल्प नहीं हैं। असल में वे एक विकल्प नहीं हैं क्योंकि postgresql उपयोगकर्ता को उस फ़ाइल तक पहुंच नहीं होगी जिसे हटाया जाना चाहिए। – ThiefMaster

+0

यही कारण है कि मैंने पूछा कि आपका बैकएंड डेटाबेस क्या था, कुछ डीबीएमएस हैं जो सिस्टम कॉल की अनुमति देते हैं, मुझे लगता है कि एमएसएसक्यूएल उनमें से एक है, पोस्टग्रेज़ में 'plperlu' है http://www.postgresql.org/docs/8.0/static /plperl-trusted.html जो वास्तविक एसक्यूएल के भीतर पर्ल कमांड चला सकता है जो बदले में सिस्टम कॉल करने में सक्षम हो सकता है, ध्यान रखें कि यह थोड़ा खतरनाक है, पोस्टग्रेस 'प्लपरल' की सिफारिश करता है जो कहीं अधिक प्रतिबंधित है लेकिन जैसा कि आप चाहते हैं फ़ाइलों को हटाने के लिए 'plperlu' एक संभावित मार्ग हो सकता है ... –

1

यदि आपका एसक्यूएलकेमी बैकएंड इसका समर्थन करता है, तो two-phase commit सक्षम करें।

  • चेकों अनुमतियाँ, आदि सुनिश्चित करें कि फ़ाइल मौजूद है और पहले चरण के लिए प्रतिबद्ध
  • वास्तव में फ़ाइल को हटा देता है के दौरान नष्ट कर दिया जा सकता है: आपको लगता है कि फाइल सिस्टम के लिए उपयोग करने के लिए (या लिखना) एक सौदे मॉडल की आवश्यकता होगी दूसरे प्रतिबद्ध चरण के दौरान।

शायद यह उतना ही अच्छा होगा जितना यह प्राप्त होगा। यूनिक्स फाइल सिस्टम, जहां तक ​​मुझे पता है, एक्सए या अन्य दो चरण लेनदेन प्रणाली का मूल रूप से समर्थन नहीं करते हैं, इसलिए आपको दूसरे चरण फ़ाइल सिस्टम को अप्रत्याशित रूप से विफल होने से छोटे एक्सपोजर के साथ रहना होगा।

+0

ध्वनि बहुत जटिल है - लेकिन शायद अधिक जटिल के लिए एक अच्छा विचार सिस्टम। – ThiefMaster

5

यह अन्य घटना आधारित जवाब के साथ-साथ इस प्रकार है, लेकिन मैंने सोचा कि जब से मैं इसे काफी अपने सटीक समस्या को हल करने में लिखा था कि मैं इस कोड पोस्ट करना चाहते हैं,:

कोड (नीचे) एक SessionExtension वर्ग पंजीकृत करता है जो फ्लश के रूप में सभी नई, बदली और हटाई गई वस्तुओं को जमा करता है, फिर सत्र वास्तव में प्रतिबद्ध या वापस घुमाए जाने पर कतार को साफ़ या मूल्यांकन करता है। जिन कक्षाओं में बाहरी फाइल संलग्न है, उनके लिए मैंने obj.after_db_new(session), obj.after_db_update(session), और/या obj.after_db_delete(session) विधियों को लागू किया जो सत्र एक्सटेंशन को उचित के रूप में आमंत्रित करता है; फिर आप बाहरी फ़ाइलों को बनाने/सहेजने/हटाने की देखभाल करने के लिए उन विधियों को पॉप्युलेट कर सकते हैं।

नोट: मैं लगभग सकारात्मक हूं कि इसे स्क्लेक्लेमी की नई घटना प्रणाली का उपयोग करके क्लीनर तरीके से फिर से लिखा जा सकता है, और इसमें कुछ अन्य त्रुटियां हैं, लेकिन यह उत्पादन और काम में है, इसलिए मैंने इसे अपडेट नहीं किया है :)

import logging; log = logging.getLogger(__name__) 
from sqlalchemy.orm.session import SessionExtension 

class TrackerExtension(SessionExtension): 

    def __init__(self): 
     self.new = set() 
     self.deleted = set() 
     self.dirty = set() 

    def after_flush(self, session, flush_context): 
     # NOTE: requires >= SA 0.5 
     self.new.update(obj for obj in session.new 
         if hasattr(obj, "after_db_new")) 
     self.deleted.update(obj for obj in session.deleted 
          if hasattr(obj, "after_db_delete")) 
     self.dirty.update(obj for obj in session.dirty 
          if hasattr(obj, "after_db_update")) 

    def after_commit(self, session): 
     # NOTE: this is rather hackneyed, in that it hides errors until 
     #  the end, just so it can commit as many objects as possible. 
     # FIXME: could integrate this w/ twophase to make everything safer in case the methods fail. 
     log.debug("after commit: new=%r deleted=%r dirty=%r", 
        self.new, self.deleted, self.dirty) 
     ecount = 0 

     if self.new: 
      for obj in self.new: 
       try: 
        obj.after_db_new(session) 
       except: 
        ecount += 1 
        log.critical("error occurred in after_db_new: obj=%r", 
           obj, exc_info=True) 
      self.new.clear() 

     if self.deleted: 
      for obj in self.deleted: 
       try: 
        obj.after_db_delete(session) 
       except: 
        ecount += 1 
        log.critical("error occurred in after_db_delete: obj=%r", 
           obj, exc_info=True) 
      self.deleted.clear() 

     if self.dirty: 
      for obj in self.dirty: 
       try: 
        obj.after_db_update(session) 
       except: 
        ecount += 1 
        log.critical("error occurred in after_db_update: obj=%r", 
           obj, exc_info=True) 
      self.dirty.clear() 

     if ecount: 
      raise RuntimeError("%r object error during after_commit() ... " 
           "see traceback for more" % ecount) 

    def after_rollback(self, session): 
     self.new.clear() 
     self.deleted.clear() 
     self.dirty.clear() 

# then add "extension=TrackerExtension()" to the Session constructor 
+0

वास्तव में 'सत्र एक्सटेंशन' अब बहिष्कृत है, लेकिन जैसा कि आपने देखा है कि इसे नए ईवेंट ढांचे में लगभग उसी कोड के साथ फिर से लिखा जा सकता है - आप केवल 'सत्र एक्सटेंशन' से प्राप्त नहीं होंगे, और इसके बजाय अपने सभी को पंजीकृत करने के लिए एक विधि जोड़ें किसी दिए गए सत्र या सत्रफैक्टरी पर ईवेंट श्रोताओं के रूप में विधियां (या आप 'event.listen_for' सजावटी का उपयोग कर सकते हैं, लेकिन इसका एक विशिष्ट 'सत्र' वर्ग में कार्यान्वयन को बांधने का नुकसान होता है (किसी के उपयोग के लिए यह संभवतः ठीक है)। – Iguananaut

 संबंधित मुद्दे

  • कोई संबंधित समस्या नहीं^_^