EntityClassDescription.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 » EntityClassDescription.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.
#-----------------------------------------------------------------------------


"""
EntityClassDescription

  Class description fills the gap between a model and informations needed at
  run-time, such as ...
  
  Automatically registered when a model is loaded/instanciated.


  Module's functions are the one a ZModelizationTool should be able to answer.
  (or something else?)
  
  CVS information

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

from RelationshipManipulation import \
     removeObjectFromBothSidesOfRelationshipWithKey

from interfaces.ClassDescription import ClassDescriptionInterface
from utils import capitalizeFirstLetter,getModule
from logging import trace,debug

# Integer constants
from ClassDescription import \
     DELETE_NULLIFY, DELETE_DENY, DELETE_CASCADE, DELETE_NOACTION

import ClassDescription
from ClassDescription import classDescriptionForName

import Validation
from interfaces.Validation import \
     CUSTOM_KEY_VALIDATION, \
     OBJECT_WIDE, \
     OBJECT_WIDE_KEY, \
     DELETE_DENY_KEY

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

class EntityClassDescription(ClassDescription.ClassDescription):
  """

  *Documentation forthcoming*

  'EntityClassDescription' is a concrete subclass of 'ClassDescription' which
  uses an Entity to service the messages it responds to.

  """

  __implements__=(ClassDescriptionInterface,)

  def __init__(self, anEntity):
    """
    Initializes a classe description with the supplied entity.

    """
    # we just need to keep a reference to the supplied entity, that's it.
    assert(anEntity is not None)
    self._entity=anEntity
    
  def adaptorName(self):
    """
    Returns the name of the adaptor (like: 'Postgres') which is used to make
    persistent the corresponding instances
    """
    return self._entity.model().adaptorName()

  def allAttributesKeys(self):
    """
    Returned the set of Attributes defined by the underlying Entity.

    See also: attributesKeys()
    """
    return tuple([attr.name() for attr in self._entity.attributes()])

  def allToManyRelationshipKeys(self):
    """
    Returned the set (tuple) of toMany relationships' names defined by the
    underlying Entity.

    See also: toManyRelationshipKeys()
    """
    toManyRels=filter(lambda rel: rel.isToMany(), self._entity.relationships())
    return tuple([rel.name() for rel in toManyRels])
  
  def allToOneRelationshipKeys(self):
    """
    Returned the set (tuple) of toOne relationships' names defined by the
    underlying Entity.

    See also: toOneRelationshipKeys()
    """
    toOneRels=filter(lambda rel: rel.isToOne(), self._entity.relationships())
    return tuple([rel.name() for rel in toOneRels])

  def attributesKeys(self):
    """
    Returns the set (tuple) of Attributes that are designated as class
    properties in the underlying Entity.

    See also: allAttributesKeys()
    """
    return tuple([attr.name()
                  for attr in self._entity.attributes()
                  if attr.isClassProperty()])
    #l=filter(lambda attr: attr.isClassProperty(), self._entity.attributes())
    #return tuple(map(lambda attr: attr.name(), l))
               
  def awakeObjectFromInsertion(self, object, editingContext):
    "latter"
    ClassDescription.ClassDescription.awakeObjectFromInsertion.im_func(self, object, editingContext)
    debug('Not implemented yet')

  def awakeObjectFromFetch(self, object, editingContext):
    "latter"
    ClassDescription.ClassDescription.awakeObjectFromFetch.im_func(self, object, editingContext)
    debug('Not implemented yet')

  def classDescriptionForDestinationKey(self, aKey):
    """
    Returns the class description registered for the destination entity of
    the underlying Entity's relationship 'aKey'
    """
    dest=self._entity.relationshipNamed(aKey).destinationEntity()
    return ClassDescription.classDescriptionForName(dest.name())
  
  def classForInstances(self):
    """
    """
    delegate=self.delegate()
    theClass=None
    if delegate and delegate.respondsTo('classForClassDescription'):
      theClass=delegate.classForClassDescription(self)
    if not theClass:
      theClass=classForEntity(self._entity)
    return theClass
  
  def createInstanceWithEditingContext(self, editingContext):
    """
    Returns an instance of the appropriate class, as specified by the
    underlying 'entity.className()'. The default implementation searches a
    class 'entity.className()' in a module 'entity.model'.

    You can provide your own logic by providing a delegate to the
    classDescription which implements method 'classForClassDescription()'. If
    the delegate returns None, than it falls back to the standard behaviour.

    Raises ValueError if such an instance cannot be created.
    """
    try:
      theClass=self.classForInstances()
    except ImportError:
      raise
    debug('theClass: %s'%theClass)
    return theClass()

  def deleteRuleForRelationshipKey(self, aKey):
    "-"
    return self._entity.relationshipNamed(aKey).deleteRule()
    
  def displayNameForKey(self, aKey):
    "-"
    _attr=self._entity.attributeNamed(aKey)
    _rel=self._entity.relationshipNamed(aKey)
    if _attr:
      return _attr.displayLabel()
    if _rel:
      return _rel.displayLabel()
    return None
  
  def entity(self):
    """
    Returns the underlying entity
    """
    return self._entity
  
  def entityName(self):
    """
    Returns the undelying entity's name
    """
    return self._entity.name()
  
  def foreignKeys(self):
    """
    Returns the names of foreign keys for the object, i.e. the source
    attributes' names of to-one relationships.
    
    See also: toOneRelationshipKeys(), allToOneRelationshipKeys()
    """
    toOneRels=filter(lambda rel: rel.isToOne(), self._entity.relationships())
    res=[]
    for rel in toOneRels:
      res.extend([a.name() for a in rel.sourceAttributes()])
    return res
  
  def inverseForRelationshipKey(self, aKey):
    "-"
    try:
      return self._entity.relationshipNamed(aKey).inverseRelationship().name()
    except AttributeError:
      return None

  def primaryKeys(self):
    "Returns the names of primary keys for the object."
    return tuple(self._entity.primaryKeyAttributeNames())

  def propagateInsertionForObject(self, anObject, editingContext):
    """
    Examines the object's relationships and searches for related objects not
    inserted in 'editingContext', then inserts these objects into
    editingContext if they are not already registered.
    
    It is normally called when an EditingContext gets the
    processRecentChanges() message.

    See also: EditingContext.propagatesInsertionForRelatedObjects()
    """
    ## To-One
    for key in self.toOneRelationshipKeys():
      trace('toOne %s on %s'%(key, str(anObject)))
      relatedObject=anObject.storedValueForKey(key)
      if relatedObject is None or relatedObject.isFault():
        continue

      if editingContext.globalIDForObject(relatedObject) is None:
        editingContext.insertObject(relatedObject)

    ## To-many
    for key in self.toManyRelationshipKeys():
      trace('toMany %s on %s'%(key, str(anObject)))
      relatedObjects=anObject.storedValueForKey(key)
      try:
        if not relatedObjects or relatedObjects.isFault():
          continue
      except AttributeError: # possibly _p_isFault on relatedObjects=sequence
        pass

      for o in relatedObjects:
        if editingContext.globalIDForObject(o) is None:
          editingContext.insertObject(o)
    
  def propagateDeleteForObject(self, anObject, editingContext):
    """
    Examines the deleted object's relationships and their delete rules, and
    takes the appropriate action.

    The process of propagating the deletion of an objects examines each
    relationship, then acts that way:

      - if the delete rule is 'DELETE_NOACTION', simply returns

      - same for 'DELETE_DENY': we leave things untouched and wait for the
        validation process to raise the appropriate error for such
        inconsistencies

      - if the delete rule is 'DELETE_NULLIFY', the relation(s) the deleted
        object has with other object(s) is/are dismissed so that the graph
        of objects is left consistent afterwards

      - if the delete rule is 'DELETE_CASCADE', objects in relation with the
        deleted object are also deleted --this is obviously detected by
        the EditingContext which will then ask them to propagate their
        deletion as well.

    See also: CustomObject.propagateDeleteWithEditingContext()
              EditingContext.processRecentChanges()

    Implementation notes:

      The propagation process is not optimized at all.
      
    """
    ## To-one
    for key in self.toOneRelationshipKeys():
      trace('toOne %s on %s'%(key, str(anObject)))
      if self.deleteRuleForRelationshipKey(key) in (DELETE_NOACTION,
                                                    DELETE_DENY):
        continue
      relatedObject=anObject.storedValueForKey(key)
      if relatedObject is None:
        continue
      relatedObject.willRead() # trigger a possible fault explicitly
      if self.deleteRuleForRelationshipKey(key)==DELETE_NULLIFY:
        removeObjectFromBothSidesOfRelationshipWithKey(anObject, relatedObject,
                                                       key)
        continue
      if self.deleteRuleForRelationshipKey(key)==DELETE_CASCADE:
        # break reference cycle
        # __TBD do we need to do this in this case???
        removeObjectFromBothSidesOfRelationshipWithKey(anObject, relatedObject,
                                                       key)
        editingContext.deleteObject(relatedObject)
        continue
      
    ## To-many
    for key in self.toManyRelationshipKeys():
      trace('toMany %s on %s'%(key, str(anObject)))
      if self.deleteRuleForRelationshipKey(key) in (DELETE_NOACTION,
                                                     DELETE_DENY):
        continue

      ##
      ## _TBD See FaultHandler/implementation notes for these 3 lines
      ##
      relatedObjects=anObject.storedValueForKey(key)
      len(relatedObjects) # trigger a possible toMany fault explicitly
      relatedObjects=anObject.storedValueForKey(key)

      if self.deleteRuleForRelationshipKey(key)==DELETE_NULLIFY:
        for relObj in relatedObjects:
          removeObjectFromBothSidesOfRelationshipWithKey(anObject, relObj, key)
        continue
      if self.deleteRuleForRelationshipKey(key)==DELETE_CASCADE:
        for relObj in relatedObjects:
          # break reference cycle
          # __TBD do we need to do this in this case???
          removeObjectFromBothSidesOfRelationshipWithKey(anObject, relObj, key)
          editingContext.deleteObject(relObj)
        continue
      
  def rootClassDescription(self):
    """
    Returns the ClassDescription being the root of the inheritance hierarchy
    to which the current ClassDescription is bound, or 'self' if it does not
    participate in an inheritance hierarchy

    See also: superClassDescription(), Entity.rootEntity()
    """
    return classDescriptionForName(self._entity.rootEntity().name())

  def superClassDescription(self):
    """
    Returns the class description being the parent of the current
    ClassDescription, or 'None' if the latter does not participate in an
    inheritance hierarchy.

    See also: rootClassDescription(), Entity.parentEntity()
    """
    _parentEntityName=self._entity.parentEntityName()
    return _parentEntityName and classDescriptionForName(_parentEntityName) \
           or None
  
  def toManyRelationshipKeys(self):
    """

    See also: allToManyRelationshipKeys()
    """
    toManyRels=filter(lambda rel: rel.isToMany(), self._entity.relationships())
    return tuple([rel.name() for rel in toManyRels if rel.isClassProperty()])
  
  def toOneRelationshipKeys(self):
    """
    
    See also: allToOneRelationshipKeys()
    """
    toOneRels=filter(lambda rel: rel.isToOne(), self._entity.relationships())
    return tuple([rel.name() for rel in toOneRels if rel.isClassProperty()])

  def userPresentableDescriptionForObject(self, anObject):
    "-"
    raise 'Unimplemented'
  

  ##
  ## Validation
  ##
  def validateObjectForDelete(self, anObject):
    """
    Checks that for every relationship declared with a 'DELETE_DENY' rule,
    'anObject' has no more objects for that key ; if it does, it raises 
    'Validation.ValidationException'.
    
    See also: CustomObject.validateForDelete(),
              Relationship.deleteRule()
    """
    # Check relationships
    error=Validation.ValidationException()
    for relation in self._entity.relationships():
      if relation.deleteRule()==DELETE_DENY:
        value=anObject.storedValueForKey(relation.name())
        if value:
          error.aggregateError(DELETE_DENY_KEY, relation.name())
    error.finalize()
    return

  def validateObjectForSave(self, anObject):
    """
    Currently does nothing, just returns
    """
    return

  def validateValueForKey(self, aValue, aKey):
    """
    
    """
    _attribute=self._entity.attributeNamed(aKey)    
    _relationship=self._entity.relationshipNamed(aKey)    
    if _attribute is None and _relationship is None:
      raise AttributeError, \
            "Entity %s has no attribute or relationship named %s" \
            % (self._entity.name(), aKey)

    prop=_attribute or _relationship

    prop.validateValue(aValue)

    #if _relationship:
    #  _relationship.validateValue(aValue)
    #  return
    #
    #else: #Attribute
    #  validationError=Validation.ValidationException()
    #  try:
    #    #import pdb; pdb.set_trace()
    #    _attribute.validateValue(aValue, object)
    #  except Validation.ValidationException, exc:
    #    validationError.aggregateException(exc)
    #  # Custom bizness logic: validate<AttributeName>
    #  _validateBizLogic='object.validate'+capitalizeFirstLetter(aKey)
    #  _validateFunction=None
    #  try:
    #    _validateFunction=eval(_validateBizLogic)
    #  except AttributeError:
    #    # Function is not defined, fine
    #    pass
    #
    #  if _validateFunction:
    #    try:
    #      apply(_validateFunction, (value,))
    #    except Validation.ValidationException, exc:
    #      validationError.aggregateException(exc)
    #      validationError.addErrorForKey(Validation.CUSTOM_KEY_VALIDATION,
    #                                     self.name())
    #  validationError.finalize()

    
  def XMLDescriptionForObject(self, anObject):
    "Returns an DOM representing the object"
    raise 'Unimplemented'


##
def classForEntity(entity):
  """
  Defines the default mechanism used to find the class of a given entity.

  This is equivalent to the following statement::

      from <packageName>.<moduleName> import <className>

  where 'packageName' the entity's model 'packageName()', and 'moduleName' and
  'className' are the entity's properties.

  Raises ImportError in case it cannot be found
  """
  moduleName=entity.moduleName()
  className=entity.className()
  packageName=entity.model().packageName()
  
  import string, sys, imp
  fullPath = packageName+'.'+moduleName
  sys_path_ori=sys.path
  # Check that this isn't already defined, since load_module reloads the module
  theClass = getattr(sys.modules.get(fullPath), className, None)
  if theClass:
    return theClass

  err_msg="""Unable to locate class %s corresponding to entity '%s'.
It is possible that the model's packageName, or the entity's moduleName or
className do not correspond to where the module is installed --for example,
you might have moved it to a sub-package. You can solve this easily by
updating your model so that 'packageName.moduleName.className' points to
the exact location where the class is installed
"""

  paths=string.split(fullPath, '.')
  module=path=exc_raised=None
  for name in paths:
    try:
      module=getModule(name,path)
      path=getattr(module,'__path__', None)
      if path: sys.path=path+sys.path
    except:
      exc_raised=sys.exc_info()[:2]
      break
  sys.path=sys_path_ori

  if not module or exc_raised: # we got an error, the module was not found
    err_msg=err_msg%(fullPath+'.'+className, entity.name())
    if exc_raised:
      import traceback, cStringIO
      exc=cStringIO.StringIO()
      traceback.print_exception(exc_raised[0], exc_raised[1], None, file=exc)
      err_msg+="\nOriginal exception was: %s"%exc.getvalue()
    raise ImportError, err_msg

  retClass=getattr(module, className, None)
  if not retClass:
    err_msg=err_msg%(fullPath+'.'+className, entity.name())
    err_msg+="\nReason: Unable to find class %s in module %s"%(className,
                                                               fullPath)
    raise ImportError, err_msg
  return retClass
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.