# This library is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see
# <http://www.gnu.org/licenses/>.
"""
Provides a way to wrap multiple interrelated functions, while
preserving uniqueness.
:author: Christopher O'Brien <obriencj@gmail.com>
:license: LGPL v.3
"""
from abc import ABCMeta
from brine import BrinedObject, BrinedFunction, BrinedMethod, BrinedPartial
from brine import brine, unbrine
from brine._cellwork import cell_get_value, cell_set_value, cell_from_value
from functools import partial
from itertools import imap
from types import BuiltinFunctionType, BuiltinMethodType
from types import FunctionType, MethodType
__all__ = [ "Barrel", "BarreledObject",
"BarreledFunction", "BarreledMethod", "BarreledPartial" ]
[docs]class BarreledObject(BrinedObject):
"""
Abstract base class for barrel wrappers. Defines the interface
required for a barrel to brine a value.
- When created, should accept a parent `barrel` and a `value` to wrap
- The `get` method should return a new copy of the wrapped `value`
- Must define the `__getstate__` and `__setstate__` methods for
`pickle` support.
"""
__metaclass__ = ABCMeta
def __init__(self, barrel, value):
"""
Parameters
----------
barrel : `Barrel`
barrel containing this wrapper
value : `object`
object to be wrapped for pickling
"""
self._barrel = barrel
super(BarreledObject, self).__init__(value)
def __getstate__(self):
return (self._barrel, ) + super(BarreledObject, self).__getstate__()
def __setstate__(self, state):
self._barrel = state[0]
super(BarreledObject, self).__setstate__(state[1:])
[docs]class BarreledFunction(BarreledObject, BrinedFunction):
"""
A brined function in a barrel. This wrapper is created
automatically around `function` instances in a `Barrel` when it is
pickled.
"""
def _brine_cell(self, cell):
val = cell_get_value(cell)
bval = self.brine_related(val)
return cell_from_value(bval)
def _unbrine_cell(self, with_globals, cell):
val = cell_get_value(cell)
ubval = self.unbrine_related(val)
cell_set_value(cell, ubval)
def _code_unnew(self, code):
uncode = super(BarreledFunction, self)._code_unnew(code)
uncode[5] = tuple(imap(self.brine_related, uncode[5]))
return uncode
def _code_new(self, with_globals, ucode):
ucode[5] = tuple(imap(self.unbrine_related, ucode[5]))
return super(BarreledFunction, self)._code_new(with_globals, ucode)
def _function_unnew(self, function):
self._barrel._putcache(function, self)
ufunc = super(BarreledFunction, self)._function_unnew(function)
if ufunc[4] is not None:
ufunc[4] = tuple(imap(self._brine_cell, ufunc[4]))
return ufunc
def _function_new(self, with_globals, ufunc):
func = super(BarreledFunction, self)._function_new(with_globals, ufunc)
# make sure the barrel only attempts to unbrine this function
# once, so put our entry into the cache before attempting to
# unbrine our cells in-place
self._barrel._putcache(self, func)
# this is the necessary second-pass, which will go through the
# newly generated function and will unbrine any cells. We need
# to do this in a second pass because it's possible that one
# of the cells will want to be the same function that we've
# just unbrined
if ufunc[4] is not None:
ub = partial(self._unbrine_cell, with_globals)
ufunc[4] = tuple(imap(ub, ufunc[4]))
return func
[docs]class BarreledMethod(BarreledObject, BrinedMethod):
"""
A brined bound method in a barrel. This wrapper is created
automatically around `instancemethod` instances in a `Barrel` when
it is pickled.
"""
pass
[docs]class BarreledPartial(BarreledObject, BrinedPartial):
"""
A brined partial in a barrel. This wrapper is created
automatically around `partial` instances in a `Barrel` when it is
pickled.
"""
def __init__(self, barrel, part):
self._barrel = barrel
brine = self.brine_related
self._func = brine(part.func)
self._args = brine(part.args or None)
self._keywords = brine(part.keywords or None)
def get(self, with_globals):
unbrine = self.unbrine_related
func = unbrine(self._func)
args = unbrine(self._args or tuple())
kwds = unbrine(self._keywords or dict())
return partial(func, *args, **kwds)
[docs]class Barrel(object):
"""
Mapping supporting automatic brining of contained values when
pickled. Provides the `dict` interface special methods.
"""
[docs] def __init__(self, *pairs, **values):
"""
Accepts a single optional positional argument, which must be a
`dict` or an iterable of key,value pairs. Also accepts an
arbutrary number of named parameters which will be used to
create further mappings.
"""
self._brined = None
self._unbrined = dict(*pairs, **values)
self._glbls = globals()
self._cache = None
# == dict API ==
def __setitem__(self, key, val):
if self._unbrined is None:
self._unbrine_all()
self._unbrined[key] = val
def __getitem__(self, key):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined[key]
def __delitem__(self, key):
if self._unbrined is not None:
del self._unbrined[key]
def __iter__(self):
if self._unbrined is None:
self._unbrine_all()
return iter(self._unbrined)
def get(self, key, default_value=None):
"""
An unbrined copy of the value assodicated with `key` if `key` is
in this Barrel, else `default_value`.
"""
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.get(key, default_value)
def iteritems(self):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.iteritems()
def items(self):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.items()
def iterkeys(self):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.iterkeys()
def keys(self):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.keys()
def itervalues(self):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.itervalues()
def values(self):
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.values()
def update(self, from_dict):
"""
Update the contents of this Barrel from the keys and values in
`from_dict`
"""
if self._unbrined is None:
self._unbrine_all()
return self._unbrined.update(from_dict)
[docs] def clear(self):
"""
Remove all key and value pairs in this Barrel, and clear the
cache.
"""
self._brined = None
self._unbrined = dict()
# == pickle API ==
def __getstate__(self):
if self._unbrined is None:
return self._brined or dict()
else:
if self._brined is None:
self._brine_all()
return self._brined
def __setstate__(self, data):
self._brined = data
self._unbrined = None
self._glbls = globals()
self._cache = None
# == Barrel API ==
[docs] def use_globals(self, glbls=None):
"""
Provide a different set of globals when rebuilding functions from
their brined wrappers.
"""
self._glbls = globals() if glbls is None else glbls
[docs] def reset(self):
"""
Clears the internal cache. Any future sets or gets from this
Barrel will cause full brining or unbrining rather than
returning an already computed value.
If you retrieved a value from this barrel and want to load a
new copy (possibly with different globals), calling `reset()`
is a way to achieve such.
"""
if self._brined is None:
self._brine_all()
self._unbrined = None
def _brine_all(self):
oldcache = self._cache
self._cache = dict()
self._brined = self._brine(self._unbrined)
self._cache = oldcache
def _unbrine_all(self):
oldcache = self._cache
self._cache = dict()
self._unbrined = self._unbrine(self._brined)
self._cache = oldcache
def _putcache(self, original, brined):
self._cache[id(original)] = brined
def _getcache(self, original):
return self._cache.get(id(original))
def _unbrine(self, value):
assert(self._cache is not None)
if isinstance(value, BrinedObject):
ret = self._getcache(value)
if not ret:
ret = value.get(self._glbls)
self._putcache(value, ret)
value = ret
elif isinstance(value, (tuple, list)):
ret = self._getcache(value)
if ret is None:
vt = type(value)
ret = vt(imap(self._unbrine, iter(value)))
self._putcache(value, ret)
value = ret
elif isinstance(value, dict):
ret = self._getcache(value)
if ret is None:
ret = dict(self._unbrine(value.items()))
self._putcache(value, ret)
value = ret
return value
def _brine(self, value):
assert(self._cache is not None)
if isinstance(value, (BuiltinFunctionType, BuiltinMethodType)):
# don't touch builtins
pass
elif isinstance(value, partial):
ret = self._getcache(value)
if not ret:
ret = BarreledPartial(self, value)
self._putcache(value, ret)
value = ret
elif isinstance(value, MethodType):
ret = self._getcache(value)
if not ret:
ret = BarreledMethod(self, value)
self._putcache(value, ret)
value = ret
elif isinstance(value, FunctionType):
ret = self._getcache(value)
if not ret:
ret = BarreledFunction(self, value)
self._putcache(value, ret)
value = ret
elif isinstance(value, (tuple,list)):
ret = self._getcache(value)
if ret is None:
vt = type(value)
ret = vt(imap(self._brine, iter(value)))
self._putcache(value, ret)
value = ret
elif isinstance(value, dict):
ret = self._getcache(value)
if ret is None:
ret = dict(self._brine(value.items()))
self._putcache(value, ret)
value = ret
return value
#
# The end.