Files
sqlalchemy/lib/sqlalchemy/util.py
T

466 lines
16 KiB
Python

# util.py
# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import thread, threading, weakref, UserList, time, string, inspect, sys, sets
from exceptions import *
import __builtin__
try:
Set = set
except:
Set = sets.Set
def to_list(x):
if x is None:
return None
if not isinstance(x, list) and not isinstance(x, tuple):
return [x]
else:
return x
def to_set(x):
if x is None:
return Set()
if not isinstance(x, Set):
return Set(to_list(x))
else:
return x
def reversed(seq):
try:
return __builtin__.reversed(seq)
except:
def rev():
i = len(seq) -1
while i >= 0:
yield seq[i]
i -= 1
raise StopIteration()
return rev()
class ArgSingleton(type):
instances = {}
def __call__(self, *args):
hashkey = (self, args)
try:
return ArgSingleton.instances[hashkey]
except KeyError:
instance = type.__call__(self, *args)
ArgSingleton.instances[hashkey] = instance
return instance
class SimpleProperty(object):
"""a "default" property accessor."""
def __init__(self, key):
self.key = key
def __set__(self, obj, value):
setattr(obj, self.key, value)
def __delete__(self, obj):
delattr(obj, self.key)
def __get__(self, obj, owner):
if obj is None:
return self
else:
return getattr(obj, self.key)
class Logger(object):
"""defines various forms of logging"""
def __init__(self, logger=None, usethreads=False, usetimestamp=True, origin=None):
self.logger = logger or sys.stdout
self.usethreads = usethreads
self.usetimestamp = usetimestamp
self.origin = origin
def write(self, msg):
if self.usetimestamp:
t = time.time()
ms = (t - long(t)) * 1000
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t))
timestamp = "[%s,%03d]" % (timestamp, ms)
else:
timestamp = None
if self.origin:
origin = "[%s]" % self.origin
origin = "%-8s" % origin
else:
origin = None
if self.usethreads:
threadname = threading.currentThread().getName()
threadname = "[" + threadname + ' '*(8-len(threadname)) + "]"
else:
threadname = None
self.logger.write(string.join([s for s in (timestamp, threadname, origin) if s is not None]) + ": " + msg + "\n")
class OrderedProperties(object):
"""
An object that maintains the order in which attributes are set upon it.
also provides an iterator and a very basic getitem/setitem interface to those attributes.
(Not really a dict, since it iterates over values, not keys. Not really
a list, either, since each value must have a key associated; hence there is
no append or extend.)
"""
def __init__(self):
self.__dict__['_OrderedProperties__data'] = OrderedDict()
def __len__(self):
return len(self.__data)
def __iter__(self):
return self.__data.itervalues()
def __setitem__(self, key, object):
self.__data[key] = object
def __getitem__(self, key):
return self.__data[key]
def __delitem__(self, key):
del self.__data[key]
def __setattr__(self, key, object):
self.__data[key] = object
def __getattr__(self, key):
try:
return self.__data[key]
except KeyError:
raise AttributeError(key)
def keys(self):
return self.__data.keys()
def has_key(self, key):
return self.__data.has_key(key)
def clear(self):
self.__data.clear()
class OrderedDict(dict):
"""A Dictionary that keeps its own internal ordering"""
def __init__(self, values = None):
self._list = []
if values is not None:
for val in values:
self.update(val)
def keys(self):
return list(self._list)
def clear(self):
self._list = []
dict.clear(self)
def update(self, dict):
for key in dict.keys():
self.__setitem__(key, dict[key])
def setdefault(self, key, value):
if not self.has_key(key):
self.__setitem__(key, value)
return value
else:
return self.__getitem__(key)
def values(self):
return map(lambda key: self[key], self._list)
def __iter__(self):
return iter(self._list)
def itervalues(self):
return iter([self[key] for key in self._list])
def iterkeys(self):
return self.__iter__()
def iteritems(self):
return iter([(key, self[key]) for key in self.keys()])
def __delitem__(self, key):
try:
del self._list[self._list.index(key)]
except ValueError:
raise KeyError(key)
dict.__delitem__(self, key)
def __setitem__(self, key, object):
if not self.has_key(key):
self._list.append(key)
dict.__setitem__(self, key, object)
def __getitem__(self, key):
return dict.__getitem__(self, key)
class ThreadLocal(object):
"""an object in which attribute access occurs only within the context of the current thread"""
def __init__(self):
self.__dict__['_tdict'] = {}
def __delattr__(self, key):
try:
del self._tdict["%d_%s" % (thread.get_ident(), key)]
except KeyError:
raise AttributeError(key)
def __getattr__(self, key):
try:
return self._tdict["%d_%s" % (thread.get_ident(), key)]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
self._tdict["%d_%s" % (thread.get_ident(), key)] = value
class DictDecorator(dict):
"""a Dictionary that delegates items not found to a second wrapped dictionary."""
def __init__(self, decorate):
self.decorate = decorate
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
return self.decorate[key]
def __repr__(self):
return dict.__repr__(self) + repr(self.decorate)
class OrderedSet(sets.Set):
def __init__(self, iterable=None):
"""Construct a set from an optional iterable."""
self._data = OrderedDict()
if iterable is not None:
self._update(iterable)
class HistoryArraySet(UserList.UserList):
"""extends a UserList to provide unique-set functionality as well as history-aware
functionality, including information about what list elements were modified
and commit/rollback capability. When a HistoryArraySet is created with or
without initial data, it is in a "committed" state. as soon as changes are made
to the list via the normal list-based access, it tracks "added" and "deleted" items,
which remain until the history is committed or rolled back."""
def __init__(self, data = None, readonly=False):
# stores the array's items as keys, and a value of True, False or None indicating
# added, deleted, or unchanged for that item
self.records = OrderedDict()
if data is not None:
self.data = data
for item in data:
# add items without triggering any change events
# *ASSUME* the list is unique already. might want to change this.
self.records[item] = None
else:
self.data = []
self.readonly=readonly
def __iter__(self):
return iter(self.data)
def __getattr__(self, attr):
"""proxies unknown HistoryArraySet methods and attributes to the underlying
data array. this allows custom list classes to be used."""
return getattr(self.data, attr)
def set_data(self, data):
"""sets the data for this HistoryArraySet to be that of the given data.
duplicates in the incoming list will be removed."""
# first mark everything current as "deleted"
for item in self.data:
self.records[item] = False
self.do_value_deleted(item)
# switch array
self.data = data
# TODO: fix this up, remove items from array while iterating
for i in range(0, len(self.data)):
if not self.__setrecord(self.data[i], False):
del self.data[i]
i -= 1
for item in self.data:
self.do_value_appended(item)
def history_contains(self, obj):
"""returns true if the given object exists within the history
for this HistoryArrayList."""
return self.records.has_key(obj)
def __hash__(self):
return id(self)
def do_value_appended(self, value):
pass
def do_value_deleted(self, value):
pass
def __setrecord(self, item, dochanged=True):
try:
val = self.records[item]
if val is True or val is None:
return False
else:
self.records[item] = None
if dochanged:
self.do_value_appended(item)
return True
except KeyError:
self.records[item] = True
if dochanged:
self.do_value_appended(item)
return True
def __delrecord(self, item, dochanged=True):
try:
val = self.records[item]
if val is None:
self.records[item] = False
if dochanged:
self.do_value_deleted(item)
return True
elif val is True:
del self.records[item]
if dochanged:
self.do_value_deleted(item)
return True
return False
except KeyError:
return False
def commit(self):
"""commits the added values in this list to be the new "unchanged" values.
values that have been marked as deleted are removed from the history."""
for key in self.records.keys():
value = self.records[key]
if value is False:
del self.records[key]
else:
self.records[key] = None
def rollback(self):
"""rolls back changes to this list to the last "committed" state."""
# TODO: speed this up
list = []
for key, status in self.records.iteritems():
if status is False or status is None:
list.append(key)
self._clear_data()
self.records = {}
for l in list:
self.append_nohistory(l)
def clear(self):
"""clears the list and removes all history."""
self._clear_data()
self.records = {}
def _clear_data(self):
if isinstance(self.data, dict):
self.data.clear()
else:
self.data[:] = []
def added_items(self):
"""returns a list of items that have been added since the last "committed" state."""
return [key for key in self.data if self.records[key] is True]
def deleted_items(self):
"""returns a list of items that have been deleted since the last "committed" state."""
return [key for key, value in self.records.iteritems() if value is False]
def unchanged_items(self):
"""returns a list of items that have not been changed since the last "committed" state."""
return [key for key in self.data if self.records[key] is None]
def append_nohistory(self, item):
"""appends an item to the list without affecting the "history"."""
if not self.records.has_key(item):
self.records[item] = None
self.data.append(item)
def remove_nohistory(self, item):
"""removes an item from the list without affecting the "history"."""
if self.records.has_key(item):
del self.records[item]
self.data.remove(item)
def has_item(self, item):
return self.records.has_key(item) and self.records[item] is not False
def __setitem__(self, i, item):
if self.__setrecord(item):
self.data[i] = item
def __delitem__(self, i):
self.__delrecord(self.data[i])
del self.data[i]
def __setslice__(self, i, j, other):
print "HAS SETSLICE"
i = max(i, 0); j = max(j, 0)
if isinstance(other, UserList.UserList):
l = other.data
elif isinstance(other, type(self.data)):
l = other
else:
l = list(other)
[self.__delrecord(x) for x in self.data[i:]]
g = [a for a in l if self.__setrecord(a)]
self.data[i:] = g
def __delslice__(self, i, j):
i = max(i, 0); j = max(j, 0)
for a in self.data[i:j]:
self.__delrecord(a)
del self.data[i:j]
def append(self, item):
if self.__setrecord(item):
self.data.append(item)
def insert(self, i, item):
if self.__setrecord(item):
self.data.insert(i, item)
def pop(self, i=-1):
item = self.data[i]
if self.__delrecord(item):
return self.data.pop(i)
def remove(self, item):
if self.__delrecord(item):
self.data.remove(item)
def extend(self, item_list):
for item in item_list:
self.append(item)
def __add__(self, other):
raise NotImplementedError()
def __radd__(self, other):
raise NotImplementedError()
def __iadd__(self, other):
raise NotImplementedError()
class ScopedRegistry(object):
"""a Registry that can store one or multiple instances of a single class
on a per-thread scoped basis, or on a customized scope
createfunc - a callable that returns a new object to be placed in the registry
scopefunc - a callable that will return a key to store/retrieve an object,
defaults to thread.get_ident for thread-local objects. use a value like
lambda: True for application scope.
"""
def __init__(self, createfunc, scopefunc=None):
self.createfunc = createfunc
if scopefunc is None:
self.scopefunc = thread.get_ident
else:
self.scopefunc = scopefunc
self.registry = {}
def __call__(self):
key = self._get_key()
try:
return self.registry[key]
except KeyError:
return self.registry.setdefault(key, self.createfunc())
def set(self, obj):
self.registry[self._get_key()] = obj
def clear(self):
try:
del self.registry[self._get_key()]
except KeyError:
pass
def _get_key(self):
return self.scopefunc()
def constructor_args(instance, **kwargs):
"""given an object instance and keyword arguments, inspects the
argument signature of the instance's __init__ method and returns
a tuple of list and keyword arguments, suitable for creating a new
instance of the class. The returned arguments are drawn from the
given keyword dictionary, or if not found are drawn from the
corresponding attributes of the original instance."""
classobj = instance.__class__
argspec = inspect.getargspec(classobj.__init__.im_func)
argnames = argspec[0] or []
defaultvalues = argspec[3] or []
(requiredargs, namedargs) = (
argnames[0:len(argnames) - len(defaultvalues)],
argnames[len(argnames) - len(defaultvalues):]
)
newparams = {}
for arg in requiredargs:
if arg == 'self':
continue
elif kwargs.has_key(arg):
newparams[arg] = kwargs[arg]
else:
newparams[arg] = getattr(instance, arg)
for arg in namedargs:
if kwargs.has_key(arg):
newparams[arg] = kwargs[arg]
else:
if hasattr(instance, arg):
newparams[arg] = getattr(instance, arg)
else:
raise AssertionError("instance has no attribute '%s'" % arg)
return newparams