mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-18 22:52:01 -04:00
466 lines
16 KiB
Python
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
|
|
|