Commit ce53164e authored by Eli Collins's avatar Eli Collins

cloning bps to passlib trunk

parents
======================
The BPS Python Library
======================
* For installation instructions, see "docs/install.rst"
* For license & copyright information, see "docs/copyright.rst"
<?xml version="1.0" encoding="UTF-8"?>
<!-- Komodo Project File - DO NOT EDIT -->
<project id="f57a343a-bef4-4b7e-9d57-2ce8862e06e0" kpf_version="4" name="bps.kpf">
<preference-set idref="f57a343a-bef4-4b7e-9d57-2ce8862e06e0">
<string id="import_exclude_matches">*.*~;*.bak;*.tmp;CVS;.#*;*.pyo;*.pyc;.svn;*%*;tmp*.html;.DS_Store;*eric4project;*.e4p;*.e3p;*.egg-info;build;dist;_build</string>
<string id="import_include_matches"></string>
<boolean id="import_live">1</boolean>
<boolean id="import_recursive">1</boolean>
<string id="import_type">useFolders</string>
<string id="mappedPaths"></string>
<string relative="path" id="pythonExtraPaths"></string>
<string id="rubyExtraPaths"></string>
</preference-set>
</project>
"""BPS (version 4) -- Big Package of Stuff -- (c) Assurance Technologies LLC 2003-2009
Requirements:
BPS requires at least Python 2.5,
but tries to be compatible with Python 2.6.
Python 3.0 compatibility has not yet been explored.
Some parts of BPS (namely :mod:`bps.host.windows`) relies
on the ``win32all`` library under windows.
Outside of that, there should be no external dependancies.
To import:
pxhelpers.threads
apf.exception_hook
"""
#----- BEGIN VERSION STAMP -----
__version__ = "4.8.1"
#----- END VERSION STAMP -----
#=========================================================
#imports
#=========================================================
#import first for monkeypatching purposes
import bps.logs
#core
from functools import partial
from warnings import warn
#pkg
from bps.fs import filepath
from bps.logs import log
from bps.meta import abstractmethod
from bps.types import BaseClass, Undef
#local
__all__ = [
#classes & class constructors
"BaseClass", "filepath",
#functions
"partial",
#decorators
"abstractmethod",
#constants
"Undef",
#exceptions
#logging
"log",
"warn",
]
#=========================================================
#EOF
#=========================================================
"""bps.basic -- tools for manipulating basic python datatypes"""
#=========================================================
#imports
#=========================================================
#core
from itertools import islice
from sys import version_info as pyver
#pkg
from bps.error.types import ParamError
#local
__all__ = [
#dict utilities
'zip_dict',
'unzip_dict',
'pop_from_dict',
## 'extract_from_dict',
'set_dict_defaults',
#sequence utilities
'intersects', 'sameset',
'unique',
'iter_unique',
'is_unique',
'enum_slice',
#functional
##'partial' - used to be provided until 2.5 added their implementation
## 'revpartial',
]
#=========================================================
#dictionary helpers
#=========================================================
def invert_dict(source, dups="error"):
"""invert dictionary.
Given a dict mapping key -> value,
this returns a new dictionary mapping value -> key.
:arg source: the source dictionary to invert
:param dups:
Sets the policy when two keys map to the same value.
* By default this is ``"error"``, which raises a ValueError
* Set to "ignore", one key will be chosen (the last one returned by iteritems).
:raises ValueError: if the source dictionary maps two keys to the same value
Usage Example::
>>> from bps.basic import invert_dict
>>> invert_dict({1:2, 3:4, 5:6})
{ 2:1, 4:3, 6:5 }
"""
if dups == "error":
out = {}
for k, v in source.iteritems():
if v in out:
raise ValueError, "dictionary not invertible: value=%r key1=%r key2=%r" % (v, out[v], k)
out[v] = k
return out
else:
assert dups == "ignore"
return dict( (v, k) for k, v in source.iteritems())
def update_dict_defaults(target, *args, **kwds):
"""cross between dict.update and dict.setdefault, which updates only the keys which aren't already present.
Usage Examples::
>>> from bps.basic import update_dict_defaults
>>> a = dict(x=1,y=2)
>>> update_dict_defaults(a, x=100, z=3)
>>> a
{ 'x': 1, 'y': 2, 'z': 3 }
>>> update_dict_defaults(a, { 'z': 100, 's': 20 })
>>> a
{ 'x': 1, 'y': 2, 'z': 3, 's': 20 }
"""
if args:
if len(args) > 1:
raise TypeError, "at most one positional argument is allowed"
source = args[0]
for k,v in source.iteritems():
if k not in target:
target[k] = v
if kwds:
for k,v in kwds.iteritems():
if k not in target:
target[k] = v
set_dict_defaults = update_dict_defaults #XXX: deprecate this name?
#---------------------------------------
# (keys, values) <-> dict
#---------------------------------------
def zip_dict(keys, values):
"converts list of keys, list of values to dict"
return dict(zip(keys, values))
def unzip_dict(data):
"converts dict to list of keys and list of values"
if data is None: #special case
return [],[]
else:
keys = []
values = []
for k,v in data.iteritems():
keys.append(k)
values.append(v)
return keys,values
#---------------------------------------
#extract one dict from another
#---------------------------------------
def pop_from_dict(source, keys, target=None):
"""for all keys in <keys>, extract any from <source> dict,
and return them in new dictionary (or place in <target> dict)
"""
if target is None:
target = {}
for k in keys:
if k in source:
target[k] = source.pop(k)
return target
##def filter_dict(func, source, target=None):
## """filter dictionary. ``func(k,v) -> bool``"""
## if target is None:
## target = {}
## for k, v in source.iteritems():
## if func(k, v):
## target[k] = v
## return target
def prefix_from_dict(source, prefix, target=None):
"""For all keys in *source* dict with the specified *prefix*,
strip the prefix, and copy the k/v pair to the *target* dict.
If target is specified, it will be used as the dictionary
that any matching k/v pairs are inserted into.
Otherwise, a new dictionary will be created as the target.
:Returns:
This always returns the target dict,
whether passed-in or created.
Usage Example::
>>> from bps.basic import strip_from_dict
>>> prefix_from_dict({"abc":1, "def": 2, "abxyz": 3}, "ab")
{ "c": 1, "xyz": 3 }
"""
if target is None:
target = {}
for key in source:
if key.startswith(prefix):
target[key[len(prefix):]] = source[key]
return target
#works, but near useless probably
##def extract_from_dict(source, keys, target=None):
## """extract specified keys from dictionary.
##
## returns a new dictionary, unless target is specified.
## if target is a dict, keys are placed in target.
## if target is ``list`` or ``tuple``, the corresponding class
## will be returned.
## """
## if target is list:
## return [ source[k] for k in keys ]
## elif target is tuple:
## return tuple(source[k] for k in keys)
## elif target is None:
## return dict( (k,source[k]) for k in keys)
## else:
## for k in keys:
## target[k] = source[k]
## return target
#=========================================================
#set helpers
#=========================================================
#xxx: would enable this, but could use more intelligent return values
##def intersection(list1, list2):
## "returns list containing all elements shared by two sequences / iterables"
## return list(set(list1).intersection(list2))
##
#TODO: write unittests
if pyver < (2,6):
def intersects(list1, list2):
"returns True if two sequences / iterables have any elements in common"
#TODO: would like a more efficient way of doing this for large sets
return bool(set(list1).intersection(list2))
else:
def intersects(list1, list2):
"returns True if two sequences / iterables have any elements in common"
return not set(list1).isdisjoint(list2)
def sameset(list1, list2):
"returns True if the two sequences contain exactly the same elements, else False"
if not isinstance(list1, set):
list1 = set(list1)
if not isinstance(list2, set):
list2 = set(list2)
return list1 == list2
#=========================================================
#iteration & functional helpers
#=========================================================
#this works, but not used
##def revpartial(func, *args, **kwds):
## "like partial(), but args & kwds are appended to end"
## #TODO: given this 'func', 'args' and 'kwds' attrs like functools.partial
## return lambda *p, **n:\
## func(*p + args, **dict(kw.items() + n.items()))
def iter_unique(seq):
"""iterate through sequence, yielding only unique values.
values will be returned in order of first occurrence.
Example Usage::
>>> from bps.basic import iter_unique
>>> for x in iter_unique([1,3,2,1,2,3]):
>>> print x
1
3
2
"""
seen = set()
cont = seen.__contains__
add = seen.add
for val in seq:
if not cont(val):
add(val)
yield val
def unique(seq):
"""return list containing only unique elements in sequence,
in order of first occurrence.
Example Usage::
>>> from bps.basic import unique
>>> unique([1,3,2,1,2,3])
[1,3,2]
"""
return list(iter_unique(seq))
def is_unique(seq):
"check if sequence/iterator contains only unique values; returns False after first duplicate is found"
if isinstance(seq, (set,frozenset)):
return True
#XXX: is there a faster way?
seen = set()
cont = seen.__contains__
add = seen.add
for elem in seq:
if cont(elem):
return False
add(elem)
return True
def enum_slice(seq, *args):
"""enumslice(iterable, [start,] stop [, step])
Combination of enumerate & islice which reports original index values.
Equivalent to ``islice(enumerate(seq), start, stop, step)``,
but without creation of intermediate sequence.
Usage::
>>> from bps.basic import enum_slice
>>> for idx, value in enum_slice("abcdef", 2, 5):
>>> print idx, value
2 c
3 d
4 e
"""
#NOTE: we calc start/stop/step ourselves,
#so we can handle negative indices (since islice doesn't).
#if islice did, this would be a much simpler function.
#handle simple case
ac = len(args)
if ac == 0:
for idx, value in enumerate(seq):
yield idx, value
return
#figure out params
elif ac == 1:
start = 0
stop, = args
step = 1
elif ac == 2:
start, stop = args
step = 1
elif ac == 3:
start, stop, step = args
else:
raise ParamError, "too many arguments"
#normalize inputs
if start is None:
start = 0
elif start < 0:
#FIXME: error if passed an iterator (works for lists/strings)
start += len(seq)
if stop is None:
pass
elif stop < 0:
#FIXME: error if passed an iterator (works for lists/strings)
stop += len(seq)
if step is None:
step = 1
#run
if step < 0:
#islice doesn't support negative ints.
#FIXME: error if passed an iterator (works for lists/strings)
offset = start
if stop is None:
stop = -1
while offset > stop:
yield offset, seq[offset]
offset += step
else:
offset = start
for value in islice(seq, start, stop, step):
yield offset, value
offset += step
#=========================================================
#EOF
#=========================================================
"""bps.cache -- caching tools"""
#===================================================
#imports
#===================================================
#core
import inspect
from functools import update_wrapper
import time
from warnings import warn
#needed imports
#legacy imports
from bps.undef import Undef
from bps.meta import find_attribute, decorate_per_instance, instancemethod
#XXX: bps3.misc.finalmethod?
#TODO: bps3.misc.AbstractMethodError - here or in bps.exc?
__all__ = [
#cached decorators
'cached_function',
'cached_method',
#stateful decorators
'stateful_function',
'stateful_method',
'is_stateful',
]
#=========================================================
#function caching decorator
#=========================================================
def cached_function(key=None, args=None, lifetime=None, tick=time.time):
"""decorator that caches a function's output.
This decorator creates an dictionary which caches the return values
of the wrapped function, so that successive calls hit the cache
rather than calling the function itself. This decorator
supports numerous features, including time-limited caching,
and customization how the cache key is calculated.
:param key:
This should be a function which takes the wrapper func's inputs,
and maps them to a hashable value to identify inputs for caching purposes.
If ``key(*args,**kwds)`` returns the ``NotImplemented`` singleton, caching will be bypassed.
:param args:
Alternately, instead of specifying a `key`, this option can be used
to specify the number of positional arguments expected, which will be formed into a tuple,
and used as the cache key. This option is mutually exlusive with *key*.
:param lifetime:
Amount of time (as measured by `tick` function)
before cached values should expire.
If lifetime is ``None`` (the default), cached values will never expire,
unless ``func.clear()`` is explicitly called by your application.
:param tick:
Function returning arbitrary objects for timestamping,
used by `lifetime`. By default, this uses ``time.time()``
The resulting decorated function object will have a some extra attributes:
``func.key(*args,**kwds)``
Calling this with a set of the function's arguments
will return the key used to cache the result for those parameters.
``func.clear(keys=None)``
Calling this will clear the internal cache of function results.
If *keys* is specified, only those cache keys will be cleared.
``func.set(key,value)``
Allows writing to the function cache directly.
``func.cache``
This is an exposed reference to the actual cache dictionary.
Please use this only if you *really* have to.
.. caution::
If your code does access the dictionary, be aware that the
``lifetime`` option will change the organization of the dict from
``key -> result`` to ``key -> (mtime,result)``.
A simple usage example::
>>> import time
>>> from bps.cache import cached_function
>>> #an example which has an expiring cache
>>> @cached_function(args=1, lifetime=2)
>>> def myfunc(value):
>>> print "myfunc called:", value
>>> return value*2
>>> #the first call won't be cached
>>> print "result:", myfunc(2)
myfunc called: 2
result: 4
>>> #but the next one will
>>> print "result:", myfunc(2)
result: 4
>>> #if we wait a bit and try again, the cache will expire
>>> time.sleep(2)
>>> print "result:", myfunc(2)
myfunc called: 2
result: 4
>>> #or we can manually flush the entire cache
>>> myfunc.clear()
>>> print "result:", myfunc(2)
myfunc called: 2
result: 4
.. seealso::
:func:`cached_method`
:func:`stateful_function`
"""
if key is None:
if args is None:
warn("one of `key` or `args` will be required for cached_function() in the future, the bare version is deprecated", DeprecationWarning, stacklevel=3)
def key():
return None
elif args == 0:
def key():
return None
elif args == 1:
def key(value):
return value
else:
def key(*a):
if len(a) != args:
raise ValueError, "expected exactly %s arguments: %r" % (args, a)
return a
else:
assert args is None, "args and key() function are mutually exlusive"
assert callable(key), "key() function must be callable"
def builder(func):
#validate the function
if hasattr(func, "on_changed"):
warn("cached_function() is wrapping a function that was wrapped with stateful_function()... the opposite wrapping order is recommended for correct behavior", stacklevel=1)
#NOTE: why the warning?
# because stateful's changed() can call clear_cache(),
# but cache_func will hide any state changes which occur.
# so you want to decorator your function the other way around.
#init locals
cache = {}
#create wrapper...
if lifetime is None:
#...with no expiration
def wrapper(*args, **kwds):
value = key(*args, **kwds)
if value is NotImplemented:
return func(*args, **kwds)
elif value in cache:
return cache[value]
result = cache[value] = func(*args, **kwds)
return result
wrapper.set = cache.__setitem__ #for easy overriding of cache
else: