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


""" ... describe me please I feel alone...

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

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

# Modeling
from Modeling.utils import isListOrTuple,isaValidName,toBoolean,finalize_docstrings
from Modeling.XMLutils import *
from Modeling.EntityClassDescription import EntityClassDescription
from Modeling.KeyValueCoding import KeyValueCoding
from Modeling.Model import ModelError
from Modeling.Attribute import Attribute
from Modeling.Exceptions import ModelValidationError
from Modeling import Validation
from Modeling import Qualifier

from Modeling.GlobalID import KeyGlobalID

# python
from cgi import escape
import re, string, sys, types

from Modeling.logging import debug

ENTITY_DOC = {
  'inheritance_methods': 'addSubEntity(), allSubEntities(), '\
              'allSubEntitiesNames(), newOrOverriddenProperties(), '\
              'newOrOverriddenPropertiesNames(), '\
              'newOrOverriddenPythonProperties(), '\
              'newOrOverriddenPythonPropertiesNames(), parentEntity(), '\
              'parentEntities(), '\
              'parentEntityName(), rootEntity(), removeSubEntity(), '\
              'subEntities()'
  }

def externalNameForInternalName(aName, separatorString='_', useAllCaps=1):
  """
  Turns an entity name into a valid name for database schema.

  With the default parameters, 'address' becomes 'ADDRESS',
  'firstName' becomes 'FIRST_NAME', and 'db2Id' becomes 'DB2_ID'

  Default separatorString is '_'.
  
  If 'useAllCaps' is set to 1, 'firstName' becomes 'first_name'
  """
  #If 'returnCaps' is set to '0', the returned string consists of lower case
  #letters, instead of upper case ones.
  #if useCaps:
  _regexp=re.compile('([a-z0-9])([A-Z])')
  _results=_regexp.sub('\\1'+separatorString+'\\2', aName)
  #else:
  #  _results=aName
  if useAllCaps:
    return string.upper(_results)
  else:
    return string.lower(_results)

def nameForExternalName(aName, separatorString='_', initialCaps=0):
  """
  Inverse function of externalNameForInternalName()

  Example: 'FIRST_NAME' becomes 'firstName', 'DB2_ID' becomes 'db2Id'
  and 'ENTITY_NAME' becomes 'EntityName' when initialCaps is set to '1'.
  """
  _parts=string.split(aName, separatorString)
  _result=""
  for _part in _parts:
    _result+=string.capitalize(_part)
  if not initialCaps and _result:
    return string.lower(_result[0])+_result[1:]
  return _result

def primaryKeyForGlobalID(aGlobalID):
  """
  Returns the primaryKey used when 'aGlobalID' was initialized, or None if
  'aGlobalID' is not a KeyGlobalID. The return value is a dictionary mapping
  the primary key's component(s) with its (their) respective value.

  See also: KeyGlobalID.__init__(), KeyGlobalID.keyValues()
  """
  try:
    return aGlobalID.keyValues()
  except AttributeError:
    return None
    

from Modeling.utils import base_persistent_object
class Entity(base_persistent_object, XMLCapability, KeyValueCoding):
  """Describes an entity

  An entity defines a class and its corresponding persistence schema.

  Implementation notes:
  - the role of 'typeName' is not clear enough for the moment being. It should
    probably be moved into a special recipient for parameters specifically
    needed at generation-time (as well as others generation-dedicated values:
     ...)
  

  Methods related to inheritance: %(inheritance_methods)s

  Unimplemented features:

    - fetch specifications

    - (beginning of-!) shared objects
  """
  # check: typeName XOR isAbstract _TBD

  # Support for older instances
  _className=''
  _isReadOnly=0
  _externalName=''
  _PKs=()
  _attrsUsedForLocking=()
  _moduleName=None
  _comment=''
  
  def __init__(self, name='', aModel=None):
    "Initializes an entity. A name **must** be provided."
    # Commented out becoz of persistence
    if (not name):# or (not aModel):
        raise ModelError, "A name should be provided at creation time."
    assert ( type(name)==types.StringType
             or type(name)==types.UnicodeType) , \
           "parameter name should be a string or unicode (type: <pre>'%s'</pre>, value: <pre>'%s'</pre>)" % (str(type(name)), str(name))
    name = str(name) # TBD: Unicode... ugly workaround
    self._attributes={} # name->Attribute
    self._attrsUsedForLocking=()
    self._className=None
    self._comment=''
    self._externalName=''
    self._model=None
    self._moduleName=None
    if aModel: aModel.addEntity(aModel)
    self._name=None
    self.setName(name)
    self._relationships={} # name->Relationship
    self._parentEntity=None # single?!
    self._PKs=()
    self._subEntities=()
    self._typeName=''
    self._isAbstract=0
    self._isReadOnly=0

  def addAttribute(self, anAttribute):
    """
    Adds 'anAttribute' to the set of this entity's attributes.
    Raises 'ModelError' if 'anAttribute' is not a 'Attribute'
    instance.
    Raises 'ValueError' if anAttribute's name is already registered by an
    attribute or a relationship. See also: definedKeys()
    """
    #assert type(anAttribute)==types.InstanceType and anAttribute.__class__ is Attribute
    if anAttribute.name() in self.definedKeys():
        raise ValueError, "Attempt to insert an attribute in entity %s but a key named '%s' is already registered." % (self.name(), anAttribute.name())
    self._attributes[anAttribute.name()]=anAttribute
    self._p_changed=1
    #_attrs = list(self._attributes)
    #_attrs.append(anAttribute)
    #self._attributes = tuple(_attrs)
    anAttribute._setEntity(self)

  def addRelationship(self, aRelationship):
    """
    Adds 'aRelationship' to the set of this entity's relationships.
    Raises 'ModelError' if 'aRelationship' is not a 'Relationship'
    instance.
    Raises 'ValueError' if aRelationship's name is already registered by an
    attribute or a relationship. See also: definedKeys()
    """
    #assert type(aRelationship)==types.InstanceType and aRelationship.__class__ is Relationship
    if aRelationship.name() in self.definedKeys():
        raise ValueError, "Attempt to insert a relationship in entity %s but a key named '%s' is already registered." % (self.name(), aRelationship.name())
    self._relationships[aRelationship.name()]=aRelationship
    self._p_changed=1
    #_rels = list(self._relationships)
    #_rels.append(aRelationship)
    #self._relationships = tuple(_rels)
    aRelationship.setEntity(self)

  def addSubEntity(self, aChildEntity):
    """
    Registers the supplied entity as a sub-entity (inheritance)

    See also: %(inheritance_methods)s

    """
    if aChildEntity in self._subEntities:
        raise ModelError, "Attempt to insert a subentity in entity %s but a sub-entity named '%s' is already registered." % (self.name(), aChildEntity.name())
    _subs = list(self._subEntities)
    _subs.append(aChildEntity)
    self._subEntities = tuple(_subs)
    aChildEntity._setParentEntity(self)
    
  def allSubEntities(self):
    """
    Returns the list of all sub-entities of the current entity, i.e. its
    direct sub-entities, the direct sub-entities of its sub-entities, etc.

    See also: %(inheritance_methods)s

    """
    _res=list(self._subEntities)
    for e in self._subEntities:
      _res+=e.allSubEntities()
    return _res
    
  def allSubEntitiesNames(self):
    """
    Returns the names of the entities returned by allSubEntities(), as a list

    See also: %(inheritance_methods)s
    
    """
    return [e.name() for e in self.allSubEntities()]
    
  def attributeNamed(self, aName):
    "Returns the attributes named 'aName' or 'None' if it does not exist."
    return self._attributes.get(aName)
    #for _attr in self.attributes():
    #  if _attr.name()==aName: return _attr
    #return None

  def attributes(self):
    """
    Returns the full set of attributes
    """
    return self._attributes.values()

  def attributesNames(self):
    "Returns the full set ('tuple') of attributes' name."
    #return tuple(map(lambda o: o.name(), self.attributes()))
    return tuple(self._attributes.keys())
  
  def attributesToFetch(self):
    """
    Returns a subset (a 'list') of the entity's attributes, those which fall
    into at least one of these categories:

      - they are not derived,
      
      - they are source attributes of a relationship

    """
    if hasattr(self, '_v_attributesToFetch'):
      return self._v_attributesToFetch
    
    attributesToFetch=[]

    def append(attribute, attributesToFetch):
      if attribute not in attributesToFetch:
        attributesToFetch.append(attribute)
        
    # PKs
    attributesToFetch.extend(self.primaryKeyAttributes())
    # Attributes used for locking
    for attribute in self.attributesUsedForLocking():    
      append(attribute, attributesToFetch); continue
    # non derived
    for attribute in self.attributes():
      if not attribute.isDerived():
        append(attribute, attributesToFetch); continue

    # Relationship's source attributes
    for rel in self.relationships():
      for srcAttr in rel.sourceAttributes():
        append(srcAttr, attributesToFetch)

    self._v_attributesToFetch=attributesToFetch
    return attributesToFetch

  def attributesUsedForLocking(self):
    """
    TBD
    """
    return self._attrsUsedForLocking

  def attributesUsedForLockingNames(self):
    return map(lambda o: o.name(), self._attrsUsedForLocking)

  def classDescriptionForInstances(self):
    """
    Returns the class description assigned to this entity

    You should rarely need to call this method directly ; instead, use the
    ClassDescription's static method 'classDescriptionForName()' which takes
    care of finding the appropriate class description.

    See also: ModelSet.classDescriptionForEntityName()
              ClassDescription.classDescriptionForName()
    """
    debug('_TBD: return either an Entity- or a CMFClassDescription')
    return EntityClassDescription(self)
    
  def className(self):
    "Returns the class name associated to this entity"
    return self._className
  
  def classProperties(self):
    """
    Returns the list of class properties (attributes+relationships)
    """
    return self.classProperties_attributes() + \
           self.classProperties_relationships()

  def classPropertiesNames(self):
    """
    Returns the list of class properties' names (attributes+relationships)
    """
    return map(lambda o: o.name(), self.classProperties())

  def classProperties_attributes(self):
    """
    Returns the list of attributes that are class properties
    """
    return tuple([attr for attr in self.attributes()
                  if attr.isClassProperty()])

  def classProperties_relationships(self):
    """
    Returns the list of relationships that are class properties
    """
    return tuple([rel for rel in self.relationships()
                  if rel.isClassProperty()])

  def comment(self):
    "Returns the comment field"
    return self._comment
  
  def definedKeys(self):
    """
    Returns the list of the entity's defined keys, i.e. its attributes' and
    relationships' names
    """
    return self.attributesNames()+self.relationshipsNames()

  def definesTableColumns(self):
    """
    Tells whether this entity defines table columns: if this entity has an
    externalName() and at least one of its attributes has a columnName(), the
    answer is '1' (true), '0' (false) otherwise.

    See also: SchemaGeneration.tableEntityGroupsForEntities()
    """
    if not self._externalName:
      return 0
    for attribute in self._attributes.values():
      if attribute.columnName():
        return 1
    return 0
  
  def destinationObjectForKeyPath(self, keyPath):
    """
    Returns the object ('Entity' or 'Attribute') at the end of
    the 'keyPath'. For example, say you have an entity 'Author' with a toMany
    relationship 'books' to the entity 'Book' which has an attribute 'title':
    the returned value for 'keypath="books.title"' is that Book's attribute
    named 'title'.

    Returned value is either an 'Attribute' or a 'Entity' object.

    Raises ValueError if keyPath is not a valid keypath (i.e. if it is None,
    empty or if at least one element in 'keyPath' does not correspond to a
    valid object in the model)

    See also: objectPathForKeyPath()
    """
    return self.objectsPathForKeyPath(keyPath)[-1]
  
  def objectsPathForKeyPath(self, keyPath):
    """
    Returns a list consisting in the Relationship instances that needs to be
    traversed to reach the final element, plus this final element which is
    either an Attribute or an Entity object.

    Raises ValueError if 'keyPath' is not a valid path.
    
    See also: destinationObjectForKeyPath()
    """
    if not keyPath:
      raise ValueError, "Invalid keypath"
    objPath=[]
    try:
      object=self
      for key in string.split(keyPath, '.'):
        rel=object.relationshipNamed(key)
        if rel:
          objPath.append(rel)
          object=rel.destinationEntity()
        else:
          object=object.attributeNamed(key)
          objPath.append(object)
      if not object:
        raise ValueError, "Invalid keypath '%s'"%keyPath
      if not isinstance(object, Attribute):
        objPath.append(rel.destinationEntity())
    except AttributeError:
      raise ValueError, "Invalid keypath '%s'"%keyPath

    return objPath
    
  def externalModelsReferenced(self):
    """
    Check all relationships defined in the entity, identify all (destination)
    entities which belong to a different model than receiver's one, and
    returns the list of their model.

    Returns a empty list when no external model are referenced.
    """
    _extModels=[]
    for _relEntity in map(lambda o: o.destinationEntity(), \
                          self.relationships()):
      if _relEntity.model() != self.model():
        _extModels.append(rel_Entity.model())
    return _extModels

  def externalName(self):
    """
    Returns the external name of the entity
    """
    return self._externalName

  def globalIDForRow(self, aRowDictionary):
    """
    Returns the GlobalID corresponding to the supplied raw data, as returned
    e.g. by 'AdaptorChannel.fetchRow()', to be used by an object belonging to
    this entity. Returned object's class is 'KeyGlobalID'.

    Parameter:

      aRowDictionary -- a dictionary whose keys are attributes'names, with
      their corresponding values.

    See also: primaryKeyForRow(), primaryKeyForGlobalID()
    """
    pkValues=self.primaryKeyForRow(aRowDictionary)
    return KeyGlobalID(self._name, pkValues)
    
  def isaValidAttributeName(self, aName):
    """
    Checks that 'aName' is a valid name for an attribute.
    A name is valid iff:
    - its first letter is a letter or an underscore,
    - no attribute is already registered with the same name.
    """
    return isaValidName(aName) and aName not in self.attributesNames()
        
  def isaValidRelationshipName(self, aName):
    """
    Checks that 'aName' is a valid name for a relationship.
    A name is valid iff:
    - its first letter is a letter or an underscore,
    - no relationship is already registered with the same name.
    """
    return isaValidName(aName) and aName not in self.relationshipsNames()
        
  def isAbstractEntity(self):
    "Indicates whether the entity is abstract."
    return self._isAbstract
  isAbstract=isAbstractEntity
  
  def isPrimaryKeyValidInObject(self, anObject):
    "_TBD to be implemented"
    raise "Unimplemented yet!"
  
  def isReadOnly(self):
    "Tells whether the receiver is a read-only attribute"
    return self._isReadOnly

  def isValidAttributeUsedForLocking(self, anAttribute):
    """
    Returns false if the attribute is not one of the Entity's attributes, or
    if it answers true to the message 'isDerived()' ; otherwise, returns true.
    """
    if type(anAttribute)==type('string'):
      anAttribute=self.attributeNamed(anAttribute)
    if anAttribute is None: return 0
    if anAttribute not in self.attributes():
      return 0
    if anAttribute.isDerived():
      return 0
    return 1
  
  def isValidPrimaryKey(self, anAttribute):
    """
    Checks whether the supplied attribute is a valid possible primary key.

    Parameter anAttribute can be either an Attribute instance or an
    attribute's name -- in any case the corresponding attribute's type can
    be either 'int' or 'string', nothing else.

    Returns *false* (integer '0') if anAttribute cannot be found in the
    entity, or if it does not belong to the entity ; returns '1' for *true*.
    """
    # _TBD ...if atribute is derived
    if type(anAttribute)==type('string'):
      anAttribute=self.attributeNamed(anAttribute)
    if anAttribute is None: return 0
    # Check entityName() rather than entity() itself since anAttribute's
    # entity can be acquisition-wrapped
    if anAttribute.entity().name()!=self.name(): return 0
    if anAttribute.type() not in ('string', 'int'): return 0
    return 1
  
  # properties: later user (e.g. 'id' is an hidden type) (externalName: same/table)
  def model(self):
    "Returns the model this entity is related to"
    return self._model
  parent=model
    
  def modelName(self):
    "Returns the name of the model this entity is related to"
    try:
      return self._model.name()
    except AttributeError:
      return ''
    
  def moduleName(self):
    """
    Returns the module's name where the corresponding class can be found
    If unset, defaults to 'className'

    See also: className(), Model.packageName()
    """
    if not self._moduleName:
      return self._className
    return self._moduleName
  
  def name(self):
    "Returns the Entity's name"
    return self._name
        
  def parentEntities(self):
    """Returns the parent entities (a la super-class, cf. inheritance)

    The returned set is ordered: it begins with the direct parent, and ends
    with the rootEntity().

    See also: %(inheritance_methods)s
    """
    parents=[]
    parent=self.parentEntity()
    while parent:
      parents.append(parent)
      parent=parent.parentEntity()
    return parents

  def parentEntity(self):
    """Returns the parent entity (a la super-class, cf. inheritance)
    
    See also: %(inheritance_methods)s

    """
    return self._parentEntity

  def parentEntityName(self):
    """
    Returns the parent entity's name (a la super-class, cf. inheritance)

    See also: %(inheritance_methods)s

    """
    return self._parentEntity and self._parentEntity.name() or ''

  def primaryKeyAttributeNames(self):
    """
    Returns a list containing the names of the attributes assigned to the
    entity's PK.
    """
    return map(lambda o: o.name(), self._PKs)
  
  def primaryKeyAttributes(self):
    """
    Returns the array of attributes used as primary keys
    """
    return self._PKs

  def primaryKeyForGlobalID(self, aGlobalID):
    """
    Returns the primaryKey used when 'aGlobalID' was initialized, or None if
    'aGlobalID' is not a KeyGlobalID. The return value is a dictionary mapping
    the primary key's component(s) with its (their) respective value.

    Note: this method is NOT relative to the current Entity and is strictly
          equivalent to KeyGlobalID.keyValues()

    See also: globalIDForRow(), primaryKeyForRow(),
              KeyGlobalID.__init__(), KeyGlobalID.keyValues()
    """
    return primaryKeyForGlobalID(aGlobalID)
    
  def primaryKeyForRow(self, aRowDictionary):
    """
    Computes the primary key from 'aRowDictionary'.

    'aRowDictionary' is a database row, i.e. a dictionary mapping a (TABLE's)
    columns' names to their values. The returned primary key is a
    sub-dictionary of aRowDictionary containing only the primary key.

    See also: primaryKeyAttributeNames(), globalIDForRow()
    """
    pkValues={}
    for attributeName in self.primaryKeyAttributeNames():
      pkValues[attributeName]=aRowDictionary[attributeName]
    return pkValues

  def primaryKeyRootName(self):
    """
    Returns the name to be used by SchemaGeneration and AdaptorChannel
    concrete subclasses, e.g. to generate and use SQL sequence to get primary
    keys of newly inserted objects.

    This method returns the externalName of the underlying entity's
    rootEntity, or simply its name if it has no externalName assigned
    (e.g. abstract entities).

    See also: rootEntity()
    """
    root=self.rootEntity()
    return root.externalName() or root.name()

  def propertyNamed(self, aName):
    """
    Return the property named 'aName' (either an Attribute or a Relationship)
    """
    return self.attributeNamed(aName) or self.relationshipNamed(aName)
    
  def propertyNameDidChange(self, oldName):
    """
    Internally called by Attribute and Relationship.setName() after a
    property's name has changed.

    Parameter:

      oldName -- the name of the property before it was changed

    Raises ValueError if no property could be found under the supplied name
    """
    attr=self.attributeNamed(oldName)
    if attr:
      del self._attributes[oldName]
      self._attributes[attr.name()]=attr
      self._p_changed=1
      return
    rel=self.relationshipNamed(oldName)
    if rel:
      del self._relationships[oldName]
      self._relationships[rel.name()]=rel
      self._p_changed=1
      return
    raise ValueError, "Unable to find property with name '%s'"%oldName
  
  def qualifierForPrimaryKey(self, row):
    """
    Returns the qualifier on can use to fetch the object identified by
    primary key values as supplied in 'row'

    This is typically called when a toOne fault ('AccessFaultHandler') is
    triggered and has to fetch the corresponding datas, or when an
    DatabaseObject is turned back into a fault.

    Raises ValueError if anything wrong happens (empty dictionary, keys that
    do not correspond to the entity's PKs, etc.)
    
    Parameter:

      row -- a dictionary with keys as PK attributes' names and their
      corresponding values.

    See also: interface 'Faulting', 'FaultHandler', 'AccessFaultHandler'
    """
    try:
      # check that the row is OK
      pkValues={}
      for key in self.primaryKeyAttributeNames():
        valueForKey=row.get(key)
        if not valueForKey:
          raise
        pkValues[key]=valueForKey
      return Qualifier.qualifierToMatchAllValues(pkValues)
    except:
      raise ValueError, 'Cannot compute the qualifier for PK for entity: %s '\
            'and row: %s'%(self._name, row)
                            
  def relationshipNamed(self, aName):
    "Returns the attributes named 'aName' or None if it does not exist."
    return self._relationships.get(aName)
    #for _rel in self.relationships():
    #  if _rel.name() == aName: return _rel
    #return None

  def relationshipsNames(self):
    "Returns the full set ('tuple') of relationships' name."
    return tuple(self._relationships.keys())
    #return tuple(map(lambda o: o.name(), self.relationships()))

  def relationships(self):
    """
    Returns the full set of relationships ; it is returned as a dictionary
    where keys are 'relationshipName's and values 'Relationship' instances.
    """
    return self._relationships.values()

  def removeAttribute(self, anAttribute):
    """
    Removes the supplied entity from the registered child entities.
    Parameter 'anEntity' can be either an 'Entity' instance or a
    subentity's name.
    Raises 'ModelError' if 'anEntity' cannot be found.
    """
    # _TBD: cf. referencesProperty(aProperty) (not implemented) which should
    # _TBD: be called before attempting to remove an attribute (or a rel.)
    #assert type(anAttribute) in (types.InstanceType, types.StringType) #+Assert
    if type(anAttribute)==types.StringType:
      attrName = anAttribute
      anAttribute = self.attributeNamed(anAttribute)
    else:
      attrName = anAttribute.name()
    try:
        del self._attributes[attrName]
        #_attrs = list(self._attributes)
        #_attrs.remove(anAttribute)
        #self._attributes=tuple(_attrs)
    except KeyError:
        raise ModelError, "Entity '%s' has no attribute named '%s'" % (self.name(), attrName)
    self._p_changed=1
    anAttribute._setEntity(None)

  def removeRelationship(self, aRelationship):
    """
    Removes the supplied relationship from the registered relationships.
    Parameter 'aRelationship' can be either an 'Relationship'
    instance or a relation's name.
    Raises 'ModelError' if 'aRelationship' cannot be found.
    """
    # _TBD: cf. referencesProperty(aProperty) (not implemented) which should
    # _TBD: be called before attempting to remove an attribute (or a rel.)
    #assert type(aRelationship) in (types.InstanceType, types.StringType) #+Assert
    if type(aRelationship)==types.StringType:
      relName = aRelationship
      aRelationship = self.relationshipNamed(aRelationship)
    else:
      relName = aRelationship.name()
    try:
      del self._relationships[relName]
      #_rels = list(self._relationships)
      #_rels.remove(aRelationship)
      #self._relationships=tuple(_rels)
    except KeyError:
      raise ModelError, "Entity '%s' has no relationship named '%s'" % (self.name(), relName)
    self._p_changed=1
    aRelationship.setEntity(None)

  def removeSubEntity(self, aChildEntity):
    """
    Removes the supplied entity from the registered child entities.
    Parameter 'aChildEntity' can be either an 'Entity' instance or a
    subentity's name.
    Raises 'ModelError' if 'anEntity' cannot be found.

    See also: %(inheritance_methods)s
    
    """
    if type(aChildEntity)==types.StringType:
        aChildEntity = self.relationshipNamed(aChildEntity)
    try:
        _subs = list(self._subEntities)
        _subs.remove(aChildEntity)
        self._subEntities=tuple(_subs)
    except ValueError:
        raise ModelError, "Entity '%s' has no sub-entity named '%s'" % (self.name(), aChildEntity.name())
    aChildEntity._setParentEntity(None)

  def restrictingQualifier(self):
    """
    Returns the restrcting qualifier. Not implemented yet, simply returns None

    NB: a restricting qualifier is handy when all entities and their root
    entity (speaking of inheritance) map to a single database.
    """
    return None
  
  def rootEntity(self):
    """
    Returns the root entity of the current entity

    See also: %(inheritance_methods)s
    
    """
    if self._parentEntity:
      return self._parentEntity.rootEntity()
    else:
      return self
    
  def setAttributesUsedForLocking(self, attributes):
    """
    """
    # _TBD Implementation Note: Attributes and Attributes'names can be mixed
    # _TBD within the same array, although this is strongly discouraged.
    if not isListOrTuple(attributes):
      raise ValueError, "Parameter 'attributes' must be a sequence"
    if not attributes:
      self._attrsUsedForLocking=()
      return
    _ok=1
    _attrs=[]
    for attr in attributes:
      if not self.isValidAttributeUsedForLocking(attr):
        _ok=0
        break
      # This allows developper to provide a list of objects where these
      # objects are instances of one of Attribute's derived classes 
      if type(attr) is types.StringType: attr=self.attributeNamed(attr)
      _attrs.append(self.attributeNamed(attr.name()))

    if not _ok:
      raise ModelError, "Invalid attributes"

    self._attrsUsedForLocking=tuple(_attrs)

  def setClassName(self, aName):
    "Sets the class name associated to this entity"
    self._className=aName

  def setComment(self, aComment):
    "Sets the comment field"
    self._comment=aComment
    
  def setExternalName(self, externalName):
    "Sets the external name"
    self._externalName=externalName

  def setIsAbstractEntity(self, aBool):
    """
    Tells this entity whether it is abstract, according to the
    supplied *boolean*.
    if aBool is a string equal to '0', it evaluates to 'false'
    """
    self._isAbstract=toBoolean(aBool)
  setIsAbstract=setIsAbstractEntity
  
  def setModuleName(self, aName):
    """
    Sets the module's name where the corresponding class can be found

    See also: moduleName()
    """
    self._moduleName=aName
  
  #def _setModel(self, aModel):
  #    "Sets the associated model. This method should NOT be called directly."
  #    assert type(aModel) is types.InstanceType and aModel.__class__ is Model
  #    self._model = aModel
      
  def setParentEntity(self, anEntity):
    """

    See also: %(inheritance_methods)s
    
    """
    if type(anEntity)==type(''):
      anEntity=self.model().entityNamed(anEntity)
    anEntity.addSubEntity(self)
      
  def _setParentEntity(self, anEntity):
    #assert type(anEntity)==types.InstanceType and anEntity.__class__==Entity
    self._parentEntity = anEntity

  def setPrimaryKeyAttributes(self, PKs):
    """
    Assigns the set of attributes used as primary keys ; empties the list of
    PKs if parameter is an empty sequence.

    Parameter 'PKs' can be either a sequence of Attributes or a sequence of
    Attributes' names.

    Raise ValueError if parameter 'PKs' is not a sequence.

    Raises 'ModelError' if 'self.isValidPrimaryKey()' returns false for at
    least one of the attributes.
    """
    # _TBD Implementation Note: Attributes and Attributes'names can be mixed
    # _TBD within the same array, although this is strongly discouraged.
    if PKs in ( (), [] ):
      self._PKs=()
      return
    if not isListOrTuple(PKs):
      raise ValueError, "Parameter 'PKs' must be a sequence"
    #if len(PKs)>1:
    #  raise NotImplementedError,'Sorry, we only support singletons as PKs '\
    #        'for the moment being'
    _ok=1
    _PKs=[]
    for PK in PKs:
      if not self.isValidPrimaryKey(PK):
        _ok=0
        break
      # This allows developper to provide a list of objects where these
      # objects are instances of one of Attribute's derived classes 
      if type(PK) is types.StringType: PK=self.attributeNamed(PK)
      _PKs.append(self.attributeNamed(PK.name()))

    if not _ok:
      raise ModelError, "Invalid PKs"

    self._PKs=tuple(_PKs)

  def _setModel(self, aModel):
    """
    Private method called by Model.addEntity() after the necessary checks that
    take care of enforcing that entities share a single namespace for their
    names are done.

    You should never call this method directly, since this could defeat the
    checks described above: in that case, the Entity could become unreachable
    from either its Model or ModelSet.
    """
    self._model = aModel

  def setName(self, aName):
    """
    Sets the name for the entity.
    Raises 'ModelError' if the name is invalid or if it is already used by
    an other entity in the Model or the enclosing ModelSet.

    See also: Model.isEntityNameDeclaredInNamespace()
    """
    if aName is not None and aName==self._name: return
    if not isaValidName(aName):
        raise ModelError, "Supplied name incorrect (%s)" % aName
    if self._model and self._model.isEntityNameDeclaredInNamespace(aName):
      raise ModelError, "Attempt to change an entity's name but that "\
            "name: %s is already registered." % (aName,)
    self._name = aName
        
  def setReadOnly(self, aBoolean):
    "Sets the read-only status for this entity"
    self._isReadOnly=toBoolean(aBoolean)
    
  def setRestrictingQualifier(self, aQualifier):
    """
    Sets the restricting qualifier.

    Not implemented yet, this method unconditionally raises
    NotImplementedError.

    See also: restrictingQualifier()
    """
    raise NotImplementedError, 'Unimplemented yet'
  
  def setTypeName(self, aName):
    "Sets the meta-type associated with this entity."
    #assert type(aName) is types.StringType
    self._typeName = aName
        

  ## __TBD Commented out: this is stupid. This portion of code will disappear
  ## __TBD as soon as I'm sure it is really useless!! [snapshotForRow]
  ## __TBD was called: by: DatabaseChannel.fetchObject()
  #def snapshotForRow(self, aRowDictionary):
  #  """
  #  Builds the snapshot for the corresponding entity, given the aRowDictionary
  #  (e.g. the raw data as extracted by 'AdaptorChannel.fetchRow()').
  #
  #  See also: attributesUsedForLocking()
  #  """
  #  dict={}
  #  for attributeName in self.attributesUsedForLockingNames():
  #    dict[attributeName]=aRowDictionary[attributeName]
  #  return dict
  
  def subEntities(self):
    """

    See also: %(inheritance_methods)s

    """
    return self._subEntities
        
  def subEntitiesNamed(self, aName):
    """
    Returns the sub-entity named 'aName' or None if it does not exist.

    See also: %(inheritance_methods)s

    """
    for _sub in self.subEntities():
        if _sub.name() == aName: return _sub
    return None
       
  def typeName(self):
    "Returns the meta-type associated with this entity"
    # _TBD: suppress the explicit storage of this and get it dynamically from
    # _TBD  class==className
    return self._typeName

  def validateValue(self, object):
    """
    Checks whether the supplied object is valid.
    Each attribute and relationship related to this entity is checked against
    the object's values.
    Raises:

      - Validation.ValidationException if the supplied object is not related
        to the current entity, or if it is not under model control at all.

      - Validation.ValidationException if the validation failed
    """
    try:
      if object.entityName() != self.name():
        raise ModelValidationError, \
              "Parameter 'object' ('%s') 's entityName is not '%s'" \
              % (str(object), self.name())
    except ModelValidationError: raise
    except AttributeError:
      raise ModelValidationError, \
            "Parameter 'object' ('%s') does not respond to 'entityName()'" \
            % (object,)

    _error=Validation.ValidationException()
    # Validation of class properties
    for _prop in self.classProperties():
      try:
        _prop.validateValue(object.storedValueForKey(_prop.name()), object)
      except Validation.ValidationException, exc:
        _error.aggregateException(exc)

    # Time to validate object as a whole: ok, this is done in ClassDescription
    
    _error.finalize()

  #def __eq__(self, anEntity):
  #  "equality test"
  #  raise "Unimplemented"

  ##
  ## Methods related to inheritance
  ##
  def clone(self, name=None, model=None):
    """
    Returns a copy of the current Entity

    This is not used in the core, but a modelization tool could take
    advantage of it to prepare a subEntity for the current Entity.

    Note: remember that, contrary to a object-model where inherited properties
    are not specified again in subEntity, the goal of a model is to propose a
    description on how properties are mapped to a database, hence: a subentity
    describes each of it parent's properties as well (see documentation on
    howto to design a model, paragraph 'Dealing with inheritance', for further
    details).

    Parameters:

      name -- optional. It it is provided, the cloned entity gets that name,
      otherwise defaults to 'self.name()'

      model -- optional. If it is provided, the cloned Entity is added to the
        model ; note that in this case, it is strongly recommended to provide
        the name for the clone (since two entities cannot have the same name
        within a single Model or ModelSet --cf. Model.addEntity())

    """
    clone=Entity(name or self.name())

    # the mechanism used here relies on functionalities decl. in XMLCapability
    clonedProps=('className', 'externalName', 'isReadOnly', 'isAbstract',
                 'parentEntity', 'typeName')
    #
    for prop in clonedProps:
      clone.xmlSetAttribute(prop)(self.xmlGetAttribute(prop)())

    for attr in self.attributes():
      clone.addAttribute(attr.clone())

    for rel in self.relationships():
      rel.clone(clone)

    clone.setPrimaryKeyAttributes(self.primaryKeyAttributeNames())
    clone.setAttributesUsedForLocking(self.attributesUsedForLockingNames())
    
    if model:
      model.addEntity(clone)

    return clone
      
  def definesProperty(self, property):
    """
    Tells whether this Entity declares the supplied property

    Parameter:

      property -- an Attribute or Relationship instance

    Returns a true value (integer 1) iff all the following conditions evaluate
    to true:

      - the property and the corresponding one in 'self' have the same python
        type if both are attributes (cf. Attribute.type()), or the same
        destinationEntity if both are relationships.

    Otherwise, returns false.
    
    This is not used within the framework's core, rather when python code is
    generating from a model ; this allows the generatiion mechanism to know if
    an attribute is overridden or new in a subclass.

    See also: newOrOverriddenProperties()
    """
    if property.name() in self.attributesNames()+self.relationshipsNames():
      try:
        self_prop=self.attributeNamed(property.name())
        if self_prop and self_prop.type()==property.type():
          return 1
        self_prop=self.relationshipNamed(property.name())
        if self_prop and \
           self_prop.destinationEntity()==property.destinationEntity():
          return 1
      except AttributeError:
        # this can happen if, e.g., property is an Attribute and property
        # is a relationship
        return 0
    return 0

  def definesPythonProperty(self, property):
    """
    Tells whether this Entity declares the supplied (python) property

    Parameter:

      property -- an Attribute or Relationship instance

    Returns a true value (integer 1) iff all the following conditions evaluate
    to true:

      - the property'name is in 'self.classPropertiesNames()'

      - the property and the corresponding one in 'self' have the same python
        type if both are attributes (cf. Attribute.type()), or the same
        destinationEntity if both are relationships.

    Otherwise, returns false.

    This is not used within the framework's core, rather when python code is
    generating from a model ; this allows the generatiion mechanism to know if
    an attribute is overridden or new in a subclass.

    See also: newOrOverriddenPythonProperties()
    """
    if property.name() in self.classPropertiesNames():
      try:
        self_prop=self.attributeNamed(property.name())
        if self_prop and self_prop.type()==property.type():
          return 1
        self_prop=self.relationshipNamed(property.name())
        if self_prop and \
           self_prop.destinationEntity()==property.destinationEntity():
          return 1
      except AttributeError:
        # this can happen if, e.g., property is an Attribute and self_prop
        # is a relationship
        return 0
    return 0

  def newOrOverriddenProperties(self):
    """
    Returns the list of entity's properties (attributes or relationships)
    which are not declared in the parent entity, or all its attributes and
    relationships if the entity does not have a parent entity.

    This is not used within the framework's core, but a GUI designed to build
    model can take advantage of this to distinguish inherited from
    non-inherited properties in an entity.
    
    See also: definesProperty()
    See also: %(inheritance_methods)s

    """
    props=self.attributes()+self.relationships()
    parent=self.parentEntity()
    if not parent:
      return props

    return [p for p in props if not parent.definesProperty(p)]
        
  def newOrOverriddenPropertiesNames(self):
    """
    Returns the list of the names of properties returned by
    newOrOverriddenProperties()

    See also: newOrOverriddenProperties(), newOrOverriddenPythonProperties()
    """
    return [p.name() for p in self.newOrOverriddenProperties()]

  def newOrOverriddenPythonProperties(self):
    """
    Returns the list of entity's python properties (attributes or
    relationships) which are not declared in the parent entity, or all its
    attributes and relationships if the entity does not have a parent entity.

    This is not used within the framework's core, but a process generating
    code templates from a model can take advantage of this to distinguish
    inherited python properties from new ones in a given entity.
    
    For a description of what precisely is a 'python property', see
    definesPythonProperty()

    See also: newOrOverriddenProperties()
    """
    props=self.classProperties()
    parent=self.parentEntity()
    if not parent:
      return props

    return [p for p in props if not parent.definesPythonProperty(p)]
        
  def newOrOverriddenPythonPropertiesNames(self):
    """
    Returns the list of the names of properties returned by
    newOrOverriddenPythonProperties()
    """
    return [p.name() for p in self.newOrOverriddenPythonProperties()]


  ## XMLCapability
  def initWithXMLDOMNode(self, aNode, phase=1, parent=None,
                         encoding='iso-8859-1'):
    """
    Initializes an entity with the supplied xml.dom.node.

    Parameter phase can be either 1 or 2 (but nothing else).
    Phase 1 initializes the attribute, phase 2 initializes relationships
    
    A XMLutils.XMLImportError is raised if the entity is not empty, i.e. if it
    already holds some attributes and/or relationships.
    """
    if phase==1 and (self.attributes() or self.relationships()):
      raise XMLImportError, "Cannot initialize a non-empty entity" 
    if phase not in (1,2):
      raise ValueError, 'Ooops, parameter phase should be 1 or 2!'

    k_v=aNode.attributes.items()
    for attributeName, value in k_v:
      # Iterate on attributes which are in the xml
      attrType=self.xmlAttributeType(attributeName)
      set=self.xmlSetAttribute(attributeName)
      if attrType=='string': value=unicodeToStr(value, encoding)
      elif attrType=='number': value=int(value)
      elif attrType=='bool': value=int(value)
      set(value)
      
    if phase==1:
      # Attributes first!
      attributesNodes=xpath.Evaluate('attribute', contextNode=aNode)
      for node in attributesNodes:
        name=node.getAttribute('name')
        attribute=Attribute(unicodeToStr(name, encoding))
        attribute.initWithXMLDOMNode(node, encoding)
        self.addAttribute(attribute)
        
      # PKs
      PKsNodes=xpath.Evaluate('primaryKey', contextNode=aNode)
      _pks=[]
      for node in PKsNodes:
        name=node.getAttribute('attributeName')
        attribute=self.attributeNamed(unicodeToStr(name))
        if attribute is None:
          raise XMLImportError, "Cannot assign to entity '%s' the attribute "\
                "'%s' as primary key since this attribute is not defined"%\
                (self.name(), name)
        _pks.append(attribute)
      self.setPrimaryKeyAttributes(_pks)

      # Attributes used for locking
      lockinNodes=xpath.Evaluate('attributesUsedForLocking', contextNode=aNode)
      _lockins=[]
      for node in lockinNodes:
        name=node.getAttribute('attributeName')
        attribute=self.attributeNamed(unicodeToStr(name))
        if attribute is None:
          raise XMLImportError, "Cannot assign to entity '%s' the attribute "\
                "'%s' as a 'used for locking' attribute since this attribute "\
                "is not defined" % (self.name(), name)
        _lockins.append(attribute)
      self.setAttributesUsedForLocking(_lockins)

    elif phase==2:
      # Relationships & inheritance
      from Relationship import SimpleRelationship,FlattenedRelationship
      parent=xpath.Evaluate('string(@parentEntity)', contextNode=aNode)
      if parent and parent!='None':
        parent=unicodeToStr(parent, encoding)
        self.setParentEntity(parent)
      relationshipsNodes=xpath.Evaluate('relation', contextNode=aNode)
      for node in relationshipsNodes:
        name=node.getAttribute('name')
        relationship=SimpleRelationship(unicodeToStr(name, encoding))
        self.addRelationship(relationship)
        relationship.initWithXMLDOMNode(node, encoding)
      relationshipsNodes=xpath.Evaluate('flattenedRelation', contextNode=aNode)
      for node in relationshipsNodes:
        name=node.getAttribute('name')
        relationship=FlattenedRelationship(unicodeToStr(name, encoding))
        self.addRelationship(relationship)
        relationship.initWithXMLDOMNode(node, encoding)

    return
    
  def getXMLDOM(self, doc=None, parentNode=None, encoding='iso-8859-1'):
    """
    Returns the (DOM) DocumentObject for the receiver.

    Parameters 'doc' and 'parentDoc' should be both omitted or supplied.
    If they are omitted, a new DocumentObject is created.
    If they are supplied, elements are added to the parentNode.

    Returns: the (possibly new) DocumentObject.
    """
    if (doc is None) ^ (parentNode is None):
      raise ValueError, "Parameters 'doc' and 'parentNode' should be together supplied or omitted"
    if doc is None:
      doc=createDOMDocumentObject('model')
      parentNode=doc.documentElement
      node=parentNode
    else:
      node=doc.createElement('entity')
      parentNode.appendChild(node)

    exportAttrDict=self.xmlAttributesDict()
    for attr in exportAttrDict.keys():
      attrType=self.xmlAttributeType(attr)
      value=self.xmlGetAttribute(attr)()
      if attrType=='bool': value=int(value)
      value=strToUnicode(str(value), encoding)
      node.setAttribute(attr, value)

    # PKs manually added
    for attribute in self.primaryKeyAttributes():
      pknode=doc.createElement('primaryKey')
      pknode.setAttribute('attributeName', strToUnicode(attribute.name()))
      node.appendChild(pknode)

    # Attributes usedForLocking manually added
    for attribute in self.attributesUsedForLocking():
      pknode=doc.createElement('attributesUsedForLocking')
      pknode.setAttribute('attributeName', strToUnicode(attribute.name()))
      node.appendChild(pknode)

    #cmpProp=lambda p1, p2: (p2.isClassProperty()-p1.isClassProperty()) or \
    #         cmp(string.lower(p1.name()), string.lower(p2.name()))
    cmpProp=lambda p1, p2: cmp(string.lower(p1.name()),string.lower(p2.name()))

    attributes=list(self.attributes())
    attributes.sort(cmpProp)
    relationships=list(self.relationships())
    relationships.sort(cmpProp)

    for attribute in attributes:            # Attributes
      attribute.getXMLDOM(doc, node, encoding)
    for relationship in relationships:      # Relationships
      relationship.getXMLDOM(doc, node, encoding)
    return doc

  def xmlAttributesDict(self):
    "."
    return {
      'name':       ('string',
                     lambda self=None,p=None: None,
                     self.name ),
      'className': ( 'string',
                     self.setClassName,
                     self.className ),
      'comment':   ( 'string',
                     self.setComment,
                     self.comment),
      'externalName': ( 'string',
                        self.setExternalName,
                        self.externalName ),
      'isReadOnly': ( 'bool',
                      self.setReadOnly,
                      self.isReadOnly ),
      'isAbstract': ( 'bool',
                      self.setIsAbstract,
                      self.isAbstract ),
      'moduleName': ( 'string',
                      self.setModuleName,
                      self.moduleName ),
      'parentEntity': ( 'string',
                        lambda self=None,p=None: None, # self.setParentEntity,
                        self.parentEntityName ),
      #'primaryKey': ( 'string',
      #                self.a,
      #                self. ),
      'typeName':   ( 'string',
                      self.setTypeName,
                      self.typeName )
      }

  ##
  ## KeyValueCoding error handling
  ##
  def handleAssignementForUnboundKey(self, value, key):
    if key=='doc': self.setComment(value)
    else:
      raise AttributeError, key
  handleTakeStoredValueForUnboundKey=handleAssignementForUnboundKey

  ##
  ## TBD Validation of an Entity
  ## 

##
#finalize_docstrings(Entity, ENTITY_DOC)


www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.