Source code for brine.barrel

# 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.