FaultHandler.py :  » Database » Modeling-Framework » Modeling-0.9 » Modeling » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » Modeling Framework 
Modeling Framework » Modeling 0.9 » Modeling » FaultHandler.py
# -*- coding: iso-8859-1 -*-
#-----------------------------------------------------------------------------
# Modeling Framework: an Object-Relational Bridge for python
#
# Copyright (c) 2001-2004 Sbastien Bigaret <sbigaret@users.sourceforge.net>
# All rights reserved.
#
# This file is part of the Modeling Framework.
#
# This code is distributed under a "3-clause BSD"-style license;
# see the LICENSE file for details.
#-----------------------------------------------------------------------------

"""
FaultHandler & subclasses

  A FaultHandler is an object that takes care of the full initialization of
  an object conforming to the Faulting interface.

  Module's methods manipulates the Faulting interface an object conforms to.

  Classes

    The module defined four different classes:

    - 'FaultHandler' is the parent class of all fault handlers. It defines the
      generic API a fault handler uses to finalize initialization of an
      object or to make an object a fault.

    - 'AccessGenericFaultHandler'

    - 'AccessArrayFaultHandler'

    - 'AccessFaultHandler'
    

  See also: Faulting interface

  CVS information

    $Id: FaultHandler.py 932 2004-07-20 06:21:57Z sbigaret $
  
"""

__version__='$Revision: 932 $'[11:-2]

import sys, weakref

# Framework
from utils import staticmethod
import Qualifier, FetchSpecification
from GlobalID import GlobalIDChangedNotification

from logging import trace

def clearFault(anObject):
  """
  Informs the object that it has been fully initialized.
  """
  #anObject.
  raise 'Unimplemented'

def handlerForFault(anObject):
  """
  Returns the fault handler for the object, or None if the object is not
  a fault.

  Simply sends message 'Faulting.faultHandler()' to 'anObject'.

  Parameter:

    anObject -- an object conforming to the Faulting interface

  """
  return anObject.faultHandler()

def isFault(anObject):
  """
  Tells whether the object is a fault. Simply calls Faulting.isFault() on
  'anObject'.
  
  Parameter:

    anObject -- an object conforming to the Faulting interface

  """
  return anObject.isFault()

def makeObjectIntoFault(anObject, aFaultHandler):
  """
  Makes 'anObject' a fault. Simply calls Faulting.makeObjectIntoFault() on
  that object.
  
  Parameters:

    anObject -- an object conforming to the Faulting interface

    aFaultHandler -- the fault handler responsible for finishing initialization
    of 'anObject'
  """
  anObject.turnIntoFault(aFaultHandler)

##
## FaultHandler
##
class FaultHandler:
  """
  A FaultHandler works with any object implementing the Faulting interface ;
  its purpose is to help any conforming object, postponing their
  initialization until needed, to get their actual datas from DB.

  """
  
  # Instance methods
  def completeInitializationOfObject(self, anObject): # abstract
    """
    abstract
    """
    __abstract__()

  #createFaultForDeferredFault(aFault, aSourceDatabaseObject) 
  #descriptionForObject(anObject) 

  def faultWillFire(self, anObject):
    """
    Informs the FaultHandler that the object is about to trigger its fault.

    Default implementation does nothing, subclasses do not need to call this
    method when subclassing;

    Parameter:

      anObject -- an object conforming to the 'Faulting' interface
    """
    pass
    
  def targetClass(self):
    """
    """
    pass


##
## AccessGenericFaultHandler
##
class AccessGenericFaultHandler(FaultHandler):
  """
  This is an abstract class providing the following capabilities:
  
    - it provides the faults the ability to be chained/grouped
      together. **Note**: this is not used in the framework yet, but it will!
    
    - It also specifies a generic API based on the fact that this sort of
      FaultHandlers is based on the "Access Layer", i.e. uses a
      'DatabaseContext' and an 'EditingContext' to fetch the datas from an
      external store/database.

  Implementation notes:

    !!! DatabaseContext: should be the right one (not the original but the one
    that is responsible for the object). Maybe this can be checked
    somewhere TBD

    Subclasses must use the '_setContext()' method to initialize the
    AccessGenericFaultHandler (typically it will be called in '__init__()')

  """
  _generation=0 # not used yet / for use w/ the chain of fault handlers.
  _next=None
  _previous=None
  _dbContext=None
  _weakref_ec=None
  
  def completeInitializationOfObject(self, anObject):
    """
    Abstract method implemented by subclasses to populate 'anObject'

    Parameter:

      anObject -- an object implementing the 'Faulting' interface. Within the
        framework, it is typically a 'DatabaseObject'
        
    """
    __abstract__()
    
  def databaseContext(self):
    """
    Returns the 'DatabaseContext' object the fault handler uses to fetch data
    rows from a database.

    See also: 'editingContext()'
    """
    return self._dbContext

  def editingContext(self):
    """
    Returns the underlying EditingContext --see _setContext().

    Return value may be None if unset or if the underlying editing context has
    been garbage-collected (NB: the latter case should not actually happen
    since an object/fault ``belonging'' to a garbage-collected/deleted
    EditingContext should normally _not_ be hold elsewhere).
    """
    return self._weakref_ec()
  
  def faultWillFire(self, anObject):
    """
    Removes itself from the chain of handlers

    Subclasses can override this method ; if they do, their implementation
    must call this implementation.
    """
    self._removeSelfFromChain()
    
  def generation(self):
    """
    Return the ``generation'' of the fault --see: 'linkAfterHandler()'.
    """

  def linkAfterHandler(self, anAccessGenericFaultHandler, generation=None):
    """
    Adds the FaultHandler to the chain ended by 'anAccessGenericFaultHandler'.

    'self' should not have any predecessor, neither
    'anAccessGenericFaultHandler' should have any successor in the chain.

    **NB**: Chaining fault handlers is not used in the framework yet.

    Parameters:

      self -- an AccessGenericFaultHandler for which 'previous()' answers None

      anAccessGenericFaultHandler -- an AccessGenericFaultHandler for which
      'next()' returns None

      generation -- a float indicating when the fault was created. If it is
      omitted or 'None', it defaults to 'time.time()'
      
    """
    if generation is None:
      import time
      generation=time.time()
    if self._previous or anAccessGenericFaultHandler._next:
      raise ValueError, 'Cannot link self after an handler: self is already '\
            'linked after an other handler, or parameter '\
            "'anAccessGenericFaultHandler' has another handler linked after "\
            'itself'
    anAccessGenericFaultHandler._next=self
    self._previous=anAccessGenericFaultHandler
    
  def next(self):
    """
    Returns the successor of 'self' in the chain, or None.

    **NB**: Chaining fault handlers is not used in the framework yet.
    """
    return self._next
  
  def previous(self):
    """
    Returns the predecessor of 'self' in the chain, or None

    **NB**: Chaining fault handlers is not used in the framework yet.
    """
    return self._previous
  
  def _setContext(self, aDatabaseContext, anEditingContext):
    """
    Initializes the AccessGenericFaultHandler ; subclasses typically call
    this method in their '__init__()' method.
    
    Parameters:

      aDatabaseContext -- the DatabaseContext from which the object's state
      can be restored. Concrete subclasses use it to restore the object from
      a database.

      anEditingContext -- the editingContext the faulted object is registered
      to. Note that the EditingContext is weak-referenced, thus a fault
      handler does not prevent it to be garbage-collected. When this happens,
      the fault is automatically notified and is invalidated as well --see
      '_handleDeletion()'.

    """
    self._dbContext=aDatabaseContext
    self._weakref_ec=weakref.ref(anEditingContext, self._handleECDeletion)

  def _handleECDeletion(self, ref):
    """
    Automatically called when the EditingContext the FaultHandler (weak-)
    references is deleted ; simply removes itself from the chain of handlers.

    You should not call this method directly, nor should it be overriden.

    See also: linkAfterHandler()
    """
    self._weakref_ec=None
    self._dbContext=None
    self.generation=None
    self._removeSelfFromChain()

  def _removeSelfFromChain(self):
    """
    Removes itself from the chain. You should never call this method, it is
    automatically called when necessary.

    Subclasses should not override this method.

    See: faultWillFire()
    """
    next=self.next()
    previous=self.previous()
    if not (previous or next):
      return
    if previous and next:
      previous._next=next
      next._previous=previous
    elif previous:
      previous._next=None
    elif next:
      next._previous=None
    self._previous=self._next=None


##
## AccessArrayFaultHandler
##
class AccessArrayFaultHandler(AccessGenericFaultHandler):
  """
  Handles to-many relationships
  """
  def __init__(self, srcKeyGlobalID, relationshipName,
               aDatabaseContext, anEditingContext, object):
    """
    Initializes the fault handler with all the necessary informations needed
    to fetch rows from a database when the corresponding faulted object needs
    to be populated with its own data.

    Parameters:

      srcKeyGlobalID -- the 'KeyGlobalID' identifying the source object for
        which the fault was created.

      relationshipName -- the name of the toMany relationship this fault
        handler services.

      aDatabaseContext -- the 'DatabaseContext' object responsible for fetching
        datas for this toMany fault

      anEditingContext -- the 'EditingContext' holding the source object and
        in which the objects 

    Implementation notes::

      __TBD cf. besoin de violer la regle du fault qui ne contient pas son
      __TBD objet, pour le cas des requetes sur __len__, etc. (sequence-like
      __TBD messages)

      __TBD It would be really better if faults could dynamically change
      __TBD their class (python2.2: derive from lists) so that triggering the
      __TBD fault automatically gives you the right thing
      __TBD cf. e.g. EntityClassDescription.propagateDeleteForObject():
      __TBD when processing toMany rels. it has to get the related objects
      __TBD w/ storedValueForKey(), trigger the fault, and then re-get it
      __TBD to get it right... That shouldnt be made that way. But is there
      __TBD any way to do this right in python 2.1 ???

    """
    self._setContext(aDatabaseContext, anEditingContext)
    self._srcGlobalID=srcKeyGlobalID
    self._relationshipName=relationshipName
    self.__temporaryHoldFetchedArrayOfObjects=0
    import weakref
    self.__object=weakref.ref(object)
    self.__list=None
    
    # NOTE: python version<2.0 are not supported
    #if sys.version_info >= (2, 2):
    self.__sequenceTypeMethods=('__len__', '__contains__',
                                '__getitem__', '__setitem__', '__delitem__',
                                '__iter__',
                                '__add__', '__radd__', '__iadd__',
                                '__mul__', '__rmul__', '__imul__',
                                '__eq__', '__ne__',
                                '__gt_', '__ge__',
                                '__lt__', '__le__',
                                '__hash__')


  def completeInitializationOfObject(self, anObject):
    """

    See also: 'DatabaseContext.objectsForSourceGlobalID()'
    """
    self.faultWillFire(anObject)
    dbContext=self.databaseContext()
    
    objects=dbContext.objectsForSourceGlobalID(self._srcGlobalID,
                                               self._relationshipName,
                                               self.editingContext())
    anObject.takeStoredValueForKey(objects, self._relationshipName)
    self.__list=self.__object().storedValueForKey(self._relationshipName)
    #if self.__temporaryHoldFetchedArrayOfObjects:
    #  self.__fetchedArrayOfObjects=objects

  def isFault(self):
    """
    Returns true or false, depending on the object internal state.

    When the object is still a fault, returns 1. When the fault was triggered,
    this object acts as a proxy for the underlying sequence, and it returns 0
    in this case.

    This is needed during validation, when an object holds a toMany fault and
    validation needs to know whether this is a fault or not.

    See also: Relationship.validateValue()
    """
    return not self.__list
  
  def relationshipName(self):
    """
    Returns the name of the source object's toMany relationship that fault
    handler is reponsible for.
    """
    return self._relationshipName
  
  def sourceGlobalID(self):
    """
    Returns the 'KeyGlobalID' identifying the source object for which that
    fault handler was created.
    """
    return self._srcGlobalID

  # sequence_like  API
  #def __len__(self):
  #  self.__temporaryHoldFetchedArrayOfObjects=1
  #  self.completeInitializationOfObject(self.__object())
  #  l=len(self.__object().storedValueForKey(self._relationshipName))
  #  return l

  if sys.version_info >= (2, 2): # python v2.2 and higher
    def __getattr__(self, name):
      if name in self.__sequenceTypeMethods:
        if not self.__list:
          self.completeInitializationOfObject(self.__object())
        #trace(self.__object().storedValueForKey(self._relationshipName))
        
        return getattr(self.__object().storedValueForKey(self._relationshipName), name)
      else:
        raise AttributeError
      
  else: # python v2.0/v2.1 -> proxy behaviour

    ## We could also call willChange on the object, but this is no use
    ## In any case the developper should be aware that it is its responsability
    ## to call it.

    def __len__(self):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      l=len(self.__list)
      return l
    
    def __getitem__(self, key):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      return self.__list[key]

    def __setitem__(self, key, value):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      self.__list[key]=value
      
    def __delitem__(self, key):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      del self.__list[key]
      
    #def __iter__(self):
    #  if not self.__list:
    #    self.completeInitializationOfObject(self.__object())
    #  l=self.__object().storedValueForKey(self._relationshipName)
      
    def __contains__(self, item):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      return item in self.__list
    
    def append(self, object):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      self.__list.append(object)
      
    def count(self, value):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      return self.__list.count(value)
    
    def index(self, value):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      return self.__list.index(value)
    
    def insert(self, index, object):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      self.__list.insert(index, object)
      
    def pop(self, index=None):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      if index: return self.__list.pop(index)
      else: return self.__list.pop()
      
    def remove(self, value):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      self.__list.remove(value)
      
    def reverse(self):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      self.__list.reverse()
    
    def sort(self, cmpfunc=None):
      if not self.__list:
        self.completeInitializationOfObject(self.__object())
      if cmpfunc: self.__list.sort(cmpfunc)
      else: self.__list.sort()
      
  # etc. (slice, etc.) cf python doc.
##
## AccessFaultHandler
##
class AccessFaultHandler(AccessGenericFaultHandler):
  """
  Handles to-one relationships
  """
  def __init__(self, keyGlobalID, aDatabaseContext, anEditingContext):
    """
    Initializes the 'AccessFaultHandler' with the objects it needs to get
    the data back from the Database.

    Parameters:

      keyGlobalID -- the KeyGlobalID for the faulted object

      aDatabaseContext -- the 'DatabaseContext' object responsible for fetching
        the data for the object identified by 'keyGlobalID'

      anEditingContext -- the 'EditingContext' object managing the graph of
        objects in which the object identified by 'keyGlobalID' participates.
    """
    if keyGlobalID.isTemporary():
      raise ValueError, 'Cannot initialize a FaultHandler with a temporary'\
            'GlobalID'
    self._setContext(aDatabaseContext, anEditingContext)
    self._globalID=keyGlobalID
    
  def completeInitializationOfObject(self, anObject):
    """
    
    """
    #import pdb; pdb.set_trace()
    self.faultWillFire(anObject)

    dbContext=self.databaseContext()

    # First check that the corresponding data were not already fetched
    if dbContext.database().snapshotForGlobalID(self._globalID):
        dbContext.initializeObject(anObject, self._globalID,
                                   self.editingContext())
        anObject.clearFault()
        return
    else:
      pass # TBD it is possible not to get the snapshot while it actually
           # TBD exists, when db is asked for a root_keyGlobalID and not
           # TBD for the real one
    # Let's fetch the data: first, we need to build the qualifier & fetchSpec
    dbContext=self.databaseContext()
    entity=dbContext.database().entityNamed(self._globalID.entityName())
    qualifier=entity.qualifierForPrimaryKey(self._globalID.keyValues())
    fetchSpec=FetchSpecification.FetchSpecification(entity.name(),
                                                    qualifier=qualifier,
                                                    deepFlag=1) ## !!!
    # sends objectsWithFetchSpec. to dbContext
    objects=dbContext.objectsWithFetchSpecification(fetchSpec,
                                                    self.editingContext())
    
    # get the result back, check existence and unicity
    if len(objects)!=1:
      raise RuntimeError, 'Error: Unexpected: Unable to complete '\
            'initialization of object: fetched %i object%s'%\
            (len(objects), len(objects)>1 and 's' or '')

    # NB: fault is cleared within DatabaseChannel.fetchObject()
    #     hence, we do not need to clear it explicitly

  #def descriptionForObject(self, anObject):
  #  """
  #  """

  def globalID(self):
    """
    Returns the 'KeyGlobalID' of the object this FaultHandler is responsible
    for.
    """
    return self._globalID


def __abstract__():
  raise 'AbstractMethod', 'Left intentionally unimplemented in this class, '\
        'subclass should override this method'
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.