Attribute.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 » Attribute.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...

  CVS information

    $Id: Attribute.py 968 2006-02-25 15:46:25Z sbigaret $

"""

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

# Modeling
from Modeling.utils import isaValidName,toBoolean
from Modeling.XMLutils import *
from Modeling.Model import ModelError
from Modeling.KeyValueCoding import KeyValueCoding
from Modeling import Validation
from Modeling.utils import capitalizeFirstLetter

# Zope 2.4
try:
  from Products.PluginIndexes.TextIndex.TextIndex import TextIndex
  from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex
  from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex
except:
  class Index:
    def __init__(self, meta_type):
      self.meta_type=meta_type
  TextIndex=Index('TextIndex')
  FieldIndex=Index('FieldIndex')
  KeywordIndex=Index('KeywordIndex')
# python
import types, string, sys, time
from cgi import escape
from mx.DateTime import DateTime,DateFrom,Time

# Module constants
# useless, should be dropped
NOT_INDEXED   = 'Not indexed'
TEXT_INDEX    = TextIndex.meta_type
FIELD_INDEX   = FieldIndex.meta_type
KEYWORD_INDEX = KeywordIndex.meta_type
SUPPORTED_INDEX_TYPE_NAMES= (NOT_INDEXED,
                             TEXT_INDEX,
                             FIELD_INDEX,
                             KEYWORD_INDEX)
SUPPORTED_INDEX_TYPES     = (None,
                             TextIndex,
                             FieldIndex,
                             KeywordIndex)


def date_types():
  "Returns the set of valid date types"
  avail_types=[ type(DateTime(0)), type(Time(0)) ]
  try:
    import DCOracle2
    avail_types.append(type(DCOracle2.Date(0,0,0)))
  except ImportError:
    pass
  try:
    from time import localtime
    from datetime import date,datetime
    avail_types.append( type(datetime(*localtime(0)[:3])) )
    avail_types.append( type(date(*localtime(0)[:3])) )
  except ImportError:
    pass
  return avail_types
  

def availableTypes():
  #return ('string'(py2.1)/'str'(py2.2), 'int', 'float', 'tuple')
  avail_types=[ types.StringType,
                types.IntType,
                types.FloatType,
                #types.TupleType,
                ]
  avail_types.extend(date_types())
  return tuple([t.__name__ for t in avail_types])

from Modeling.utils import base_persistent_object

class Attribute(base_persistent_object, XMLCapability, KeyValueCoding):
  "Describes an attribute"
  # + public/private _TBD
  # _TBD check: defaultValue of correct type

  #
  _isClassProperty=1
  _definition=''
  _isFlattened=0
  _isDerived=0
  _precision=0
  _scale=0
  _externalType=''
  _columnName=''
  _width=0
  _comment=''
  
  def __init__(self, aName='', anEntity=None):
    "Initializes an attribute. A name **must** be provided."
    if (not aName):# or (not anEntity):
      raise ModelError, "Attribute: A name should be provided at creation time"
    assert type(aName)==types.StringType, "parameter name should be a string (type: <pre>'%s'</pre>, value: <pre>'%s'</pre>)" % (escape(str(type(aName)),1), escape(str(aName),1))
    #assert type(anEntity)==types.InstanceType and anEntity.__class__ is Entity
    if not isaValidName(aName):
      raise ModelError, "The name '%s' is not a valid name"
    self._allowsNone=1
    self._columnName=''
    self._comment=''
    self._defaultValue=None
    self._definition=''
    self._displayLabel=''
    self._entity=None
    self._externalType=''
    self._name=aName
    self._isClassProperty=1
    self._isDerived=0
    self._isFlattened=0
    self._precision=0
    self._scale=0
    self._type = types.StringType.__name__
    self._width=0
    if anEntity:
      anEntity.addAttribute(self)
        
  def allowsNone(self):
    """
    Indicates whether the attribute can be empty (None) or not
    (see isRequired inverse method)
    """
    return self._allowsNone
  allowsNull=allowsNone
  
  def availableTypes(self):
    "Returns all available types"
    return availableTypes()
  
  def availableIndexTypeNames(self):
    "Returns all available type names, as a tuple"
    return SUPPORTED_INDEX_TYPE_NAMES

  def clone(self, name=None):
    """
    Returns a copy of the current Attribute
    
    Parameter:

      name -- optional. If provided the clone gets that name, otherwise
      defaults to 'self.name()'.
      
    See also: Entity.clone()
    """
    clone=Attribute(name or self.name())
    # the mechanism used here relies on functionalities decl. in XMLCapability
    for prop in self.fullPropertySet().keys():
      clone.xmlSetAttribute(prop)(self.xmlGetAttribute(prop)())

    return clone
  
  def columnName(self):
    "..."
    return self._columnName
  
  def comment(self):
    "Returns the comment field"
    return self._comment
  
  def defaultValue(self):
    "Returns the default value associated to this attribute"
    return self._defaultValue

  def defaultValueAsPythonStatement(self):
    """
    Returns the default value in a string that can be used in python code,
    e.g. in assignments

    This is mainly for use in the generation of templates of code
    """
    if self.defaultValue()==None:
      return 'None'
    my_type=self.type()
    if my_type in ('string', 'str'):
      #if self.defaultValue() is None: return 'None'
      import re
      return "'%s'"%re.sub("'", "\\'", self.defaultValue())
    if my_type=='int':    return str(self.defaultValue())
    if my_type=='float':  return str(self.defaultValue())
    #if my_type=='tuple':  return str(self.defaultValue())
    if my_type=='float':  return str(self.defaultValue())
    if my_type=='DateTime':  return "DateTimeFrom('%s')"%str(self.defaultValue())
    return None
  
  def definition(self):
    "Returns the definition for a flattened or derived attribute"
    return self._definition
  
  def displayLabel(self):
    "Returns the label associated to the current attribute"
    return self._displayLabel
    
  def entity(self):
    "Returns the associated 'Entity'"
    return self._entity
  parent=entity

  def externalType(self):
    """
    """
    return self._externalType
  
  def finalAttribute(self):
    """
    Returns the final attribute pointed by a flattened attribute.
    Returns None if none can be found, or if the attribute is not flattened

    See also: isFlattened(), definition(), relationshipPath(),
              relationshipPathObjects()
    """
    if not self.isFlattened(): return None
    attr=None
    path=string.split(self._definition, '.')
    try:
      finalRel=self.relationshipPathObjects()[-1]
      attr=finalRel.destinationEntity().attributeNamed(path[-1])
    except:
      pass
    return attr
  
  def isClassProperty(self):
    "Indicates whether the attribute belongs to the class properties/fields"
    return self._isClassProperty

  def isDerived(self):
    """
    Tells whether the attribute is derived. Note that a flattened attribute
    is also considered derived
    """
    return (self._isDerived or self._isFlattened)
  
  def isFlattened(self):
    "Indicates whether the attribute is flattened"
    return self._isFlattened

  def isNotClassProperty(self):
    "negation of isClassProperty"
    return not self.isClassProperty()
  
  #def isPrivate(self):
  #  "Indicates whether the attribute is private"
  #  return not self.isPublic()
  #
  #def isPublic(self):
  #  "Indicates whether the attribute is public"
  #  return self._isPublic

  def name(self):
    "Returns the attribute's name "
    return self._name

  def isRequired(self):
    "Returns whether the attribute is required (see also: allowsNone)"
    return not self._allowsNone

  def precision(self):
    """
    """
    return self._precision
  
  def readFormat(self):
    """
    Simply returns the attribute's columnName()
    """
    return self.columnName()
  
  def relationshipPath(self):
    """
    Returns the relationshipPath (string) for a flattened attribute, or
    None if the attribute is not flattened

    The relationshipPath consists of the definition() from which the last
    part (which designates the finalAttribute()) is stripped off.
    For example, the relationshipPath for an attribute whose definition is
    'relationship1.relationship2.attribute' is 'relationship1.relationship2'.
    
    See also: isFlattened(), definition(), finalAttribute(),
              relationshipPathObjects()
    """
    if not self.isFlattened(): return None
    return string.join(string.split(self.definition(), '.')[:-1], '.')
  
  def relationshipPathObjects(self):
    """
    Returns the list ('tuple')of Relationship objects that should be traversed
    to obtain the finalAttribute(), or None if the attribute is not flattened
    
    See also: isFlattened(), definition(), finalAttribute(),
              relationshipPath()
    """
    if not self.isFlattened(): return None

    path=string.split(self._definition, '.')
    rels=[]
    currentEntity=self.entity()
    for relName in path[:-1]:
      rel=currentEntity.relationshipNamed(relName)
      rels.append(rel)
      currentEntity=rel.destinationEntity()
    return tuple(rels)
  
  def scale(self):
    """
    """
    return self._scale
  
  def setAllowsNone(self, allowsNone):
    """
    Tells the attribute whether it may have 'None' value or not
    (see inverse method setIsRequired)
    """
    self._allowsNone=toBoolean(allowsNone)

  def setColumnName(self, columnName):
    "..."
    self._columnName=columnName
  
  def setComment(self, aComment):
    "Sets the comment field"
    self._comment=aComment
    
  def convertStringToAttributeType(self, aValue):
    """
    Internally used to convert a string value (taken from a GUI, for example)
    to the correct type.

    See: setDefaultValue()
    """
    # NB: types.StringType.__name__: 'string' in py2.1, 'str' in py2.2
    if aValue=='None':
      if self.type() in availableTypes():
        return  None
      else:
        raise ModelError, \
              "Invalid 'None' default value for attribute '%s' (type: %s)"%(self._name, self.type())
    if not aValue:
      if self.type() in ('string', 'str'): return ""
      if self.type()=='int':    return 0
      if self.type()=='float':  return 0.0
      #if self.type()=='tuple':  return ()
      if self.type()=='DateTime':  return DateFrom(time.time())
    try:
      if self.type() in ('string', 'str'): return str(aValue)
      if self.type()=='int':    return int(aValue)
      if self.type()=='float':  return float(aValue)
      #if self.type()=='tuple':
      #  _tmp=eval(aValue)
      #  if type(_tmp)!=types.TupleType: raise ValueError
      #  return _tmp
      if self.type()=='DateTime':
        if type(aValue) not in date_types():
          # Some python db-adapters, such as MySQLdb, already return the
          # appropriate type
          return DateFrom(aValue)
        return aValue
    except (ValueError, TypeError):
      raise ModelError, \
            "Invalid value ('%s') for attribute '%s' type '%s' "%(aValue, self._name,self.type())
    
  def setScale(self, scale):
    """
    """
    self._scale=scale
  
  def setDefaultValue(self, aValue):
    "Sets the default value associated to this attribute"
    self._defaultValue=self.convertStringToAttributeType(aValue)
    
  def setDefinition(self, definition):
    "Sets the definition for a flattened or derived attribute"
    self._definition=definition
  
  def setDisplayLabel(self, aName):
    "Sets the label associated to the attribute"
    self._displayLabel = aName
    
  def _setEntity(self, anEntity):
    "Binds the attribute with the supplied entity"
    self._entity=anEntity
    
  def setExternalType(self, externalType):
    """
    """
    self._externalType=externalType
  
  def setIsClassProperty(self, aBool):
    "Tells the receiver whether it belongs to the class properties/fields"
    self._isClassProperty = toBoolean(aBool)

  def setIsFlattened(self, aBool):
    """
    Tells the attribute that it is flattened
    See also: setDefinition()
    """
    self._isFlattened = toBoolean(aBool)
  
  def setPrecision(self, precision):
    """
    """
    self._precision=int(precision)
  
  def setWidth(self, width):
    "Sets the maximum size for that attribute"
    self._width=int(width)
    
  #def setIsPrivate(self, aBool):
  #  "Tells the attribute whether it is private"
  #  self._isPublic = not toBoolean(aBool)
  #
  #def setIsPublic(self, aBool):
  #  "Tells the attribute whether it is public"
  #  self._isPublic = toBoolean(aBool)
  
  def setIsRequired(self, isRequired):
    "Tells the attribute whether is required or not (inverse of setAllowNone)"
    self._allowsNone=not toBoolean(isRequired)

  def setName(self, aName):
    "Sets the attribute's name. Raises if invalid"
    if not isaValidName(aName):
      raise ModelError, "Supplied name incorrect (%s)" % aName
    
    oldName=self._name
    self._name = aName
    if self._entity:
      self._entity.propertyNameDidChange(oldName)
      
  def setType(self, aType, check=1):
    """
    Sets the type associated to the attribute.
    aType object should be an available type's name
    (see Modeling.Attribute.availableTypes() for a complete list
    Specify 'check=0' if you do not want the 'defaultValue' to be
    verified against the new type.
    """
    if sys.version_info >= (2, 2): # python v2.2 and higher
      # backward compatibility, for models generated with python<=2.1
      if aType=='string':
        aType='str'
    if aType in availableTypes():
      self._type = aType
    else:
      raise ModelError, "Invalid parameter aType ('%s')" % str(aType)
    if check: self.setDefaultValue(str(self._defaultValue))
    
  def type(self):
    """
    Returns the type associated to the attribute.
    """
    return self._type

  def validateValue(self, value, object=None):
    """
    Checks whether the supplied object is valid, i.e. the following tests are
    made:

      1. value cannot be *void* ('not value' equals to 1) if attribute is
         required

      2. value has the same type than the attribute's one

      3. if self.type() is 'string' and self.width() is set and non-zero, the
         length of 'value' should not exceeded self.width()

      4. (Not implemented yet) if attribute is constrained, the value is part
         of the constrained set (repeat: not implemented yet)

    Parameter 'value' is required, and 'object' is optional --it is
    only used to make the error message clearer in case of failure.

    Silently returns if it succeeds.

    In case of failure, it raises 'Modeling.Validation.ValidationException' ;
    in this case, exception's *value* ('sys.exc_info[1]') consists in a set
    of lines, describing line per line the different tests which failed.
    """
    #if object is None: # or object is None:
    #  raise ValueError, "Parameter 'object' is required"

    _error=Validation.ValidationException()
    # isRequired?
    if value is None and self.isRequired():
      _error.addErrorForKey(Validation.REQUIRED, self.name())

    # correct types?
    if value is not None:
      # int and long int are equivalent
      if type(value)==types.LongType:
        if self.type()!=types.IntType.__name__:
          _error.addErrorForKey(Validation.TYPE_MISMATCH, self.name())      
      
      elif type(value) in date_types():
        if self.type() not in [t.__name__ for t in date_types()]:
          _error.addErrorForKey(Validation.TYPE_MISMATCH, self.name())
      
      elif type(value).__name__!=self.type():
        _error.addErrorForKey(Validation.TYPE_MISMATCH, self.name())
        
      # String should not exceed width
      if type(value)==types.StringType and self.type()=='string' and \
         self.width():
        if len(value)>self.width():
          _error.addErrorForKey(Validation.TYPE_MISMATCH, self.name())
          #print 'len(value): %s != self.width(): %s'%(len(value),self.width())
        
    _error.finalize()
    return

  def width(self):
    """
    Returns the maximum width for this attribute. Should be '0' (zero) for
    date and numbers
    """
    return self._width
  
  def __str__(self):
    """
    Returns a printable representation of the attribute ; the format is
    <entityName>.<attributeName>.
    If self.entity() is not already set, then it returns None.
    Note: this string uniquely identificates an attribute in a given ModelSet
    or Model since entities'names share the same namespace
    """
    _entity=self.entity()
    if _entity:
      return _entity.name()+'.'+self.name()
    else:
      return ""

  ## XML
  def initWithXMLDOMNode(self, aNode, encoding='iso-8859-1'):
    """
    Initializes a model with the supplied xml.dom.node.

    """
    k_v=aNode.attributes.items()
    # Now we must make sure that the type is initialized BEFORE the default
    # value is set --> we simply make sure that this will be the first one
    # to be initialized
    try:
      t=[a for a in k_v if a[0]=='type'][0] #IndexError
      k_v.remove(t)
      k_v=[t]+k_v
    except IndexError: pass
    
    for attributeName, value in k_v:
      # Iterate on attributes declared in node
      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)

  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('attribute')
      parentNode=doc.documentElement
      node=parentNode
    else:
      node=doc.createElement('attribute')
      parentNode.appendChild(node)

    exportAttrDict=self.xmlAttributesDict(select=1)
    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)

    return doc

  def xmlAttributesDict(self, select=0):
    "-"
    if not select:
      return self.fullPropertySet()
    else:
      if not self.isDerived():
        return self.nonDerivedPropertySet()
      elif self._isFlattened:
        return self.flattenedPropertySet()
    raise NotImplementedError, 'All cases are not properly covered!'

  ##
  # PropertySets
  ##
  def fullPropertySet(self):
    setType=self.setType
    return {
      'name'                      : ('string',
                                     lambda self=None,p=None: None,
                                     self.name),
      'columnName'                : ('string',
                                     self.setColumnName,
                                     self.columnName),
      'comment'                   : ( 'string',
                                      self.setComment,
                                      self.comment),
      'displayLabel'              : ('string',
                                     self.setDisplayLabel,
                                     self.displayLabel),
      'externalType'              : ('string',
                                     self.setExternalType,
                                     self.externalType),
      'isClassProperty'           : ('bool',
                                     self.setIsClassProperty,
                                     self.isClassProperty),
      'isRequired'                : ('bool',
                                     self.setIsRequired,
                                     self.isRequired),
      # defaultValue must be loaded AFTER type is set, or we will get the
      # wrong value
      'defaultValue'              : ('string',
                                     self.setDefaultValue,
                                     self.defaultValue),
      'definition'                : ('string',
                                     self.setDefinition,
                                     self.definition),
      'precision'                 : ('string',
                                     self.setPrecision,
                                     self.precision),
      'scale'                     : ('string',
                                     self.setScale,
                                     self.scale),
      'type'                      : ('string',
                                     lambda aType, setType=setType:\
                                     setType(aType=aType,
                                             check=0),
                                     self.type),
      'isFlattened'               : ('bool',
                                     self.setIsFlattened,
                                     self.isFlattened),
      'width'                     : ('string',
                                     self.setWidth,
                                     self.width),
      }

  def nonDerivedPropertySet(self):
    # in sense of isDerived(), NOT of self._isDerived  
    subList=('name',
             'columnName', 'precision', 'scale', 'width', 'externalType', #DB
             'comment', 'displayLabel', 'isClassProperty', 'isRequired',
             'defaultValue', 'type', )
    dict=self.fullPropertySet()
    deleteKeys=filter(lambda k, list=subList: k not in list, dict.keys())
    for key in deleteKeys: del dict[key]
    return dict

  def flattenedPropertySet(self):
    subList=('name', 'displayLabel', 'isClassProperty', 'definition',
             'isFlattened', 'comment')
    dict=self.fullPropertySet()
    deleteKeys=filter(lambda k, list=subList: k not in list, dict.keys())
    for key in deleteKeys: del dict[key]
    return dict


  ##
  ##
  def validateAttribute(self):
    """Validates the attributes against general model consistency:
    - attribute cannot be both public and catalogable
    - defaultValue should have the correct type
    - no columnName() for flattened attributes
    - etc. TBD
    """
    raise NotImplementedError

  ##
  ## KeyValueCoding error handling
  ##
  def handleAssignementForUnboundKey(self, value, key):
    if key=='doc': self.setComment(value)
    else:
      raise AttributeError, key
  handleTakeStoredValueForUnboundKey=handleAssignementForUnboundKey
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.