ProxyTypes-0.9/0000755000000000000000000000000012655467325012245 5ustar rootrootProxyTypes-0.9/ProxyTypes.egg-info/0000755000000000000000000000000012655467325016105 5ustar rootrootProxyTypes-0.9/ProxyTypes.egg-info/top_level.txt0000666000000000000000000000000510457504500020617 0ustar rootrootpeak ProxyTypes-0.9/ProxyTypes.egg-info/SOURCES.txt0000666000000000000000000000053310457504500017757 0ustar rootrootREADME.txt quirky-tests.txt setup.py test_proxies.py ProxyTypes.egg-info/PKG-INFO ProxyTypes.egg-info/SOURCES.txt ProxyTypes.egg-info/dependency_links.txt ProxyTypes.egg-info/namespace_packages.txt ProxyTypes.egg-info/top_level.txt ez_setup/README.txt ez_setup/__init__.py peak/__init__.py peak/util/__init__.py peak/util/proxies.py ProxyTypes-0.9/ProxyTypes.egg-info/PKG-INFO0000666000000000000000000002322010457504500017166 0ustar rootrootMetadata-Version: 1.0 Name: ProxyTypes Version: 0.9 Summary: General purpose proxy and wrapper types Home-page: http://cheeseshop.python.org/pypi/ProxyTypes Author: Phillip J. Eby Author-email: peak@eby-sarna.com License: PSF or ZPL Description: Simple Proxy Types ================== The ``peak.util.proxies`` module provides some useful base classes for creating proxies and wrappers for ordinary Python objects. Proxy objects automatically delegate all attribute access and operations to the proxied object. Wrappers are similar, but can be subclassed to allow additional attributes and operations to be added to the wrapped object. Note that these proxy types are not intended to be tamper-proof; the unproxied form of an object can be readily accessed using a proxy's ``__subject__`` attribute, and some proxy types even allow this attribute to be set. (This can be handy for algorithms that lazily create circular structures and thus need to be able to hand out "forward reference" proxies.) .. contents:: **Table of Contents** Proxy Basics ------------ Here's a quick demo of the ``ObjectProxy`` type:: >>> from peak.util.proxies import ObjectProxy >>> p = ObjectProxy(42) >>> p 42 >>> isinstance(p, int) True >>> p.__class__ >>> p*2 84 >>> 'X' * p 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >>> hex(p) '0x2a' >>> chr(p) '*' >>> p ^ 1 43 >>> p ** 2 1764 As you can see, a proxy is virtually indistinguishable from the object it proxies, except via its ``__subject__`` attribute, and its ``type()``:: >>> p.__subject__ 42 >>> type(p) You can change the ``__subject__`` of an ``ObjectProxy``, and it will then refer to something else:: >>> p.__subject__ = 99 >>> p 99 >>> p-33 66 >>> p.__subject__ = "foo" >>> p 'foo' All operations are delegated to the subject, including setattr and delattr:: >>> class Dummy: pass >>> d = Dummy() >>> p = ObjectProxy(d) >>> p.foo = "bar" >>> d.foo 'bar' >>> del p.foo >>> hasattr(d,'foo') False Callback Proxies ---------------- Sometimes, you may want a proxy's subject to be determined dynamically whenever the proxy is used. For this purpose, you can use the ``CallbackProxy`` type, which accepts a callback function and creates a proxy that will invoke the callback in order to get the target. Here's a quick example of a counter that gets incremented each time it's used, from zero to three:: >>> from peak.util.proxies import CallbackProxy >>> callback = iter(range(4)).next >>> counter = CallbackProxy(callback) >>> counter 0 >>> counter 1 >>> str(counter) '2' >>> hex(counter) '0x3' >>> counter Traceback (most recent call last): ... StopIteration As you can see, the callback is automatically invoked on any attempt to use the proxy. This is a somewhat silly example; a better one would be something like a ``thread_id`` proxy that is always equal to the ID # of the thread it's running in. A callback proxy's callback can be obtained or changed via the ``get_callback`` and ``set_callback`` functions:: >>> from peak.util.proxies import get_callback, set_callback >>> set_callback(counter, lambda: 42) >>> counter 42 >>> get_callback(counter) at ...> Lazy Proxies ------------ A ``LazyProxy`` is similar to a ``DynamicProxy``, but its callback is called at most once, and then cached:: >>> from peak.util.proxies import LazyProxy >>> def callback(): ... print "called!" ... return 42 >>> lazy = LazyProxy(callback) >>> lazy called! 42 >>> lazy 42 You can use the ``get_callback`` and ``set_callback`` functions on lazy proxies, but it has no effect if the callback was already called:: >>> set_callback(lazy, lambda: 99) >>> lazy 42 But you can use the ``get_cache`` and ``set_cache`` functions to tamper with the cached value:: >>> from peak.util.proxies import get_cache, set_cache >>> get_cache(lazy) 42 >>> set_cache(lazy, 99) >>> lazy 99 Wrappers -------- The ``ObjectWrapper``, ``CallbackWrapper`` and ``LazyWrapper`` classes are similar to their proxy counterparts, except that they are intended to be subclassed in order to add custom extra attributes or methods. Any attribute that exists in a subclass of these classes will be read or written from the wrapper instance, instead of the wrapped object. For example:: >>> from peak.util.proxies import ObjectWrapper >>> class NameWrapper(ObjectWrapper): ... name = None ... def __init__(self, ob, name): ... ObjectWrapper.__init__(self, ob) ... self.name = name ... def __str__(self): ... return self.name >>> w = NameWrapper(42, "The Ultimate Answer") >>> w 42 >>> print w The Ultimate Answer >>> w * 2 84 >>> w.name 'The Ultimate Answer' Notice that any attributes you add must be defined *in the class*. You can't add arbitary attributes at runtime, because they'll be set on the wrapped object instead of the wrapper:: >>> w.foo = 'bar' Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'foo' Note that this means that all instance attributes must be implemented as either slots, properties, or have a default value defined in the class body (like the ``name = None`` shown in the example above. The ``CallbackWrapper`` and ``LazyWrapper`` base classes are basically the same as ``ObjectWrapper``, except that they use a callback or cached lazy callback instead of expecting an object as their subject. Creating Custom Subclasses and Mixins ------------------------------------- In addition to all the concrete classes described above, there are also two abstract base classes: ``AbstractProxy`` and ``AbstractWrapper``. If you want to create a mixin type that can be used with any of the concrete types, you should subclass the abstract version and set ``__slots__`` to an empty list:: >>> from peak.util.proxies import AbstractWrapper >>> class NamedMixin(AbstractWrapper): ... __slots__ = [] ... name = None ... def __init__(self, ob, name): ... super(NamedMixin, self).__init__(ob) ... self.name = name ... def __str__(self): ... return self.name Then, when you mix it in with the respective base class, you can add back in any necessary slots, or leave off ``__slots__`` to give the subclass instances a dictionary of their own:: >>> from peak.util.proxies import CallbackWrapper, LazyWrapper >>> class NamedObject(NamedMixin, ObjectWrapper): pass >>> class NamedCallback(NamedMixin, CallbackWrapper): pass >>> class NamedLazy(NamedMixin, LazyWrapper): pass >>> print NamedObject(42, "The Answer") The Answer >>> n = NamedCallback(callback, "Test") >>> n called! 42 >>> n called! 42 >>> n = NamedLazy(callback, "Once") >>> n called! 42 >>> n 42 Both the ``AbstractProxy`` and ``AbstractWrapper`` base classes work by assuming that ``self.__subject__`` will be the wrapped or proxied object. If you don't want to use any of the standard three ways of defining ``__subject__`` (i.e., as an object, callback, or lazy callback), you will need to subclass ``AbstractProxy`` or ``AbstractWrapper`` and provide your own way of defining ``__subject__``. Mailing List ------------ Please direct questions regarding this package to the PEAK mailing list; see http://www.eby-sarna.com/mailman/listinfo/PEAK/ for details. Platform: UNKNOWN ProxyTypes-0.9/ProxyTypes.egg-info/namespace_packages.txt0000666000000000000000000000001710457504500022423 0ustar rootrootpeak peak.util ProxyTypes-0.9/ProxyTypes.egg-info/dependency_links.txt0000666000000000000000000000000110457504500022140 0ustar rootroot ProxyTypes-0.9/peak/0000755000000000000000000000000012655467325013165 5ustar rootrootProxyTypes-0.9/peak/util/0000755000000000000000000000000012655467325014142 5ustar rootrootProxyTypes-0.9/peak/util/__init__.py0000666000000000000000000000006710457444266016257 0ustar rootroot__import__('pkg_resources').declare_namespace(__name__)ProxyTypes-0.9/peak/util/proxies.py0000666000000000000000000001221510457503724016202 0ustar rootrootclass AbstractProxy(object): """Delegates all operations (except ``.__subject__``) to another object""" __slots__ = () def __call__(self,*args,**kw): return self.__subject__(*args,**kw) def __getattribute__(self, attr, oga=object.__getattribute__): subject = oga(self,'__subject__') if attr=='__subject__': return subject return getattr(subject,attr) def __setattr__(self,attr,val, osa=object.__setattr__): if attr=='__subject__': osa(self,attr,val) else: setattr(self.__subject__,attr,val) def __delattr__(self,attr, oda=object.__delattr__): if attr=='__subject__': oda(self,attr) else: delattr(self.__subject__,attr) def __nonzero__(self): return bool(self.__subject__) def __getitem__(self,arg): return self.__subject__[arg] def __setitem__(self,arg,val): self.__subject__[arg] = val def __delitem__(self,arg): del self.__subject__[arg] def __getslice__(self,i,j): return self.__subject__[i:j] def __setslice__(self,i,j,val): self.__subject__[i:j] = val def __delslice__(self,i,j): del self.__subject__[i:j] def __contains__(self,ob): return ob in self.__subject__ for name in 'repr str hash len abs complex int long float iter oct hex'.split(): exec "def __%s__(self): return %s(self.__subject__)" % (name,name) for name in 'cmp', 'coerce', 'divmod': exec "def __%s__(self,ob): return %s(self.__subject__,ob)" % (name,name) for name,op in [ ('lt','<'), ('gt','>'), ('le','<='), ('ge','>='), ('eq','=='), ('ne','!=') ]: exec "def __%s__(self,ob): return self.__subject__ %s ob" % (name,op) for name,op in [('neg','-'), ('pos','+'), ('invert','~')]: exec "def __%s__(self): return %s self.__subject__" % (name,op) for name, op in [ ('or','|'), ('and','&'), ('xor','^'), ('lshift','<<'), ('rshift','>>'), ('add','+'), ('sub','-'), ('mul','*'), ('div','/'), ('mod','%'), ('truediv','/'), ('floordiv','//') ]: exec ( "def __%(name)s__(self,ob):\n" " return self.__subject__ %(op)s ob\n" "\n" "def __r%(name)s__(self,ob):\n" " return ob %(op)s self.__subject__\n" "\n" "def __i%(name)s__(self,ob):\n" " self.__subject__ %(op)s=ob\n" " return self\n" ) % locals() del name, op # Oddball signatures def __rdivmod__(self,ob): return divmod(ob, self.__subject__) def __pow__(self,*args): return pow(self.__subject__,*args) def __ipow__(self,ob): self.__subject__ **= ob return self def __rpow__(self,ob): return pow(ob, self.__subject__) class ObjectProxy(AbstractProxy): """Proxy for a specific object""" __slots__ = "__subject__" def __init__(self,subject): self.__subject__ = subject class CallbackProxy(AbstractProxy): """Proxy for a dynamically-chosen object""" __slots__ = '__callback__' def __init__(self, func): set_callback(self,func) set_callback = CallbackProxy.__callback__.__set__ get_callback = CallbackProxy.__callback__.__get__ CallbackProxy.__subject__ = property(lambda self, gc=get_callback: gc(self)()) class LazyProxy(CallbackProxy): """Proxy for a lazily-obtained object, that is cached on first use""" __slots__ = "__cache__" get_cache = LazyProxy.__cache__.__get__ set_cache = LazyProxy.__cache__.__set__ def __subject__(self, get_cache=get_cache, set_cache=set_cache): try: return get_cache(self) except AttributeError: set_cache(self, get_callback(self)()) return get_cache(self) LazyProxy.__subject__ = property(__subject__, set_cache) del __subject__ class AbstractWrapper(AbstractProxy): """Mixin to allow extra behaviors and attributes on proxy instance""" __slots__ = () def __getattribute__(self, attr, oga=object.__getattribute__): if attr.startswith('__'): subject = oga(self,'__subject__') if attr=='__subject__': return subject return getattr(subject,attr) return oga(self,attr) def __getattr__(self,attr, oga=object.__getattribute__): return getattr(oga(self,'__subject__'), attr) def __setattr__(self,attr,val, osa=object.__setattr__): if ( attr=='__subject__' or hasattr(type(self),attr) and not attr.startswith('__') ): osa(self,attr,val) else: setattr(self.__subject__,attr,val) def __delattr__(self,attr, oda=object.__delattr__): if ( attr=='__subject__' or hasattr(type(self),attr) and not attr.startswith('__') ): oda(self,attr) else: delattr(self.__subject__,attr) class ObjectWrapper(ObjectProxy, AbstractWrapper): __slots__ = () class CallbackWrapper(CallbackProxy, AbstractWrapper): __slots__ = () class LazyWrapper(LazyProxy, AbstractWrapper): __slots__ = () ProxyTypes-0.9/peak/__init__.py0000666000000000000000000000006710457444260015274 0ustar rootroot__import__('pkg_resources').declare_namespace(__name__)ProxyTypes-0.9/ez_setup/0000755000000000000000000000000012655467325014103 5ustar rootrootProxyTypes-0.9/ez_setup/__init__.py0000666000000000000000000001673110457504346016221 0ustar rootroot#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6b4" DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', } import sys, os def _validate_md5(egg_name, data): if egg_name in md5_data: from md5 import md5 digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ try: import setuptools if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) except ImportError: egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg import pkg_resources try: pkg_resources.require("setuptools>="+version) except pkg_resources.VersionConflict: # XXX could we install in a subprocess here? print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first." ) % version sys.exit(2) def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: import tempfile, shutil tmpdir = tempfile.mkdtemp(prefix="easy_install-") try: egg = download_setuptools(version, to_dir=tmpdir, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: shutil.rmtree(tmpdir) else: if setuptools.__version__ == '0.0.1': # tell the user to uninstall obsolete version use_setuptools(version) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re from md5 import md5 for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) ProxyTypes-0.9/ez_setup/README.txt0000666000000000000000000000114610457504346015600 0ustar rootrootThis directory exists so that Subversion-based projects can share a single copy of the ``ez_setup`` bootstrap module for ``setuptools``, and have it automatically updated in their projects when ``setuptools`` is updated. For your convenience, you may use the following svn:externals definition:: ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup You can set this by executing this command in your project directory:: svn propedit svn:externals . And then adding the line shown above to the file that comes up for editing. Then, whenever you update your project, ``ez_setup`` will be updated as well. ProxyTypes-0.9/test_proxies.py0000666000000000000000000001157010457504036015344 0ustar rootrootimport unittest from peak.util.proxies import * class ProxyTestMixin: def checkInteger(self, v): p = self.proxied(v) self.assertEqual(p|010101, v|010101) self.assertEqual(p&010101, v&010101) self.assertEqual(p^010101, v^010101) self.assertEqual(~p, ~v) self.assertEqual(p<<3, v<<3) self.assertEqual(p>>2, v>>2) self.assertEqual(010101|p, 010101|v) self.assertEqual(010101&p, 010101&v) self.assertEqual(010101^p, 010101^v) self.assertEqual(3<>p, 2>>v) for f in hex, oct: self.assertEqual(f(p), f(v)) self.checkNumeric(v) def checkNumeric(self, v): p = self.proxied(v) self.assertEqual(p*2, v*2) self.assertEqual(2*p, 2*v) self.assertEqual(2**p, 2**v) self.assertEqual(p**2, v**2) self.assertEqual(-p, -v) self.assertEqual(+p, +v) for f in abs, int, long, float, hash, complex: self.assertEqual(f(p), f(v)) self.assertEqual(p<22, v<22) self.assertEqual(p>=10, v>=10) self.assertEqual(p>9.0, v>9.0) self.assertEqual(p<=9.25, v<=9.25) self.assertEqual(p==7, v==7) self.assertEqual(p!=18, v!=18) self.assertEqual(16+p, 16+v) self.assertEqual(62-p, 62-v) self.assertEqual(p+16, v+16) self.assertEqual(p-62, v-62) self.assertEqual(p%7, v%7) self.assertEqual(p/6, v/6) self.assertEqual(22=p, 10>=v) self.assertEqual(9.0>p, 9.0>v) self.assertEqual(9.25<=p, 9.25<=v) self.assertEqual(7==p, 7==v) self.assertEqual(18!=p, 18!=v) self.assertEqual(cmp(p,14), cmp(v,14)) self.assertEqual(cmp(14,p), cmp(14,v)) self.assertEqual(divmod(p,3), divmod(v,3)) if v: self.assertEqual(divmod(3,p), divmod(3,v)) self.assertEqual(62//p, 62//v) self.assertEqual(7/p, 7/v) self.assertEqual(7%p, 7%v) self.checkBasics(v) def checkList(self, v): p = self.proxied(v) for i in range(len(v)): self.assertEqual(p[i], v[i]) self.assertEqual(p[i:], v[i:]) self.assertEqual(p[:i], v[:i]) self.assertEqual(p[i::-1], v[i::-1]) self.checkContainer(v) c = list(v) del p[::1] del c[::1] self.assertEqual(v, c) p[1:1] = [23] c[1:1] = [23] self.assertEqual(v, c) def checkContainer(self, v): p = self.proxied(v) self.assertEqual(list(p), list(v)) self.assertEqual(list(iter(p)), list(iter(v))) self.assertEqual(len(p), len(v)) self.assertEqual(42 in p, 42 in v) self.assertEqual(99 in p, 99 in v) self.checkBasics(v) def checkBasics(self, v): p = self.proxied(v) for f in bool, repr, str: self.assertEqual(f(p), f(v)) def testNumbers(self): for i in range(20): self.checkInteger(i) f = -40 while f<=20.0: self.checkNumeric(f) f += 2.25 def testLists(self): from UserList import UserList for d in [1,2], [3,42,59], [99,23,55]: self.checkList(d) self.checkList(UserList(d)) class InPlaceMixin(ProxyTestMixin): def checkInteger(self, vv): mk = lambda: (self.proxied(vv), vv) p,v = mk(); p|=010101; v|=010101; self.assertEqual(p.__subject__, v) p,v = mk(); p&=010101; v&=010101; self.assertEqual(p.__subject__, v) p,v = mk(); p^=010101; v^=010101; self.assertEqual(p.__subject__, v) p,v = mk(); p<<=3; v<<=3; self.assertEqual(p.__subject__, v) p,v = mk(); p>>=3; v>>=3; self.assertEqual(p.__subject__, v) ProxyTestMixin.checkInteger(self, vv) def checkNumeric(self, vv): mk = lambda: (self.proxied(vv), vv) p,v = mk(); p+=17; v+=17; self.assertEqual(p.__subject__, v) p,v = mk(); p-=22; v-=22; self.assertEqual(p.__subject__, v) p,v = mk(); p*=15; v*=15; self.assertEqual(p.__subject__, v) p,v = mk(); p//=3; v//=3; self.assertEqual(p.__subject__, v) p,v = mk(); p**=2; v**=2; self.assertEqual(p.__subject__, v) p,v = mk(); p/=61; v/=61; self.assertEqual(p.__subject__, v) p,v = mk(); p%=19; v%=19; self.assertEqual(p.__subject__, v) ProxyTestMixin.checkNumeric(self, vv) class TestCallbackProxy(ProxyTestMixin, unittest.TestCase): proxied = lambda self, v: CallbackProxy(lambda:v) class TestObjectProxy(InPlaceMixin, unittest.TestCase): proxied = ObjectProxy class TestLazyProxy(InPlaceMixin, unittest.TestCase): proxied = lambda self, v: LazyProxy(lambda:v) def additional_tests(): import doctest return doctest.DocFileSuite( 'README.txt', 'quirky-tests.txt', optionflags=doctest.ELLIPSIS, ) ProxyTypes-0.9/setup.py0000666000000000000000000000201110457504254013744 0ustar rootroot#!/usr/bin/env python """Distutils setup file""" import ez_setup ez_setup.use_setuptools() from setuptools import setup # Metadata PACKAGE_NAME = "ProxyTypes" PACKAGE_VERSION = "0.9" PACKAGES = ['peak', 'peak.util'] def get_description(): # Get our long description from the documentation f = file('README.txt') lines = [] for line in f: if not line.strip(): break # skip to first blank line for line in f: if line.startswith('.. contents::'): break # read to table of contents lines.append(line) f.close() return ''.join(lines) setup( name=PACKAGE_NAME, version=PACKAGE_VERSION, description="General purpose proxy and wrapper types", long_description = open('README.txt').read(), url = "http://cheeseshop.python.org/pypi/ProxyTypes", author="Phillip J. Eby", author_email="peak@eby-sarna.com", license="PSF or ZPL", test_suite = 'test_proxies', packages = PACKAGES, namespace_packages = PACKAGES, ) ProxyTypes-0.9/setup.cfg0000666000000000000000000000010010457504500014042 0ustar rootroot[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 ProxyTypes-0.9/README.txt0000666000000000000000000001663710457463052013753 0ustar rootrootSimple Proxy Types ================== The ``peak.util.proxies`` module provides some useful base classes for creating proxies and wrappers for ordinary Python objects. Proxy objects automatically delegate all attribute access and operations to the proxied object. Wrappers are similar, but can be subclassed to allow additional attributes and operations to be added to the wrapped object. Note that these proxy types are not intended to be tamper-proof; the unproxied form of an object can be readily accessed using a proxy's ``__subject__`` attribute, and some proxy types even allow this attribute to be set. (This can be handy for algorithms that lazily create circular structures and thus need to be able to hand out "forward reference" proxies.) .. contents:: **Table of Contents** Proxy Basics ------------ Here's a quick demo of the ``ObjectProxy`` type:: >>> from peak.util.proxies import ObjectProxy >>> p = ObjectProxy(42) >>> p 42 >>> isinstance(p, int) True >>> p.__class__ >>> p*2 84 >>> 'X' * p 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >>> hex(p) '0x2a' >>> chr(p) '*' >>> p ^ 1 43 >>> p ** 2 1764 As you can see, a proxy is virtually indistinguishable from the object it proxies, except via its ``__subject__`` attribute, and its ``type()``:: >>> p.__subject__ 42 >>> type(p) You can change the ``__subject__`` of an ``ObjectProxy``, and it will then refer to something else:: >>> p.__subject__ = 99 >>> p 99 >>> p-33 66 >>> p.__subject__ = "foo" >>> p 'foo' All operations are delegated to the subject, including setattr and delattr:: >>> class Dummy: pass >>> d = Dummy() >>> p = ObjectProxy(d) >>> p.foo = "bar" >>> d.foo 'bar' >>> del p.foo >>> hasattr(d,'foo') False Callback Proxies ---------------- Sometimes, you may want a proxy's subject to be determined dynamically whenever the proxy is used. For this purpose, you can use the ``CallbackProxy`` type, which accepts a callback function and creates a proxy that will invoke the callback in order to get the target. Here's a quick example of a counter that gets incremented each time it's used, from zero to three:: >>> from peak.util.proxies import CallbackProxy >>> callback = iter(range(4)).next >>> counter = CallbackProxy(callback) >>> counter 0 >>> counter 1 >>> str(counter) '2' >>> hex(counter) '0x3' >>> counter Traceback (most recent call last): ... StopIteration As you can see, the callback is automatically invoked on any attempt to use the proxy. This is a somewhat silly example; a better one would be something like a ``thread_id`` proxy that is always equal to the ID # of the thread it's running in. A callback proxy's callback can be obtained or changed via the ``get_callback`` and ``set_callback`` functions:: >>> from peak.util.proxies import get_callback, set_callback >>> set_callback(counter, lambda: 42) >>> counter 42 >>> get_callback(counter) at ...> Lazy Proxies ------------ A ``LazyProxy`` is similar to a ``DynamicProxy``, but its callback is called at most once, and then cached:: >>> from peak.util.proxies import LazyProxy >>> def callback(): ... print "called!" ... return 42 >>> lazy = LazyProxy(callback) >>> lazy called! 42 >>> lazy 42 You can use the ``get_callback`` and ``set_callback`` functions on lazy proxies, but it has no effect if the callback was already called:: >>> set_callback(lazy, lambda: 99) >>> lazy 42 But you can use the ``get_cache`` and ``set_cache`` functions to tamper with the cached value:: >>> from peak.util.proxies import get_cache, set_cache >>> get_cache(lazy) 42 >>> set_cache(lazy, 99) >>> lazy 99 Wrappers -------- The ``ObjectWrapper``, ``CallbackWrapper`` and ``LazyWrapper`` classes are similar to their proxy counterparts, except that they are intended to be subclassed in order to add custom extra attributes or methods. Any attribute that exists in a subclass of these classes will be read or written from the wrapper instance, instead of the wrapped object. For example:: >>> from peak.util.proxies import ObjectWrapper >>> class NameWrapper(ObjectWrapper): ... name = None ... def __init__(self, ob, name): ... ObjectWrapper.__init__(self, ob) ... self.name = name ... def __str__(self): ... return self.name >>> w = NameWrapper(42, "The Ultimate Answer") >>> w 42 >>> print w The Ultimate Answer >>> w * 2 84 >>> w.name 'The Ultimate Answer' Notice that any attributes you add must be defined *in the class*. You can't add arbitary attributes at runtime, because they'll be set on the wrapped object instead of the wrapper:: >>> w.foo = 'bar' Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'foo' Note that this means that all instance attributes must be implemented as either slots, properties, or have a default value defined in the class body (like the ``name = None`` shown in the example above. The ``CallbackWrapper`` and ``LazyWrapper`` base classes are basically the same as ``ObjectWrapper``, except that they use a callback or cached lazy callback instead of expecting an object as their subject. Creating Custom Subclasses and Mixins ------------------------------------- In addition to all the concrete classes described above, there are also two abstract base classes: ``AbstractProxy`` and ``AbstractWrapper``. If you want to create a mixin type that can be used with any of the concrete types, you should subclass the abstract version and set ``__slots__`` to an empty list:: >>> from peak.util.proxies import AbstractWrapper >>> class NamedMixin(AbstractWrapper): ... __slots__ = [] ... name = None ... def __init__(self, ob, name): ... super(NamedMixin, self).__init__(ob) ... self.name = name ... def __str__(self): ... return self.name Then, when you mix it in with the respective base class, you can add back in any necessary slots, or leave off ``__slots__`` to give the subclass instances a dictionary of their own:: >>> from peak.util.proxies import CallbackWrapper, LazyWrapper >>> class NamedObject(NamedMixin, ObjectWrapper): pass >>> class NamedCallback(NamedMixin, CallbackWrapper): pass >>> class NamedLazy(NamedMixin, LazyWrapper): pass >>> print NamedObject(42, "The Answer") The Answer >>> n = NamedCallback(callback, "Test") >>> n called! 42 >>> n called! 42 >>> n = NamedLazy(callback, "Once") >>> n called! 42 >>> n 42 Both the ``AbstractProxy`` and ``AbstractWrapper`` base classes work by assuming that ``self.__subject__`` will be the wrapped or proxied object. If you don't want to use any of the standard three ways of defining ``__subject__`` (i.e., as an object, callback, or lazy callback), you will need to subclass ``AbstractProxy`` or ``AbstractWrapper`` and provide your own way of defining ``__subject__``. Mailing List ------------ Please direct questions regarding this package to the PEAK mailing list; see http://www.eby-sarna.com/mailman/listinfo/PEAK/ for details. ProxyTypes-0.9/quirky-tests.txt0000666000000000000000000000356010457503710015465 0ustar rootrootThis file tests proxies for some odd aspects of the Python object model. >>> class Dummy(object): ... def __getitem__(self, key): ... print "get", key ... def __getslice__(self, s, e): ... print "get", s,e ... def __contains__(self, key): ... print "contains", key ... def __setitem__(self, key, val): ... print "set", key, val ... def __setslice__(self, s, e, val): ... print "set", s,e,val ... def __delitem__(self, key): ... print "del", key ... def __delslice__(self, s, e): ... print "del", s,e ... def __cmp__(self, x): ... print "comparing", x ... return 0 ... def __pow__(self,*args): ... print "pow", args ... def __rpow__(self,*args): ... print "rpow", args >>> d = Dummy() >>> from peak.util.proxies import ObjectProxy >>> p = ObjectProxy(d) >>> d[1:2] get 1 2 >>> p[1:2] get 1 2 >>> d[1:2:3] get slice(1, 2, 3) >>> p[1:2:3] get slice(1, 2, 3) >>> d["x":"y"] get slice('x', 'y', None) >>> p["x":"y"] get slice('x', 'y', None) >>> "x" in d contains x False >>> "x" in p contains x False >>> d[1] = 1 set 1 1 >>> p[1] = 1 set 1 1 >>> d[1:2] = 3 set 1 2 3 >>> p[1:2] = 3 set 1 2 3 >>> d[1:2:3] = 4 set slice(1, 2, 3) 4 >>> p[1:2:3] = 4 set slice(1, 2, 3) 4 >>> del d[1] del 1 >>> del p[1] del 1 >>> del d[1:2] del 1 2 >>> del p[1:2] del 1 2 >>> del d[1:2:3] del slice(1, 2, 3) >>> del p[1:2:3] del slice(1, 2, 3) >>> del d["x":"y"] del slice('x', 'y', None) >>> del p["x":"y"] del slice('x', 'y', None) >>> cmp(d,27) comparing 27 0 >>> cmp(p,27) comparing 27 0 >>> cmp(27,d) comparing 27 0 >>> cmp(27,p) comparing 27 0 >>> pow(d,27) pow (27,) >>> pow(p,27) pow (27,) >>> pow(d,51,27) pow (51, 27) >>> pow(p,51,27) pow (51, 27) >>> pow(19,d) rpow (19,) >>> pow(19,p) rpow (19,) ProxyTypes-0.9/PKG-INFO0000666000000000000000000002322010457504500013326 0ustar rootrootMetadata-Version: 1.0 Name: ProxyTypes Version: 0.9 Summary: General purpose proxy and wrapper types Home-page: http://cheeseshop.python.org/pypi/ProxyTypes Author: Phillip J. Eby Author-email: peak@eby-sarna.com License: PSF or ZPL Description: Simple Proxy Types ================== The ``peak.util.proxies`` module provides some useful base classes for creating proxies and wrappers for ordinary Python objects. Proxy objects automatically delegate all attribute access and operations to the proxied object. Wrappers are similar, but can be subclassed to allow additional attributes and operations to be added to the wrapped object. Note that these proxy types are not intended to be tamper-proof; the unproxied form of an object can be readily accessed using a proxy's ``__subject__`` attribute, and some proxy types even allow this attribute to be set. (This can be handy for algorithms that lazily create circular structures and thus need to be able to hand out "forward reference" proxies.) .. contents:: **Table of Contents** Proxy Basics ------------ Here's a quick demo of the ``ObjectProxy`` type:: >>> from peak.util.proxies import ObjectProxy >>> p = ObjectProxy(42) >>> p 42 >>> isinstance(p, int) True >>> p.__class__ >>> p*2 84 >>> 'X' * p 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >>> hex(p) '0x2a' >>> chr(p) '*' >>> p ^ 1 43 >>> p ** 2 1764 As you can see, a proxy is virtually indistinguishable from the object it proxies, except via its ``__subject__`` attribute, and its ``type()``:: >>> p.__subject__ 42 >>> type(p) You can change the ``__subject__`` of an ``ObjectProxy``, and it will then refer to something else:: >>> p.__subject__ = 99 >>> p 99 >>> p-33 66 >>> p.__subject__ = "foo" >>> p 'foo' All operations are delegated to the subject, including setattr and delattr:: >>> class Dummy: pass >>> d = Dummy() >>> p = ObjectProxy(d) >>> p.foo = "bar" >>> d.foo 'bar' >>> del p.foo >>> hasattr(d,'foo') False Callback Proxies ---------------- Sometimes, you may want a proxy's subject to be determined dynamically whenever the proxy is used. For this purpose, you can use the ``CallbackProxy`` type, which accepts a callback function and creates a proxy that will invoke the callback in order to get the target. Here's a quick example of a counter that gets incremented each time it's used, from zero to three:: >>> from peak.util.proxies import CallbackProxy >>> callback = iter(range(4)).next >>> counter = CallbackProxy(callback) >>> counter 0 >>> counter 1 >>> str(counter) '2' >>> hex(counter) '0x3' >>> counter Traceback (most recent call last): ... StopIteration As you can see, the callback is automatically invoked on any attempt to use the proxy. This is a somewhat silly example; a better one would be something like a ``thread_id`` proxy that is always equal to the ID # of the thread it's running in. A callback proxy's callback can be obtained or changed via the ``get_callback`` and ``set_callback`` functions:: >>> from peak.util.proxies import get_callback, set_callback >>> set_callback(counter, lambda: 42) >>> counter 42 >>> get_callback(counter) at ...> Lazy Proxies ------------ A ``LazyProxy`` is similar to a ``DynamicProxy``, but its callback is called at most once, and then cached:: >>> from peak.util.proxies import LazyProxy >>> def callback(): ... print "called!" ... return 42 >>> lazy = LazyProxy(callback) >>> lazy called! 42 >>> lazy 42 You can use the ``get_callback`` and ``set_callback`` functions on lazy proxies, but it has no effect if the callback was already called:: >>> set_callback(lazy, lambda: 99) >>> lazy 42 But you can use the ``get_cache`` and ``set_cache`` functions to tamper with the cached value:: >>> from peak.util.proxies import get_cache, set_cache >>> get_cache(lazy) 42 >>> set_cache(lazy, 99) >>> lazy 99 Wrappers -------- The ``ObjectWrapper``, ``CallbackWrapper`` and ``LazyWrapper`` classes are similar to their proxy counterparts, except that they are intended to be subclassed in order to add custom extra attributes or methods. Any attribute that exists in a subclass of these classes will be read or written from the wrapper instance, instead of the wrapped object. For example:: >>> from peak.util.proxies import ObjectWrapper >>> class NameWrapper(ObjectWrapper): ... name = None ... def __init__(self, ob, name): ... ObjectWrapper.__init__(self, ob) ... self.name = name ... def __str__(self): ... return self.name >>> w = NameWrapper(42, "The Ultimate Answer") >>> w 42 >>> print w The Ultimate Answer >>> w * 2 84 >>> w.name 'The Ultimate Answer' Notice that any attributes you add must be defined *in the class*. You can't add arbitary attributes at runtime, because they'll be set on the wrapped object instead of the wrapper:: >>> w.foo = 'bar' Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'foo' Note that this means that all instance attributes must be implemented as either slots, properties, or have a default value defined in the class body (like the ``name = None`` shown in the example above. The ``CallbackWrapper`` and ``LazyWrapper`` base classes are basically the same as ``ObjectWrapper``, except that they use a callback or cached lazy callback instead of expecting an object as their subject. Creating Custom Subclasses and Mixins ------------------------------------- In addition to all the concrete classes described above, there are also two abstract base classes: ``AbstractProxy`` and ``AbstractWrapper``. If you want to create a mixin type that can be used with any of the concrete types, you should subclass the abstract version and set ``__slots__`` to an empty list:: >>> from peak.util.proxies import AbstractWrapper >>> class NamedMixin(AbstractWrapper): ... __slots__ = [] ... name = None ... def __init__(self, ob, name): ... super(NamedMixin, self).__init__(ob) ... self.name = name ... def __str__(self): ... return self.name Then, when you mix it in with the respective base class, you can add back in any necessary slots, or leave off ``__slots__`` to give the subclass instances a dictionary of their own:: >>> from peak.util.proxies import CallbackWrapper, LazyWrapper >>> class NamedObject(NamedMixin, ObjectWrapper): pass >>> class NamedCallback(NamedMixin, CallbackWrapper): pass >>> class NamedLazy(NamedMixin, LazyWrapper): pass >>> print NamedObject(42, "The Answer") The Answer >>> n = NamedCallback(callback, "Test") >>> n called! 42 >>> n called! 42 >>> n = NamedLazy(callback, "Once") >>> n called! 42 >>> n 42 Both the ``AbstractProxy`` and ``AbstractWrapper`` base classes work by assuming that ``self.__subject__`` will be the wrapped or proxied object. If you don't want to use any of the standard three ways of defining ``__subject__`` (i.e., as an object, callback, or lazy callback), you will need to subclass ``AbstractProxy`` or ``AbstractWrapper`` and provide your own way of defining ``__subject__``. Mailing List ------------ Please direct questions regarding this package to the PEAK mailing list; see http://www.eby-sarna.com/mailman/listinfo/PEAK/ for details. Platform: UNKNOWN