# -*- 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.
#-----------------------------------------------------------------------------
"""
GlobalID
A GlobalID is used to Uniquely identify an object within the framework ;
more precisely, speaking of an EditingContext 'ec', any object 'o'
registered within 'ec' corresponds to one and only one GlobalID --see
EditingContext.globalIDForObject() and EditingContext.objectForGlobalID().
Hence, it is completely equivalent to speak in terms of objects or in terms
of GlobalIDs when talking relatively to a given EditingContext.
There are two type of GlobalIDs:
- TemporaryGlobalID are assigned to newly created objects (see
EditingContext.insertObject()), which are not yet saved within a
database
- KeyGlobalID identifies an object that does exist in a database store (it
can be either a fully-initialized/already fetched object, or a ``fault''
object (a ``ghost'' in ZODB.Persistence jargon) that still needs to be
fetched.
Both types are immutable types.
CVS information
$Id: GlobalID.py 932 2004-07-20 06:21:57Z sbigaret $
"""
__version__='$Revision: 932 $'[11:-2]
from interfaces.GlobalID import IKeyGlobalID,ITemporaryGlobalID
import time, socket
GlobalIDChangedNotification='GlobalIdChangedNotification'
def globalIDWithEntityName(entityName, informations=None):
"""
Returns a new GlobalID based on the supplied informations.
Parameter 'informations' can be either:
- None, in which case a TemporaryGlobalId is returned, or
- an object implementing XXX,
- a dictionary '{PK_name: value}', containing all entities' PKs
In the two latter cases a KeyGlobalID is returned.
"""
if informations is None:
return TemporaryGlobalID()
else:
return KeyGlobalID(entityName, informations)
## GlobalID
class KeyGlobalID:
"""
KeyGlobalID
Important note: the internal state of KeyGlobalID objects is considered
read-only, and at no time the framework expects their internal values to
change. Trying to change them will certainly trigger wrong and possibly
bad behaviour, including (not exhaustively): loss of data, apparition of
dangling references, confusion in the objects' changes that are made
persistent, etc.
"""
__implements__=(IKeyGlobalID, )
__is_temporary__=0
def __init__(self, entityName, keyValues):
"""
Initializes a new GlobalID with the following parameters:
Parameters:
entityName -- the name of the corresponding object's entityName
(remember a GlobalID acts as a label that sticks to a particular
object),
keyValues -- a dictionary ; the entity's primary key(s)'s names are its
keys, map to their respective values.
"""
self.__dict__['_entityName']=entityName
self.__dict__['_keyValues']=keyValues
#self.__hash=hash('%s/%s'%(self._entityName, str(self._keyValues)))
self._buildHashCode()
def entityName(self):
return self._entityName
def isTemporary(self):
"""
Returns 'false'.
"""
return 0
def keyCount(self):
"""
Returns the number of entries supplied in 'keyValues' when the GlobalID
was initialized. For example, if an object is stored in the database in a
table which has a simple (as opposed to a compound-) primary key, its
GlobalID will return '1' (one).
"""
return len(self._keyValues)
def keyValues(self):
"""
Return the object's key values, as specified when the object was
initialized.
See also: __init__()
"""
return self._keyValues.copy()
def __eq__(self, aGlobalId):
"""
Compares self with 'aGlobalID'
A GlobalID is equal to another one iff:
- they both are GlobalIDs (either KeyGlobalID or TemporaryGlobalID)
- they both are KeyGlobalID, with the same entityName() and keyValues()
- they both are TemporaryGlobalID, with the same internal state (as
exposed by __str__)
"""
if not hasattr(aGlobalId, '__is_temporary__'): return 0
if aGlobalId.__is_temporary__: return 0
if self._entityName!=aGlobalId._entityName or \
self._keyValues !=aGlobalId._keyValues : return 0
return 1
def __cmp__(self, aGlobalId):
"""
"""
hs=hash(self)
hp=hash(aGlobalId)
if hs<hp: return -1
elif hs>hp: return 1
else: return 0
def __str__(self):
"""
Returns a string representation of the GlobalID. This string representation
is unique among GlobalIDs.
"""
return '<KeyGlobalID: %s %s at %s>'%(self._entityName,
str(self._keyValues),
hex(id(self)))
def __hash__(self):
return self.__hash
def _buildHashCode(self):
"Computes the hash code for the object."
# According to python doc., the two lists keys() and values() directly
# corresponds if they are called with no intervening modification to
# the dictionary. So we can safely add these two lists and derive the hash
# code from that (which is obviously order-dependent)
l=tuple(self._keyValues.keys()+self._keyValues.values())
self.__dict__['_KeyGlobalID__hash']=hash(self._entityName)^hash(l)
def __setattr__(self, name, value):
"Raises TypeError since this is an immutable object"
raise TypeError, 'object has read-only attributes'
## Index for TemporaryGlobalID
from threading import RLock
temporaryGlobalIDIndex=0
TemporaryGlobalIDIndex_lock=RLock()
lock=TemporaryGlobalIDIndex_lock.acquire
unlock=TemporaryGlobalIDIndex_lock.release
def nextTemporaryIndex():
"This is part of the initialization process of TemporaryGlobalID"
lock()
try:
global temporaryGlobalIDIndex
temporaryGlobalIDIndex += 1
return temporaryGlobalIDIndex
finally:
unlock()
## TemporaryGlobalID
class TemporaryGlobalID:
"""
TemporaryGlobalID
Important note: the internal state of KeyGlobalID objects is considered
read-only, and at no time the framework expects their internal values to
change. Trying to change them will certainly trigger wrong and possibly
bad behaviour, including (not exhaustively): loss of data, apparition of
dangling references, confusion in the objects' changes that are made
persistent, etc.
"""
__implements__=(ITemporaryGlobalID,)
__is_temporary__=1
# We now cache the fqdn (_host) and ipaddrlist: when an host has problems
# resolving dns entries this can hang a few seconds, and
# EditingContext.insert() then appears to be really slow (bug #804756)
_host=socket.getfqdn()
_ipaddrlist=socket.gethostbyname_ex(_host)[2]
def __init__(self, *args, **kw):
"Initializes a new TemporaryGlobalID. Arguments are currently ignored."
self.__dict__['_index']=nextTemporaryIndex()
self.__dict__['_ip']=self.nonLocalIPIfAvailable(self._ipaddrlist)
self.__dict__['_time']=time.strftime("%Y%m%d%H%M%S")
self.__dict__['_TemporaryGlobalID__hash']=hash("%s/%s/%s/%s"%(self._index,self._time, self._host,self._ip))
def isTemporary(self):
"Returns true"
return 1
def __eq__(self, aGlobalID):
"""
Compares self with 'aGlobalID'
A GlobalID is equal to another one iff:
- they both are GlobalIDs (either KeyGlobalID or TemporaryGlobalID)
- they both are KeyGlobalID, with the same entityName() and keyValues()
- they both are TemporaryGlobalID, with the same internal state (as
exposed by __str__)
"""
if not hasattr(aGlobalID, '__is_temporary__'): return 0
if not aGlobalID.__is_temporary__: return 0
if self._host != aGlobalID._host or \
self._index != aGlobalID._index or \
self._ip != aGlobalID._ip or \
self._time != aGlobalID._time:
return 0
return 1
def __str__(self):
"""
Returns a string representation of the GlobalID. This string representation
is unique among GlobalIDs.
"""
return '<TemporaryGlobalID %s / %s / %s [%s] at %s>'%(self._index,self._time, self._host,self._ip, hex(id(self)))
def __hash__(self):
return self.__hash
def nonLocalIPIfAvailable(self, ipaddrList):
if not ipaddrList:
return '127.0.0.1'
for ip in ipaddrList:
if ip!='127.0.0.1':
return ip
return '127.0.0.1'
def __setattr__(self, name, value):
"Raises TypeError since this is an immutable object"
raise TypeError, 'object has read-only attributes'
|